From f2ec89c02f67c32dde3501f933860c656e99422a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Iv=C3=A1n=20Patricio=20Moreno?= Date: Wed, 23 Oct 2019 13:13:50 -0500 Subject: [PATCH 01/84] =?UTF-8?q?Instrucciones=20y=20gu=C3=ADa=20de=20cont?= =?UTF-8?q?ribuci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dfda90912..3e53318f5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,38 @@ -# Eloquent JavaScript +# Traducción al español de Eloquent JavaScript + +En este repositorio haremos la traducción del libro Eloquent JavaScript en español. +Por favor revisa las instrucciones para contribuir y las guías de contribución y las + +## Instrucciones para contribuir + +1. Has un fork de este repositorio +2. Revisa los **Issues** para que veas que capítulos están en progreso. Lo más recomendables es tomar un capítulo que nadie esté trabajando +3. Pon como un Issue que capítulo vas a traducir +4. Cuando lleves un porcentaje considerable del capítulo, has un PR para integrarlo y crear nuevas versiones de los libros +5. Un revisor verificará el texto y hará comentarios o aprobará el PR + +## Guía de contribución + +1. No uses un traductor automático. +2. Cuando un término se use comúnmente en inglés dentro del entorno de la programación, déjalo sin traducir pero usa cursiva la primera vez que se mencione. +3. Intenta usar español neutro. No uses regionalismos de tu país, queremos que esta sea una traducción lo más entendible para todos los hablantes de español. +4. Sigue las reglas de ortografía y gramática. +5. Si tienes dudas acerca de cómo traducir algo, levanta un Issue para que cooperemos. + +## Roles + +Aunque la mayoría de los desarrolladores que sabemos inglés podemos traducir el libro, creo que necesitamos por lo menos 2 personas que levanten la mano como: + +1. Revisores. Se encargarán de verificar los PR de las nuevas traducciones y aprobar o hacer comentarios sobre el PR para continuar. +2. Editores. Revisarán la versión final del texto para unificarla en estilo y revisar que no se hayan cometido errores graves. + +Si tienes tiempo disponible a la semana para hacer esto, levanta un Issue y te daremos acceso de escritura a este repo. + +## Otro tipo de ayuda + +Es probable que necesitemos alguien con conocimientos en edición de imágenes para las imágenes ilustrativas y la portada. Si puedes apoyarnos levanta un Issue. + +## Eloquent JavaScript These are the sources used to build the third edition of Eloquent JavaScript (https://eloquentjavascript.net). From d7c4bd8e15a5d2de7c1991e4df53a70f0258cf3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Iv=C3=A1n=20Patricio=20Moreno?= Date: Wed, 23 Oct 2019 13:32:24 -0500 Subject: [PATCH 02/84] =?UTF-8?q?Agregu=C3=A9=20docs=20folder=20para=20pod?= =?UTF-8?q?er=20publicar=20como=20Github=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.keep | 0 docs/00_intro.html | 226 ++ docs/01_values.html | 293 +++ docs/02_program_structure.html | 515 ++++ docs/03_functions.html | 581 +++++ docs/04_data.html | 794 ++++++ docs/05_higher_order.html | 511 ++++ docs/06_object.html | 660 +++++ docs/07_robot.html | 383 +++ docs/08_error.html | 485 ++++ docs/09_regexp.html | 853 +++++++ docs/10_modules.html | 375 +++ docs/11_async.html | 681 +++++ docs/12_language.html | 502 ++++ docs/13_browser.html | 176 ++ docs/14_dom.html | 572 +++++ docs/15_event.html | 582 +++++ docs/16_game.html | 795 ++++++ docs/17_canvas.html | 746 ++++++ docs/18_http.html | 695 ++++++ docs/19_paint.html | 767 ++++++ docs/20_node.html | 548 +++++ docs/21_skillsharing.html | 636 +++++ docs/Eloquent_JavaScript.epub | 1 + docs/Eloquent_JavaScript.mobi | 1 + docs/Eloquent_JavaScript.pdf | 1 + docs/Eloquent_JavaScript_small.pdf | 1 + docs/author.html | 10 + docs/author.json | 5 + docs/author.txt | 1 + docs/backers.html | 3687 ++++++++++++++++++++++++++++ docs/backers3.html | 802 ++++++ docs/code | 1 + docs/css/ejs.css | 461 ++++ docs/css/game.css | 24 + docs/css/paint.css | 13 + docs/empty.html | 1 + docs/errata.html | 98 + docs/example/bert.json | 4 + docs/example/data.txt | 1 + docs/example/fruit.json | 3 + docs/example/fruit.xml | 5 + docs/example/message.html | 35 + docs/example/muriel.json | 3 + docs/example/submit.html | 22 + docs/example/suzie.json | 5 + docs/favicon.ico | Bin 0 -> 1406 bytes docs/font/cinzel_bold.woff | Bin 0 -> 27364 bytes docs/font/pt_mono.woff | Bin 0 -> 36568 bytes docs/img | 1 + docs/index.html | 146 ++ docs/js/.tern-project | 3 + docs/js/acorn_codemirror.js | 11 + docs/js/chapter_info.js | 715 ++++++ docs/js/code.js | 217 ++ docs/js/ejs.js | 242 ++ docs/js/node_modules | 1 + docs/js/sandbox.js | 642 +++++ 58 files changed, 19539 insertions(+) create mode 100644 docs/.keep create mode 100644 docs/00_intro.html create mode 100644 docs/01_values.html create mode 100644 docs/02_program_structure.html create mode 100644 docs/03_functions.html create mode 100644 docs/04_data.html create mode 100644 docs/05_higher_order.html create mode 100644 docs/06_object.html create mode 100644 docs/07_robot.html create mode 100644 docs/08_error.html create mode 100644 docs/09_regexp.html create mode 100644 docs/10_modules.html create mode 100644 docs/11_async.html create mode 100644 docs/12_language.html create mode 100644 docs/13_browser.html create mode 100644 docs/14_dom.html create mode 100644 docs/15_event.html create mode 100644 docs/16_game.html create mode 100644 docs/17_canvas.html create mode 100644 docs/18_http.html create mode 100644 docs/19_paint.html create mode 100644 docs/20_node.html create mode 100644 docs/21_skillsharing.html create mode 120000 docs/Eloquent_JavaScript.epub create mode 120000 docs/Eloquent_JavaScript.mobi create mode 120000 docs/Eloquent_JavaScript.pdf create mode 120000 docs/Eloquent_JavaScript_small.pdf create mode 100644 docs/author.html create mode 100644 docs/author.json create mode 100644 docs/author.txt create mode 100644 docs/backers.html create mode 100644 docs/backers3.html create mode 120000 docs/code create mode 100644 docs/css/ejs.css create mode 100644 docs/css/game.css create mode 100644 docs/css/paint.css create mode 100644 docs/empty.html create mode 100644 docs/errata.html create mode 100644 docs/example/bert.json create mode 100644 docs/example/data.txt create mode 100644 docs/example/fruit.json create mode 100644 docs/example/fruit.xml create mode 100644 docs/example/message.html create mode 100644 docs/example/muriel.json create mode 100644 docs/example/submit.html create mode 100644 docs/example/suzie.json create mode 100644 docs/favicon.ico create mode 100644 docs/font/cinzel_bold.woff create mode 100644 docs/font/pt_mono.woff create mode 120000 docs/img create mode 100644 docs/index.html create mode 100644 docs/js/.tern-project create mode 100644 docs/js/acorn_codemirror.js create mode 100644 docs/js/chapter_info.js create mode 100644 docs/js/code.js create mode 100644 docs/js/ejs.js create mode 120000 docs/js/node_modules create mode 100644 docs/js/sandbox.js diff --git a/docs/.keep b/docs/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/00_intro.html b/docs/00_intro.html new file mode 100644 index 000000000..f36a4c1f8 --- /dev/null +++ b/docs/00_intro.html @@ -0,0 +1,226 @@ + + + + + Introduction :: Eloquent JavaScript + + + + + + +
+ + +

Introduction

+ +
+ +

We think we are creating the system for our own purposes. We believe we are making it in our own image... But the computer is not really like us. It is a projection of a very slim part of ourselves: that portion devoted to logic, order, rule, and clarity.

+ +
Ellen Ullman, Close to the Machine: Technophilia and its Discontents
+ +
Picture of a screwdriver and a circuit board
+ +

This is a book about instructing computers. Computers are about as common as screwdrivers today, but they are quite a bit more complex, and making them do what you want them to do isn’t always easy.

+ +

If the task you have for your computer is a common, well-understood one, such as showing you your email or acting like a calculator, you can open the appropriate application and get to work. But for unique or open-ended tasks, there probably is no application.

+ +

That is where programming may come in. Programming is the act of constructing a program—a set of precise instructions telling a computer what to do. Because computers are dumb, pedantic beasts, programming is fundamentally tedious and frustrating.

+ +

Fortunately, if you can get over that fact, and maybe even enjoy the rigor of thinking in terms that dumb machines can deal with, programming can be rewarding. It allows you to do things in seconds that would take forever by hand. It is a way to make your computer tool do things that it couldn’t do before. And it provides a wonderful exercise in abstract thinking.

+ +

Most programming is done with programming languages. A programming language is an artificially constructed language used to instruct computers. It is interesting that the most effective way we’ve found to communicate with a computer borrows so heavily from the way we communicate with each other. Like human languages, computer languages allow words and phrases to be combined in new ways, making it possible to express ever new concepts.

+ +

At one point language-based interfaces, such as the BASIC and DOS prompts of the 1980s and 1990s, were the main method of interacting with computers. They have largely been replaced with visual interfaces, which are easier to learn but offer less freedom. Computer languages are still there, if you know where to look. One such language, JavaScript, is built into every modern web browser and is thus available on almost every device.

+ +

This book will try to make you familiar enough with this language to do useful and amusing things with it.

+ +

On programming

+ +

Besides explaining JavaScript, I will introduce the basic principles of programming. Programming, it turns out, is hard. The fundamental rules are simple and clear, but programs built on top of these rules tend to become complex enough to introduce their own rules and complexity. You’re building your own maze, in a way, and you might just get lost in it.

+ +

There will be times when reading this book feels terribly frustrating. If you are new to programming, there will be a lot of new material to digest. Much of this material will then be combined in ways that require you to make additional connections.

+ +

It is up to you to make the necessary effort. When you are struggling to follow the book, do not jump to any conclusions about your own capabilities. You are fine—you just need to keep at it. Take a break, reread some material, and make sure you read and understand the example programs and exercises. Learning is hard work, but everything you learn is yours and will make subsequent learning easier.

+ +
+ +

When action grows unprofitable, gather information; when information grows unprofitable, sleep.

+ +
Ursula K. Le Guin, The Left Hand of Darkness
+ +
+ +

A program is many things. It is a piece of text typed by a programmer, it is the directing force that makes the computer do what it does, it is data in the computer’s memory, yet it controls the actions performed on this same memory. Analogies that try to compare programs to objects we are familiar with tend to fall short. A superficially fitting one is that of a machine—lots of separate parts tend to be involved, and to make the whole thing tick, we have to consider the ways in which these parts interconnect and contribute to the operation of the whole.

+ +

A computer is a physical machine that acts as a host for these immaterial machines. Computers themselves can do only stupidly straightforward things. The reason they are so useful is that they do these things at an incredibly high speed. A program can ingeniously combine an enormous number of these simple actions to do very complicated things.

+ +

A program is a building of thought. It is costless to build, it is weightless, and it grows easily under our typing hands.

+ +

But without care, a program’s size and complexity will grow out of control, confusing even the person who created it. Keeping programs under control is the main problem of programming. When a program works, it is beautiful. The art of programming is the skill of controlling complexity. The great program is subdued—made simple in its complexity.

+ +

Some programmers believe that this complexity is best managed by using only a small set of well-understood techniques in their programs. They have composed strict rules (“best practices”) prescribing the form programs should have and carefully stay within their safe little zone.

+ +

This is not only boring, it is ineffective. New problems often require new solutions. The field of programming is young and still developing rapidly, and it is varied enough to have room for wildly different approaches. There are many terrible mistakes to make in program design, and you should go ahead and make them so that you understand them. A sense of what a good program looks like is developed in practice, not learned from a list of rules.

+ +

Why language matters

+ +

In the beginning, at the birth of computing, there were no programming languages. Programs looked something like this:

+ +
00110001 00000000 00000000
+00110001 00000001 00000001
+00110011 00000001 00000010
+01010001 00001011 00000010
+00100010 00000010 00001000
+01000011 00000001 00000000
+01000001 00000001 00000001
+00010000 00000010 00000000
+01100010 00000000 00000000
+ +

That is a program to add the numbers from 1 to 10 together and print out the result: 1 + 2 + ... + 10 = 55. It could run on a simple, hypothetical machine. To program early computers, it was necessary to set large arrays of switches in the right position or punch holes in strips of cardboard and feed them to the computer. You can probably imagine how tedious and error-prone this procedure was. Even writing simple programs required much cleverness and discipline. Complex ones were nearly inconceivable.

+ +

Of course, manually entering these arcane patterns of bits (the ones and zeros) did give the programmer a profound sense of being a mighty wizard. And that has to be worth something in terms of job satisfaction.

+ +

Each line of the previous program contains a single instruction. It could be written in English like this:

+ +
    + +
  1. + +

    Store the number 0 in memory location 0.

  2. + +
  3. + +

    Store the number 1 in memory location 1.

  4. + +
  5. + +

    Store the value of memory location 1 in memory location 2.

  6. + +
  7. + +

    Subtract the number 11 from the value in memory location 2.

  8. + +
  9. + +

    If the value in memory location 2 is the number 0, continue with instruction 9.

  10. + +
  11. + +

    Add the value of memory location 1 to memory location 0.

  12. + +
  13. + +

    Add the number 1 to the value of memory location 1.

  14. + +
  15. + +

    Continue with instruction 3.

  16. + +
  17. + +

    Output the value of memory location 0.

  18. + +
+ +

Although that is already more readable than the soup of bits, it is still rather obscure. Using names instead of numbers for the instructions and memory locations helps.

+ +
 Set “total” to 0.
+ Set “count” to 1.
+[loop]
+ Set “compare” to “count”.
+ Subtract 11 from “compare”.
+ If “compare” is zero, continue at [end].
+ Add “count” to “total”.
+ Add 1 to “count”.
+ Continue at [loop].
+[end]
+ Output “total”.
+ +

Can you see how the program works at this point? The first two lines give two memory locations their starting values: total will be used to build up the result of the computation, and count will keep track of the number that we are currently looking at. The lines using compare are probably the weirdest ones. The program wants to see whether count is equal to 11 to decide whether it can stop running. Because our hypothetical machine is rather primitive, it can only test whether a number is zero and make a decision based on that. So it uses the memory location labeled compare to compute the value of count - 11 and makes a decision based on that value. The next two lines add the value of count to the result and increment count by 1 every time the program has decided that count is not 11 yet.

+ +

Here is the same program in JavaScript:

+ +
let total = 0, count = 1;
+while (count <= 10) {
+  total += count;
+  count += 1;
+}
+console.log(total);
+// → 55
+ +

This version gives us a few more improvements. Most important, there is no need to specify the way we want the program to jump back and forth anymore. The while construct takes care of that. It continues executing the block (wrapped in braces) below it as long as the condition it was given holds. That condition is count <= 10, which means “count is less than or equal to 10”. We no longer have to create a temporary value and compare that to zero, which was just an uninteresting detail. Part of the power of programming languages is that they can take care of uninteresting details for us.

+ +

At the end of the program, after the while construct has finished, the console.log operation is used to write out the result.

+ +

Finally, here is what the program could look like if we happened to have the convenient operations range and sum available, which respectively create a collection of numbers within a range and compute the sum of a collection of numbers:

+ +
console.log(sum(range(1, 10)));
+// → 55
+ +

The moral of this story is that the same program can be expressed in both long and short, unreadable and readable ways. The first version of the program was extremely obscure, whereas this last one is almost English: log the sum of the range of numbers from 1 to 10. (We will see in later chapters how to define operations like sum and range.)

+ +

A good programming language helps the programmer by allowing them to talk about the actions that the computer has to perform on a higher level. It helps omit details, provides convenient building blocks (such as while and console.log), allows you to define your own building blocks (such as sum and range), and makes those blocks easy to compose.

+ +

What is JavaScript?

+ +

JavaScript was introduced in 1995 as a way to add programs to web pages in the Netscape Navigator browser. The language has since been adopted by all other major graphical web browsers. It has made modern web applications possible—applications with which you can interact directly without doing a page reload for every action. JavaScript is also used in more traditional websites to provide various forms of interactivity and cleverness.

+ +

It is important to note that JavaScript has almost nothing to do with the programming language named Java. The similar name was inspired by marketing considerations rather than good judgment. When JavaScript was being introduced, the Java language was being heavily marketed and was gaining popularity. Someone thought it was a good idea to try to ride along on this success. Now we are stuck with the name.

+ +

After its adoption outside of Netscape, a standard document was written to describe the way the JavaScript language should work so that the various pieces of software that claimed to support JavaScript were actually talking about the same language. This is called the ECMAScript standard, after the Ecma International organization that did the standardization. In practice, the terms ECMAScript and JavaScript can be used interchangeably—they are two names for the same language.

+ +

There are those who will say terrible things about JavaScript. Many of these things are true. When I was required to write something in JavaScript for the first time, I quickly came to despise it. It would accept almost anything I typed but interpret it in a way that was completely different from what I meant. This had a lot to do with the fact that I did not have a clue what I was doing, of course, but there is a real issue here: JavaScript is ridiculously liberal in what it allows. The idea behind this design was that it would make programming in JavaScript easier for beginners. In actuality, it mostly makes finding problems in your programs harder because the system will not point them out to you.

+ +

This flexibility also has its advantages, though. It leaves space for a lot of techniques that are impossible in more rigid languages, and as you will see (for example in Chapter 10), it can be used to overcome some of JavaScript’s shortcomings. After learning the language properly and working with it for a while, I have learned to actually like JavaScript.

+ +

There have been several versions of JavaScript. ECMAScript version 3 was the widely supported version in the time of JavaScript’s ascent to dominance, roughly between 2000 and 2010. During this time, work was underway on an ambitious version 4, which planned a number of radical improvements and extensions to the language. Changing a living, widely used language in such a radical way turned out to be politically difficult, and work on the version 4 was abandoned in 2008, leading to a much less ambitious version 5, which made only some uncontroversial improvements, coming out in 2009. Then in 2015 version 6 came out, a major update that included some of the ideas planned for version 4. Since then we’ve had new, small updates every year.

+ +

The fact that the language is evolving means that browsers have to constantly keep up, and if you’re using an older browser, it may not support every feature. The language designers are careful to not make any changes that could break existing programs, so new browsers can still run old programs. In this book, I’m using the 2017 version of JavaScript.

+ +

Web browsers are not the only platforms on which JavaScript is used. Some databases, such as MongoDB and CouchDB, use JavaScript as their scripting and query language. Several platforms for desktop and server programming, most notably the Node.js project (the subject of Chapter 20), provide an environment for programming JavaScript outside of the browser.

+ +

Code, and what to do with it

+ +

Code is the text that makes up programs. Most chapters in this book contain quite a lot of code. I believe reading code and writing code are indispensable parts of learning to program. Try to not just glance over the examples—read them attentively and understand them. This may be slow and confusing at first, but I promise that you’ll quickly get the hang of it. The same goes for the exercises. Don’t assume you understand them until you’ve actually written a working solution.

+ +

I recommend you try your solutions to exercises in an actual JavaScript interpreter. That way, you’ll get immediate feedback on whether what you are doing is working, and, I hope, you’ll be tempted to experiment and go beyond the exercises.

+ +

When reading this book in your browser, you can edit (and run) all example programs by clicking them.

+ +

If you want to run the programs defined in this book outside of the book’s website, some care will be required. Many examples stand on their own and should work in any JavaScript environment. But code in later chapters is often written for a specific environment (the browser or Node.js) and can run only there. In addition, many chapters define bigger programs, and the pieces of code that appear in them depend on each other or on external files. The sandbox on the website provides links to Zip files containing all the scripts and data files necessary to run the code for a given chapter.

+ +

Overview of this book

+ +

This book contains roughly three parts. The first 12 chapters discuss the JavaScript language. The next seven chapters are about web browsers and the way JavaScript is used to program them. Finally, two chapters are devoted to Node.js, another environment to program JavaScript in.

+ +

Throughout the book, there are five project chapters, which describe larger example programs to give you a taste of actual programming. In order of appearance, we will work through building a delivery robot, a programming language, a platform game, a pixel paint program, and a dynamic website.

+ +

The language part of the book starts with four chapters that introduce the basic structure of the JavaScript language. They introduce control structures (such as the while word you saw in this introduction), functions (writing your own building blocks), and data structures. After these, you will be able to write basic programs. Next, Chapters 5 and 6 introduce techniques to use functions and objects to write more abstract code and keep complexity under control.

+ +

After a first project chapter, the language part of the book continues with chapters on error handling and bug fixing, regular expressions (an important tool for working with text), modularity (another defense against complexity), and asynchronous programming (dealing with events that take time). The second project chapter concludes the first part of the book.

+ +

The second part, Chapters 13 to 19, describes the tools that browser JavaScript has access to. You’ll learn to display things on the screen (Chapters 14 and 17), respond to user input (Chapter 15), and communicate over the network (Chapter 18). There are again two project chapters in this part.

+ +

After that, Chapter 20 describes Node.js, and Chapter 21 builds a small website using that tool.

+ +

Typographic conventions

+ +

In this book, text written in a monospaced font will represent elements of programs—sometimes they are self-sufficient fragments, and sometimes they just refer to part of a nearby program. Programs (of which you have already seen a few) are written as follows:

+ +
function factorial(n) {
+  if (n == 0) {
+    return 1;
+  } else {
+    return factorial(n - 1) * n;
+  }
+}
+ +

Sometimes, to show the output that a program produces, the expected output is written after it, with two slashes and an arrow in front.

+ +
console.log(factorial(8));
+// → 40320
+ +

Good luck!

+
diff --git a/docs/01_values.html b/docs/01_values.html new file mode 100644 index 000000000..ab9ab5d2a --- /dev/null +++ b/docs/01_values.html @@ -0,0 +1,293 @@ + + + + + Values, Types, and Operators :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 1Values, Types, and Operators

+ +
+ +

Below the surface of the machine, the program moves. Without effort, it expands and contracts. In great harmony, electrons scatter and regroup. The forms on the monitor are but ripples on the water. The essence stays invisibly below.

+ +
Master Yuan-Ma, The Book of Programming
+ +
Picture of a sea of bits
+ +

Inside the computer’s world, there is only data. You can read data, modify data, create new data—but that which isn’t data cannot be mentioned. All this data is stored as long sequences of bits and is thus fundamentally alike.

+ +

Bits are any kind of two-valued things, usually described as zeros and ones. Inside the computer, they take forms such as a high or low electrical charge, a strong or weak signal, or a shiny or dull spot on the surface of a CD. Any piece of discrete information can be reduced to a sequence of zeros and ones and thus represented in bits.

+ +

For example, we can express the number 13 in bits. It works the same way as a decimal number, but instead of 10 different digits, you have only 2, and the weight of each increases by a factor of 2 from right to left. Here are the bits that make up the number 13, with the weights of the digits shown below them:

+ +
   0   0   0   0   1   1   0   1
+ 128  64  32  16   8   4   2   1
+ +

So that’s the binary number 00001101. Its non-zero digits stand for 8, 4, and 1, and add up to 13.

+ +

Values

+ +

Imagine a sea of bits—an ocean of them. A typical modern computer has more than 30 billion bits in its volatile data storage (working memory). Nonvolatile storage (the hard disk or equivalent) tends to have yet a few orders of magnitude more.

+ +

To be able to work with such quantities of bits without getting lost, we must separate them into chunks that represent pieces of information. In a JavaScript environment, those chunks are called values. Though all values are made of bits, they play different roles. Every value has a type that determines its role. Some values are numbers, some values are pieces of text, some values are functions, and so on.

+ +

To create a value, you must merely invoke its name. This is convenient. You don’t have to gather building material for your values or pay for them. You just call for one, and whoosh, you have it. They are not really created from thin air, of course. Every value has to be stored somewhere, and if you want to use a gigantic amount of them at the same time, you might run out of memory. Fortunately, this is a problem only if you need them all simultaneously. As soon as you no longer use a value, it will dissipate, leaving behind its bits to be recycled as building material for the next generation of values.

+ +

This chapter introduces the atomic elements of JavaScript programs, that is, the simple value types and the operators that can act on such values.

+ +

Numbers

+ +

Values of the number type are, unsurprisingly, numeric values. In a JavaScript program, they are written as follows:

+ +
13
+ +

Use that in a program, and it will cause the bit pattern for the number 13 to come into existence inside the computer’s memory.

+ +

JavaScript uses a fixed number of bits, 64 of them, to store a single number value. There are only so many patterns you can make with 64 bits, which means that the number of different numbers that can be represented is limited. With N decimal digits, you can represent 10N numbers. Similarly, given 64 binary digits, you can represent 264 different numbers, which is about 18 quintillion (an 18 with 18 zeros after it). That’s a lot.

+ +

Computer memory used to be much smaller, and people tended to use groups of 8 or 16 bits to represent their numbers. It was easy to accidentally overflow such small numbers—to end up with a number that did not fit into the given number of bits. Today, even computers that fit in your pocket have plenty of memory, so you are free to use 64-bit chunks, and you need to worry about overflow only when dealing with truly astronomical numbers.

+ +

Not all whole numbers less than 18 quintillion fit in a JavaScript number, though. Those bits also store negative numbers, so one bit indicates the sign of the number. A bigger issue is that nonwhole numbers must also be represented. To do this, some of the bits are used to store the position of the decimal point. The actual maximum whole number that can be stored is more in the range of 9 quadrillion (15 zeros)—which is still pleasantly huge.

+ +

Fractional numbers are written by using a dot.

+ +
9.81
+ +

For very big or very small numbers, you may also use scientific notation by adding an e (for exponent), followed by the exponent of the number.

+ +
2.998e8
+ +

That is 2.998 × 108 = 299,800,000.

+ +

Calculations with whole numbers (also called integers) smaller than the aforementioned 9 quadrillion are guaranteed to always be precise. Unfortunately, calculations with fractional numbers are generally not. Just as π (pi) cannot be precisely expressed by a finite number of decimal digits, many numbers lose some precision when only 64 bits are available to store them. This is a shame, but it causes practical problems only in specific situations. The important thing is to be aware of it and treat fractional digital numbers as approximations, not as precise values.

+ +

Arithmetic

+ +

The main thing to do with numbers is arithmetic. Arithmetic operations such as addition or multiplication take two number values and produce a new number from them. Here is what they look like in JavaScript:

+ +
100 + 4 * 11
+ +

The + and * symbols are called operators. The first stands for addition, and the second stands for multiplication. Putting an operator between two values will apply it to those values and produce a new value.

+ +

But does the example mean “add 4 and 100, and multiply the result by 11,” or is the multiplication done before the adding? As you might have guessed, the multiplication happens first. But as in mathematics, you can change this by wrapping the addition in parentheses.

+ +
(100 + 4) * 11
+ +

For subtraction, there is the - operator, and division can be done with the / operator.

+ +

When operators appear together without parentheses, the order in which they are applied is determined by the precedence of the operators. The example shows that multiplication comes before addition. The / operator has the same precedence as *. Likewise for + and -. When multiple operators with the same precedence appear next to each other, as in 1 - 2 + 1, they are applied left to right: (1 - 2) + 1.

+ +

These rules of precedence are not something you should worry about. When in doubt, just add parentheses.

+ +

There is one more arithmetic operator, which you might not immediately recognize. The % symbol is used to represent the remainder operation. X % Y is the remainder of dividing X by Y. For example, 314 % 100 produces 14, and 144 % 12 gives 0. The remainder operator’s precedence is the same as that of multiplication and division. You’ll also often see this operator referred to as modulo.

+ +

Special numbers

+ +

There are three special values in JavaScript that are considered numbers but don’t behave like normal numbers.

+ +

The first two are Infinity and -Infinity, which represent the positive and negative infinities. Infinity - 1 is still Infinity, and so on. Don’t put too much trust in infinity-based computation, though. It isn’t mathematically sound, and it will quickly lead to the next special number: NaN.

+ +

NaN stands for “not a number”, even though it is a value of the number type. You’ll get this result when you, for example, try to calculate 0 / 0 (zero divided by zero), Infinity - Infinity, or any number of other numeric operations that don’t yield a meaningful result.

+ +

Strings

+ +

The next basic data type is the string. Strings are used to represent text. They are written by enclosing their content in quotes.

+ +
`Down on the sea`
+"Lie on the ocean"
+'Float on the ocean'
+ +

You can use single quotes, double quotes, or backticks to mark strings, as long as the quotes at the start and the end of the string match.

+ +

Almost anything can be put between quotes, and JavaScript will make a string value out of it. But a few characters are more difficult. You can imagine how putting quotes between quotes might be hard. Newlines (the characters you get when you press enter) can be included without escaping only when the string is quoted with backticks (`).

+ +

To make it possible to include such characters in a string, the following notation is used: whenever a backslash (\) is found inside quoted text, it indicates that the character after it has a special meaning. This is called escaping the character. A quote that is preceded by a backslash will not end the string but be part of it. When an n character occurs after a backslash, it is interpreted as a newline. Similarly, a t after a backslash means a tab character. Take the following string:

+ +
"This is the first line\nAnd this is the second"
+ +

The actual text contained is this:

+ +
This is the first line
+And this is the second
+ +

There are, of course, situations where you want a backslash in a string to be just a backslash, not a special code. If two backslashes follow each other, they will collapse together, and only one will be left in the resulting string value. This is how the string “A newline character is written like "\n".” can be expressed:

+ +
"A newline character is written like \"\\n\"."
+ +

Strings, too, have to be modeled as a series of bits to be able to exist inside the computer. The way JavaScript does this is based on the Unicode standard. This standard assigns a number to virtually every character you would ever need, including characters from Greek, Arabic, Japanese, Armenian, and so on. If we have a number for every character, a string can be described by a sequence of numbers.

+ +

And that’s what JavaScript does. But there’s a complication: JavaScript’s representation uses 16 bits per string element, which can describe up to 216 different characters. But Unicode defines more characters than that—about twice as many, at this point. So some characters, such as many emoji, take up two “character positions” in JavaScript strings. We’ll come back to this in Chapter 5.

+ +

Strings cannot be divided, multiplied, or subtracted, but the + operator can be used on them. It does not add, but it concatenates—it glues two strings together. The following line will produce the string "concatenate":

+ +
"con" + "cat" + "e" + "nate"
+ +

String values have a number of associated functions (methods) that can be used to perform other operations on them. I’ll say more about these in Chapter 4.

+ +

Strings written with single or double quotes behave very much the same—the only difference is in which type of quote you need to escape inside of them. Backtick-quoted strings, usually called template +literals, can do a few more tricks. Apart from being able to span lines, they can also embed other values.

+ +
`half of 100 is ${100 / 2}`
+ +

When you write something inside ${} in a template literal, its result will be computed, converted to a string, and included at that position. The example produces “half of 100 is 50”.

+ +

Unary operators

+ +

Not all operators are symbols. Some are written as words. One example is the typeof operator, which produces a string value naming the type of the value you give it.

+ +
console.log(typeof 4.5)
+// → number
+console.log(typeof "x")
+// → string
+ +

We will use console.log in example code to indicate that we want to see the result of evaluating something. More about that in the next chapter.

+ +

The other operators shown all operated on two values, but typeof takes only one. Operators that use two values are called binary operators, while those that take one are called unary operators. The minus operator can be used both as a binary operator and as a unary operator.

+ +
console.log(- (10 - 2))
+// → -8
+ +

Boolean values

+ +

It is often useful to have a value that distinguishes between only two possibilities, like “yes” and “no” or “on” and “off”. For this purpose, JavaScript has a Boolean type, which has just two values, true and false, which are written as those words.

+ +

Comparison

+ +

Here is one way to produce Boolean values:

+ +
console.log(3 > 2)
+// → true
+console.log(3 < 2)
+// → false
+ +

The > and < signs are the traditional symbols for “is greater than” and “is less than”, respectively. They are binary operators. Applying them results in a Boolean value that indicates whether they hold true in this case.

+ +

Strings can be compared in the same way.

+ +
console.log("Aardvark" < "Zoroaster")
+// → true
+ +

The way strings are ordered is roughly alphabetic but not really what you’d expect to see in a dictionary: uppercase letters are always “less” than lowercase ones, so "Z" < "a", and nonalphabetic characters (!, -, and so on) are also included in the ordering. When comparing strings, JavaScript goes over the characters from left to right, comparing the Unicode codes one by one.

+ +

Other similar operators are >= (greater than or equal to), <= (less than or equal to), == (equal to), and != (not equal to).

+ +
console.log("Itchy" != "Scratchy")
+// → true
+console.log("Apple" == "Orange")
+// → false
+ +

There is only one value in JavaScript that is not equal to itself, and that is NaN (“not a number”).

+ +
console.log(NaN == NaN)
+// → false
+ +

NaN is supposed to denote the result of a nonsensical computation, and as such, it isn’t equal to the result of any other nonsensical computations.

+ +

Logical operators

+ +

There are also some operations that can be applied to Boolean values themselves. JavaScript supports three logical operators: and, or, and not. These can be used to “reason” about Booleans.

+ +

The && operator represents logical and. It is a binary operator, and its result is true only if both the values given to it are true.

+ +
console.log(true && false)
+// → false
+console.log(true && true)
+// → true
+ +

The || operator denotes logical or. It produces true if either of the values given to it is true.

+ +
console.log(false || true)
+// → true
+console.log(false || false)
+// → false
+ +

Not is written as an exclamation mark (!). It is a unary operator that flips the value given to it—!true produces false, and !false gives true.

+ +

When mixing these Boolean operators with arithmetic and other operators, it is not always obvious when parentheses are needed. In practice, you can usually get by with knowing that of the operators we have seen so far, || has the lowest precedence, then comes &&, then the comparison operators (>, ==, and so on), and then the rest. This order has been chosen such that, in typical expressions like the following one, as few parentheses as possible are necessary:

+ +
1 + 1 == 2 && 10 * 10 > 50
+ +

The last logical operator I will discuss is not unary, not binary, but ternary, operating on three values. It is written with a question mark and a colon, like this:

+ +
console.log(true ? 1 : 2);
+// → 1
+console.log(false ? 1 : 2);
+// → 2
+ +

This one is called the conditional operator (or sometimes just the ternary operator since it is the only such operator in the language). The value on the left of the question mark “picks” which of the other two values will come out. When it is true, it chooses the middle value, and when it is false, it chooses the value on the right.

+ +

Empty values

+ +

There are two special values, written null and undefined, that are used to denote the absence of a meaningful value. They are themselves values, but they carry no information.

+ +

Many operations in the language that don’t produce a meaningful value (you’ll see some later) yield undefined simply because they have to yield some value.

+ +

The difference in meaning between undefined and null is an accident of JavaScript’s design, and it doesn’t matter most of the time. In cases where you actually have to concern yourself with these values, I recommend treating them as mostly interchangeable.

+ +

Automatic type conversion

+ +

In the Introduction, I mentioned that JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions:

+ +
console.log(8 * null)
+// → 0
+console.log("5" - 1)
+// → 4
+console.log("5" + 1)
+// → 51
+console.log("five" * 2)
+// → NaN
+console.log(false == 0)
+// → true
+ +

When an operator is applied to the “wrong” type of value, JavaScript will quietly convert that value to the type it needs, using a set of rules that often aren’t what you want or expect. This is called type coercion. The null in the first expression becomes 0, and the "5" in the second expression becomes 5 (from string to number). Yet in the third expression, + tries string concatenation before numeric addition, so the 1 is converted to "1" (from number to string).

+ +

When something that doesn’t map to a number in an obvious way (such as "five" or undefined) is converted to a number, you get the value NaN. Further arithmetic operations on NaN keep producing NaN, so if you find yourself getting one of those in an unexpected place, look for accidental type conversions.

+ +

When comparing values of the same type using ==, the outcome is easy to predict: you should get true when both values are the same, except in the case of NaN. But when the types differ, JavaScript uses a complicated and confusing set of rules to determine what to do. In most cases, it just tries to convert one of the values to the other value’s type. However, when null or undefined occurs on either side of the operator, it produces true only if both sides are one of null or undefined.

+ +
console.log(null == undefined);
+// → true
+console.log(null == 0);
+// → false
+ +

That behavior is often useful. When you want to test whether a value has a real value instead of null or undefined, you can compare it to null with the == (or !=) operator.

+ +

But what if you want to test whether something refers to the precise value false? Expressions like 0 == false and "" == false are also true because of automatic type conversion. When you do not want any type conversions to happen, there are two additional operators: === and !==. The first tests whether a value is precisely equal to the other, and the second tests whether it is not precisely equal. So "" === false is false as expected.

+ +

I recommend using the three-character comparison operators defensively to prevent unexpected type conversions from tripping you up. But when you’re certain the types on both sides will be the same, there is no problem with using the shorter operators.

+ +

Short-circuiting of logical operators

+ +

The logical operators && and || handle values of different types in a peculiar way. They will convert the value on their left side to Boolean type in order to decide what to do, but depending on the operator and the result of that conversion, they will return either the original left-hand value or the right-hand value.

+ +

The || operator, for example, will return the value to its left when that can be converted to true and will return the value on its right otherwise. This has the expected effect when the values are Boolean and does something analogous for values of other types.

+ +
console.log(null || "user")
+// → user
+console.log("Agnes" || "user")
+// → Agnes
+ +

We can use this functionality as a way to fall back on a default value. If you have a value that might be empty, you can put || after it with a replacement value. If the initial value can be converted to false, you’ll get the replacement instead. The rules for converting strings and numbers to Boolean values state that 0, NaN, and the empty string ("") count as false, while all the other values count as true. So 0 || -1 produces -1, and "" || "!?" yields "!?".

+ +

The && operator works similarly but the other way around. When the value to its left is something that converts to false, it returns that value, and otherwise it returns the value on its right.

+ +

Another important property of these two operators is that the part to their right is evaluated only when necessary. In the case of true || X, no matter what X is—even if it’s a piece of program that does something terrible—the result will be true, and X is never evaluated. The same goes for false && X, which is false and will ignore X. This is called short-circuit evaluation.

+ +

The conditional operator works in a similar way. Of the second and third values, only the one that is selected is evaluated.

+ +

Summary

+ +

We looked at four types of JavaScript values in this chapter: numbers, strings, Booleans, and undefined values.

+ +

Such values are created by typing in their name (true, null) or value (13, "abc"). You can combine and transform values with operators. We saw binary operators for arithmetic (+, -, *, /, and %), string concatenation (+), comparison (==, !=, ===, !==, <, >, <=, >=), and logic (&&, ||), as well as several unary operators (- to negate a number, ! to negate logically, and typeof to find a value’s type) and a ternary operator (?:) to pick one of two values based on a third value.

+ +

This gives you enough information to use JavaScript as a pocket calculator but not much more. The next chapter will start tying these expressions together into basic programs.

+
diff --git a/docs/02_program_structure.html b/docs/02_program_structure.html new file mode 100644 index 000000000..9b2bfd233 --- /dev/null +++ b/docs/02_program_structure.html @@ -0,0 +1,515 @@ + + + + + Program Structure :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 2Program Structure

+ +
+ +

And my heart glows bright red under my filmy, translucent skin and they have to administer 10cc of JavaScript to get me to come back. (I respond well to toxins in the blood.) Man, that stuff will kick the peaches right out your gills!

+ +
_why, Why's (Poignant) Guide to Ruby
+ +
Picture of tentacles holding objects
+ +

In this chapter, we will start to do things that can actually be called programming. We will expand our command of the JavaScript language beyond the nouns and sentence fragments we’ve seen so far, to the point where we can express meaningful prose.

+ +

Expressions and statements

+ +

In Chapter 1, we made values and applied operators to them to get new values. Creating values like this is the main substance of any JavaScript program. But that substance has to be framed in a larger structure to be useful. So that’s what we’ll cover next.

+ +

A fragment of code that produces a value is called an expression. Every value that is written literally (such as 22 or "psychoanalysis") is an expression. An expression between parentheses is also an expression, as is a binary operator applied to two expressions or a unary operator applied to one.

+ +

This shows part of the beauty of a language-based interface. Expressions can contain other expressions in a way similar to how subsentences in human languages are nested—a subsentence can contain its own subsentences, and so on. This allows us to build expressions that describe arbitrarily complex computations.

+ +

If an expression corresponds to a sentence fragment, a JavaScript statement corresponds to a full sentence. A program is a list of statements.

+ +

The simplest kind of statement is an expression with a semicolon after it. This is a program:

+ +
1;
+!false;
+ +

It is a useless program, though. An expression can be content to just produce a value, which can then be used by the enclosing code. A statement stands on its own, so it amounts to something only if it affects the world. It could display something on the screen—that counts as changing the world—or it could change the internal state of the machine in a way that will affect the statements that come after it. These changes are called side effects. The statements in the previous example just produce the values 1 and true and then immediately throw them away. This leaves no impression on the world at all. When you run this program, nothing observable happens.

+ +

In some cases, JavaScript allows you to omit the semicolon at the end of a statement. In other cases, it has to be there, or the next line will be treated as part of the same statement. The rules for when it can be safely omitted are somewhat complex and error-prone. So in this book, every statement that needs a semicolon will always get one. I recommend you do the same, at least until you’ve learned more about the subtleties of missing semicolons.

+ +

Bindings

+ +

How does a program keep an internal state? How does it remember things? We have seen how to produce new values from old values, but this does not change the old values, and the new value has to be immediately used or it will dissipate again. To catch and hold values, JavaScript provides a thing called a binding, or variable:

+ +
let caught = 5 * 5;
+ +

That’s a second kind of statement. The special word (keyword) let indicates that this sentence is going to define a binding. It is followed by the name of the binding and, if we want to immediately give it a value, by an = operator and an expression.

+ +

The previous statement creates a binding called caught and uses it to grab hold of the number that is produced by multiplying 5 by 5.

+ +

After a binding has been defined, its name can be used as an expression. The value of such an expression is the value the binding currently holds. Here’s an example:

+ +
let ten = 10;
+console.log(ten * ten);
+// → 100
+ +

When a binding points at a value, that does not mean it is tied to that value forever. The = operator can be used at any time on existing bindings to disconnect them from their current value and have them point to a new one.

+ +
let mood = "light";
+console.log(mood);
+// → light
+mood = "dark";
+console.log(mood);
+// → dark
+ +

You should imagine bindings as tentacles, rather than boxes. They do not contain values; they grasp them—two bindings can refer to the same value. A program can access only the values that it still has a reference to. When you need to remember something, you grow a tentacle to hold on to it or you reattach one of your existing tentacles to it.

+ +

Let’s look at another example. To remember the number of dollars that Luigi still owes you, you create a binding. And then when he pays back $35, you give this binding a new value.

+ +
let luigisDebt = 140;
+luigisDebt = luigisDebt - 35;
+console.log(luigisDebt);
+// → 105
+ +

When you define a binding without giving it a value, the tentacle has nothing to grasp, so it ends in thin air. If you ask for the value of an empty binding, you’ll get the value undefined.

+ +

A single let statement may define multiple bindings. The definitions must be separated by commas.

+ +
let one = 1, two = 2;
+console.log(one + two);
+// → 3
+ +

The words var and const can also be used to create bindings, in a way similar to let.

+ +
var name = "Ayda";
+const greeting = "Hello ";
+console.log(greeting + name);
+// → Hello Ayda
+ +

The first, var (short for “variable”), is the way bindings were declared in pre-2015 JavaScript. I’ll get back to the precise way it differs from let in the next chapter. For now, remember that it mostly does the same thing, but we’ll rarely use it in this book because it has some confusing properties.

+ +

The word const stands for constant. It defines a constant binding, which points at the same value for as long as it lives. This is useful for bindings that give a name to a value so that you can easily refer to it later.

+ +

Binding names

+ +

Binding names can be any word. Digits can be part of binding names—catch22 is a valid name, for example—but the name must not start with a digit. A binding name may include dollar signs ($) or underscores (_) but no other punctuation or special characters.

+ +

Words with a special meaning, such as let, are keywords, and they may not be used as binding names. There are also a number of words that are “reserved for use” in future versions of JavaScript, which also can’t be used as binding names. The full list of keywords and reserved words is rather long.

+ +
break case catch class const continue debugger default
+delete do else enum export extends false finally for
+function if implements import interface in instanceof let
+new package private protected public return static super
+switch this throw true try typeof var void while with yield
+ +

Don’t worry about memorizing this list. When creating a binding produces an unexpected syntax error, see whether you’re trying to define a reserved word.

+ +

The environment

+ +

The collection of bindings and their values that exist at a given time is called the environment. When a program starts up, this environment is not empty. It always contains bindings that are part of the language standard, and most of the time, it also has bindings that provide ways to interact with the surrounding system. For example, in a browser, there are functions to interact with the currently loaded website and to read mouse and keyboard input.

+ +

Functions

+ +

A lot of the values provided in the default environment have the type function. A function is a piece of program wrapped in a value. Such values can be applied in order to run the wrapped program. For example, in a browser environment, the binding prompt holds a function that shows a little dialog box asking for user input. It is used like this:

+ +
prompt("Enter passcode");
A prompt dialog
+ +

Executing a function is called invoking, calling, or applying it. You can call a function by putting parentheses after an expression that produces a function value. Usually you’ll directly use the name of the binding that holds the function. The values between the parentheses are given to the program inside the function. In the example, the prompt function uses the string that we give it as the text to show in the dialog box. Values given to functions are called arguments. Different functions might need a different number or different types of arguments.

+ +

The prompt function isn’t used much in modern web programming, mostly because you have no control over the way the resulting dialog looks, but can be helpful in toy programs and experiments.

+ +

The console.log function

+ +

In the examples, I used console.log to output values. Most JavaScript systems (including all modern web browsers and Node.js) provide a console.log function that writes out its arguments to some text output device. In browsers, the output lands in the JavaScript +console. This part of the browser interface is hidden by default, but most browsers open it when you press F12 or, on a Mac, command-option-I. If that does not work, search through the menus for an item named Developer Tools or similar.

+ +

When running the examples (or your own code) on the pages of this book, console.log output will be shown after the example, instead of in the browser’s JavaScript console.

+ +
let x = 30;
+console.log("the value of x is", x);
+// → the value of x is 30
+ +

Though binding names cannot contain period characters, console.log does have one. This is because console.log isn’t a simple binding. It is actually an expression that retrieves the log property from the value held by the console binding. We’ll find out exactly what this means in Chapter 4.

+ +

Return values

+ +

Showing a dialog box or writing text to the screen is a side +effect. A lot of functions are useful because of the side effects they produce. Functions may also produce values, in which case they don’t need to have a side effect to be useful. For example, the function Math.max takes any amount of number arguments and gives back the greatest.

+ +
console.log(Math.max(2, 4));
+// → 4
+ +

When a function produces a value, it is said to return that value. Anything that produces a value is an expression in JavaScript, which means function calls can be used within larger expressions. Here a call to Math.min, which is the opposite of Math.max, is used as part of a plus expression:

+ +
console.log(Math.min(2, 4) + 100);
+// → 102
+ +

The next chapter explains how to write your own functions.

+ +

Control flow

+ +

When your program contains more than one statement, the statements are executed as if they are a story, from top to bottom. This example program has two statements. The first one asks the user for a number, and the second, which is executed after the first, shows the square of that number.

+ +
let theNumber = Number(prompt("Pick a number"));
+console.log("Your number is the square root of " +
+            theNumber * theNumber);
+ +

The function Number converts a value to a number. We need that conversion because the result of prompt is a string value, and we want a number. There are similar functions called String and Boolean that convert values to those types.

+ +

Here is the rather trivial schematic representation of straight-line control flow:

Trivial control flow
+ +

Conditional execution

+ +

Not all programs are straight roads. We may, for example, want to create a branching road, where the program takes the proper branch based on the situation at hand. This is called conditional +execution.

Conditional control flow
+ +

Conditional execution is created with the if keyword in JavaScript. In the simple case, we want some code to be executed if, and only if, a certain condition holds. We might, for example, want to show the square of the input only if the input is actually a number.

+ +
let theNumber = Number(prompt("Pick a number"));
+if (!Number.isNaN(theNumber)) {
+  console.log("Your number is the square root of " +
+              theNumber * theNumber);
+}
+ +

With this modification, if you enter “parrot”, no output is shown.

+ +

The if keyword executes or skips a statement depending on the value of a Boolean expression. The deciding expression is written after the keyword, between parentheses, followed by the statement to execute.

+ +

The Number.isNaN function is a standard JavaScript function that returns true only if the argument it is given is NaN. The Number function happens to return NaN when you give it a string that doesn’t represent a valid number. Thus, the condition translates to “unless theNumber is not-a-number, do this”.

+ +

The statement after the if is wrapped in braces ({ and }) in this example. The braces can be used to group any number of statements into a single statement, called a block. You could also have omitted them in this case, since they hold only a single statement, but to avoid having to think about whether they are needed, most JavaScript programmers use them in every wrapped statement like this. We’ll mostly follow that convention in this book, except for the occasional one-liner.

+ +
if (1 + 1 == 2) console.log("It's true");
+// → It's true
+ +

You often won’t just have code that executes when a condition holds true, but also code that handles the other case. This alternate path is represented by the second arrow in the diagram. You can use the else keyword, together with if, to create two separate, alternative execution paths.

+ +
let theNumber = Number(prompt("Pick a number"));
+if (!Number.isNaN(theNumber)) {
+  console.log("Your number is the square root of " +
+              theNumber * theNumber);
+} else {
+  console.log("Hey. Why didn't you give me a number?");
+}
+ +

If you have more than two paths to choose from, you can “chain” multiple if/else pairs together. Here’s an example:

+ +
let num = Number(prompt("Pick a number"));
+
+if (num < 10) {
+  console.log("Small");
+} else if (num < 100) {
+  console.log("Medium");
+} else {
+  console.log("Large");
+}
+ +

The program will first check whether num is less than 10. If it is, it chooses that branch, shows "Small", and is done. If it isn’t, it takes the else branch, which itself contains a second if. If the second condition (< 100) holds, that means the number is between 10 and 100, and "Medium" is shown. If it doesn’t, the second and last else branch is chosen.

+ +

The schema for this program looks something like this:

Nested if control flow
+ +

while and do loops

+ +

Consider a program that outputs all even numbers from 0 to 12. One way to write this is as follows:

+ +
console.log(0);
+console.log(2);
+console.log(4);
+console.log(6);
+console.log(8);
+console.log(10);
+console.log(12);
+ +

That works, but the idea of writing a program is to make something less work, not more. If we needed all even numbers less than 1,000, this approach would be unworkable. What we need is a way to run a piece of code multiple times. This form of control flow is called a loop.

Loop control flow
+ +

Looping control flow allows us to go back to some point in the program where we were before and repeat it with our current program state. If we combine this with a binding that counts, we can do something like this:

+ +
let number = 0;
+while (number <= 12) {
+  console.log(number);
+  number = number + 2;
+}
+// → 0
+// → 2
+//   … etcetera
+ +

A statement starting with the keyword while creates a loop. The word while is followed by an expression in parentheses and then a statement, much like if. The loop keeps entering that statement as long as the expression produces a value that gives true when converted to Boolean.

+ +

The number binding demonstrates the way a binding can track the progress of a program. Every time the loop repeats, number gets a value that is 2 more than its previous value. At the beginning of every repetition, it is compared with the number 12 to decide whether the program’s work is finished.

+ +

As an example that actually does something useful, we can now write a program that calculates and shows the value of 210 (2 to the 10th power). We use two bindings: one to keep track of our result and one to count how often we have multiplied this result by 2. The loop tests whether the second binding has reached 10 yet and, if not, updates both bindings.

+ +
let result = 1;
+let counter = 0;
+while (counter < 10) {
+  result = result * 2;
+  counter = counter + 1;
+}
+console.log(result);
+// → 1024
+ +

The counter could also have started at 1 and checked for <= 10, but for reasons that will become apparent in Chapter 4, it is a good idea to get used to counting from 0.

+ +

A do loop is a control structure similar to a while loop. It differs only on one point: a do loop always executes its body at least once, and it starts testing whether it should stop only after that first execution. To reflect this, the test appears after the body of the loop.

+ +
let yourName;
+do {
+  yourName = prompt("Who are you?");
+} while (!yourName);
+console.log(yourName);
+ +

This program will force you to enter a name. It will ask again and again until it gets something that is not an empty string. Applying the ! operator will convert a value to Boolean type before negating it, and all strings except "" convert to true. This means the loop continues going round until you provide a non-empty name.

+ +

Indenting Code

+ +

In the examples, I’ve been adding spaces in front of statements that are part of some larger statement. These spaces are not required—the computer will accept the program just fine without them. In fact, even the line breaks in programs are optional. You could write a program as a single long line if you felt like it.

+ +

The role of this indentation inside blocks is to make the structure of the code stand out. In code where new blocks are opened inside other blocks, it can become hard to see where one block ends and another begins. With proper indentation, the visual shape of a program corresponds to the shape of the blocks inside it. I like to use two spaces for every open block, but tastes differ—some people use four spaces, and some people use tab characters. The important thing is that each new block adds the same amount of space.

+ +
if (false != true) {
+  console.log("That makes sense.");
+  if (1 < 2) {
+    console.log("No surprise there.");
+  }
+}
+ +

Most code editor programs (including the one in this book) will help by automatically indenting new lines the proper amount.

+ +

for loops

+ +

Many loops follow the pattern shown in the while examples. First a “counter” binding is created to track the progress of the loop. Then comes a while loop, usually with a test expression that checks whether the counter has reached its end value. At the end of the loop body, the counter is updated to track progress.

+ +

Because this pattern is so common, JavaScript and similar languages provide a slightly shorter and more comprehensive form, the for loop.

+ +
for (let number = 0; number <= 12; number = number + 2) {
+  console.log(number);
+}
+// → 0
+// → 2
+//   … etcetera
+ +

This program is exactly equivalent to the earlier even-number-printing example. The only change is that all the statements that are related to the “state” of the loop are grouped together after for.

+ +

The parentheses after a for keyword must contain two semicolons. The part before the first semicolon initializes the loop, usually by defining a binding. The second part is the expression that checks whether the loop must continue. The final part updates the state of the loop after every iteration. In most cases, this is shorter and clearer than a while construct.

+ +

This is the code that computes 210 using for instead of while:

+ +
let result = 1;
+for (let counter = 0; counter < 10; counter = counter + 1) {
+  result = result * 2;
+}
+console.log(result);
+// → 1024
+ +

Breaking Out of a Loop

+ +

Having the looping condition produce false is not the only way a loop can finish. There is a special statement called break that has the effect of immediately jumping out of the enclosing loop.

+ +

This program illustrates the break statement. It finds the first number that is both greater than or equal to 20 and divisible by 7.

+ +
for (let current = 20; ; current = current + 1) {
+  if (current % 7 == 0) {
+    console.log(current);
+    break;
+  }
+}
+// → 21
+ +

Using the remainder (%) operator is an easy way to test whether a number is divisible by another number. If it is, the remainder of their division is zero.

+ +

The for construct in the example does not have a part that checks for the end of the loop. This means that the loop will never stop unless the break statement inside is executed.

+ +

If you were to remove that break statement or you accidentally write an end condition that always produces true, your program would get stuck in an infinite loop. A program stuck in an infinite loop will never finish running, which is usually a bad thing.

+ +

If you create an infinite loop in one of the examples on these pages, you’ll usually be asked whether you want to stop the script after a few seconds. If that fails, you will have to close the tab that you’re working in, or on some browsers close your whole browser, to recover.

+ +

The continue keyword is similar to break, in that it influences the progress of a loop. When continue is encountered in a loop body, control jumps out of the body and continues with the loop’s next iteration.

+ +

Updating bindings succinctly

+ +

Especially when looping, a program often needs to “update” a binding to hold a value based on that binding’s previous value.

+ +
counter = counter + 1;
+ +

JavaScript provides a shortcut for this.

+ +
counter += 1;
+ +

Similar shortcuts work for many other operators, such as result *= 2 to double result or counter -= 1 to count downward.

+ +

This allows us to shorten our counting example a little more.

+ +
for (let number = 0; number <= 12; number += 2) {
+  console.log(number);
+}
+ +

For counter += 1 and counter -= 1, there are even shorter equivalents: counter++ and counter--.

+ +

Dispatching on a value with switch

+ +

It is not uncommon for code to look like this:

+ +
if (x == "value1") action1();
+else if (x == "value2") action2();
+else if (x == "value3") action3();
+else defaultAction();
+ +

There is a construct called switch that is intended to express such a “dispatch” in a more direct way. Unfortunately, the syntax JavaScript uses for this (which it inherited from the C/Java line of programming languages) is somewhat awkward—a chain of if statements may look better. Here is an example:

+ +
switch (prompt("What is the weather like?")) {
+  case "rainy":
+    console.log("Remember to bring an umbrella.");
+    break;
+  case "sunny":
+    console.log("Dress lightly.");
+  case "cloudy":
+    console.log("Go outside.");
+    break;
+  default:
+    console.log("Unknown weather type!");
+    break;
+}
+ +

You may put any number of case labels inside the block opened by switch. The program will start executing at the label that corresponds to the value that switch was given, or at default if no matching value is found. It will continue executing, even across other labels, until it reaches a break statement. In some cases, such as the "sunny" case in the example, this can be used to share some code between cases (it recommends going outside for both sunny and cloudy weather). But be careful—it is easy to forget such a break, which will cause the program to execute code you do not want executed.

+ +

Capitalization

+ +

Binding names may not contain spaces, yet it is often helpful to use multiple words to clearly describe what the binding represents. These are pretty much your choices for writing a binding name with several words in it:

+ +
fuzzylittleturtle
+fuzzy_little_turtle
+FuzzyLittleTurtle
+fuzzyLittleTurtle
+ +

The first style can be hard to read. I rather like the look of the underscores, though that style is a little painful to type. The standard JavaScript functions, and most JavaScript programmers, follow the bottom style—they capitalize every word except the first. It is not hard to get used to little things like that, and code with mixed naming styles can be jarring to read, so we follow this convention.

+ +

In a few cases, such as the Number function, the first letter of a binding is also capitalized. This was done to mark this function as a constructor. What a constructor is will become clear in Chapter 6. For now, the important thing is not to be bothered by this apparent lack of consistency.

+ +

Comments

+ +

Often, raw code does not convey all the information you want a program to convey to human readers, or it conveys it in such a cryptic way that people might not understand it. At other times, you might just want to include some related thoughts as part of your program. This is what comments are for.

+ +

A comment is a piece of text that is part of a program but is completely ignored by the computer. JavaScript has two ways of writing comments. To write a single-line comment, you can use two slash characters (//) and then the comment text after it.

+ +
let accountBalance = calculateBalance(account);
+// It's a green hollow where a river sings
+accountBalance.adjust();
+// Madly catching white tatters in the grass.
+let report = new Report();
+// Where the sun on the proud mountain rings:
+addToReport(accountBalance, report);
+// It's a little valley, foaming like light in a glass.
+ +

A // comment goes only to the end of the line. A section of text between /* and */ will be ignored in its entirety, regardless of whether it contains line breaks. This is useful for adding blocks of information about a file or a chunk of program.

+ +
/*
+  I first found this number scrawled on the back of an old notebook.
+  Since then, it has often dropped by, showing up in phone numbers
+  and the serial numbers of products that I've bought. It obviously
+  likes me, so I've decided to keep it.
+*/
+const myNumber = 11213;
+ +

Summary

+ +

You now know that a program is built out of statements, which themselves sometimes contain more statements. Statements tend to contain expressions, which themselves can be built out of smaller expressions.

+ +

Putting statements after one another gives you a program that is executed from top to bottom. You can introduce disturbances in the flow of control by using conditional (if, else, and switch) and looping (while, do, and for) statements.

+ +

Bindings can be used to file pieces of data under a name, and they are useful for tracking state in your program. The environment is the set of bindings that are defined. JavaScript systems always put a number of useful standard bindings into your environment.

+ +

Functions are special values that encapsulate a piece of program. You can invoke them by writing functionName(argument1, argument2). Such a function call is an expression and may produce a value.

+ +

Exercises

+ +

If you are unsure how to test your solutions to the exercises, refer to the Introduction.

+ +

Each exercise starts with a problem description. Read this description and try to solve the exercise. If you run into problems, consider reading the hints after the exercise. Full solutions to the exercises are not included in this book, but you can find them online at https://eloquentjavascript.net/code. If you want to learn something from the exercises, I recommend looking at the solutions only after you’ve solved the exercise, or at least after you’ve attacked it long and hard enough to have a slight headache.

+ +

Looping a triangle

+ +

Write a loop that makes seven calls to console.log to output the following triangle:

+ +
#
+##
+###
+####
+#####
+######
+#######
+ +

It may be useful to know that you can find the length of a string by writing .length after it.

+ +
let abc = "abc";
+console.log(abc.length);
+// → 3
+ +

Most exercises contain a piece of code that you can modify to solve the exercise. Remember that you can click code blocks to edit them.

+ +
// Your code here.
+ +
+ +

You can start with a program that prints out the numbers 1 to 7, which you can derive by making a few modifications to the even number printing example given earlier in the chapter, where the for loop was introduced.

+ +

Now consider the equivalence between numbers and strings of hash characters. You can go from 1 to 2 by adding 1 (+= 1). You can go from "#" to "##" by adding a character (+= "#"). Thus, your solution can closely follow the number-printing program.

+ +
+ +

FizzBuzz

+ +

Write a program that uses console.log to print all the numbers from 1 to 100, with two exceptions. For numbers divisible by 3, print "Fizz" instead of the number, and for numbers divisible by 5 (and not 3), print "Buzz" instead.

+ +

When you have that working, modify your program to print "FizzBuzz" for numbers that are divisible by both 3 and 5 (and still print "Fizz" or "Buzz" for numbers divisible by only one of those).

+ +

(This is actually an interview question that has been claimed to weed out a significant percentage of programmer candidates. So if you solved it, your labor market value just went up.)

+ +
// Your code here.
+ +
+ +

Going over the numbers is clearly a looping job, and selecting what to print is a matter of conditional execution. Remember the trick of using the remainder (%) operator for checking whether a number is divisible by another number (has a remainder of zero).

+ +

In the first version, there are three possible outcomes for every number, so you’ll have to create an if/else if/else chain.

+ +

The second version of the program has a straightforward solution and a clever one. The simple solution is to add another conditional “branch” to precisely test the given condition. For the clever solution, build up a string containing the word or words to output and print either this word or the number if there is no word, potentially by making good use of the || operator.

+ +
+ +

Chessboard

+ +

Write a program that creates a string that represents an 8×8 grid, using newline characters to separate lines. At each position of the grid there is either a space or a "#" character. The characters should form a chessboard.

+ +

Passing this string to console.log should show something like this:

+ +
 # # # #
+# # # # 
+ # # # #
+# # # # 
+ # # # #
+# # # # 
+ # # # #
+# # # #
+ +

When you have a program that generates this pattern, define a binding size = 8 and change the program so that it works for any size, outputting a grid of the given width and height.

+ +
// Your code here.
+ +
+ +

You can build the string by starting with an empty one ("") and repeatedly adding characters. A newline character is written "\n".

+ +

To work with two dimensions, you will need a loop inside of a loop. Put braces around the bodies of both loops to make it easy to see where they start and end. Try to properly indent these bodies. The order of the loops must follow the order in which we build up the string (line by line, left to right, top to bottom). So the outer loop handles the lines, and the inner loop handles the characters on a line.

+ +

You’ll need two bindings to track your progress. To know whether to put a space or a hash sign at a given position, you could test whether the sum of the two counters is even (% 2).

+ +

Terminating a line by adding a newline character must happen after the line has been built up, so do this after the inner loop but inside the outer loop.

+ +
+
diff --git a/docs/03_functions.html b/docs/03_functions.html new file mode 100644 index 000000000..405fdfed2 --- /dev/null +++ b/docs/03_functions.html @@ -0,0 +1,581 @@ + + + + + Functions :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 3Functions

+ +
+ +

People think that computer science is the art of geniuses but the actual reality is the opposite, just many people doing things that build on each other, like a wall of mini stones.

+ +
Donald Knuth
+ +
Picture of fern leaves with a fractal shape
+ +

Functions are the bread and butter of JavaScript programming. The concept of wrapping a piece of program in a value has many uses. It gives us a way to structure larger programs, to reduce repetition, to associate names with subprograms, and to isolate these subprograms from each other.

+ +

The most obvious application of functions is defining new vocabulary. Creating new words in prose is usually bad style. But in programming, it is indispensable.

+ +

Typical adult English speakers have some 20,000 words in their vocabulary. Few programming languages come with 20,000 commands built in. And the vocabulary that is available tends to be more precisely defined, and thus less flexible, than in human language. Therefore, we usually have to introduce new concepts to avoid repeating ourselves too much.

+ +

Defining a function

+ +

A function definition is a regular binding where the value of the binding is a function. For example, this code defines square to refer to a function that produces the square of a given number:

+ +
const square = function(x) {
+  return x * x;
+};
+
+console.log(square(12));
+// → 144
+ +

A function is created with an expression that starts with the keyword function. Functions have a set of parameters (in this case, only x) and a body, which contains the statements that are to be executed when the function is called. The function body of a function created this way must always be wrapped in braces, even when it consists of only a single statement.

+ +

A function can have multiple parameters or no parameters at all. In the following example, makeNoise does not list any parameter names, whereas power lists two:

+ +
const makeNoise = function() {
+  console.log("Pling!");
+};
+
+makeNoise();
+// → Pling!
+
+const power = function(base, exponent) {
+  let result = 1;
+  for (let count = 0; count < exponent; count++) {
+    result *= base;
+  }
+  return result;
+};
+
+console.log(power(2, 10));
+// → 1024
+ +

Some functions produce a value, such as power and square, and some don’t, such as makeNoise, whose only result is a side effect. A return statement determines the value the function returns. When control comes across such a statement, it immediately jumps out of the current function and gives the returned value to the code that called the function. A return keyword without an expression after it will cause the function to return undefined. Functions that don’t have a return statement at all, such as makeNoise, similarly return undefined.

+ +

Parameters to a function behave like regular bindings, but their initial values are given by the caller of the function, not the code in the function itself.

+ +

Bindings and scopes

+ +

Each binding has a scope, which is the part of the program in which the binding is visible. For bindings defined outside of any function or block, the scope is the whole program—you can refer to such bindings wherever you want. These are called global.

+ +

But bindings created for function parameters or declared inside a function can be referenced only in that function, so they are known as local bindings. Every time the function is called, new instances of these bindings are created. This provides some isolation between functions—each function call acts in its own little world (its local environment) and can often be understood without knowing a lot about what’s going on in the global environment.

+ +

Bindings declared with let and const are in fact local to the block that they are declared in, so if you create one of those inside of a loop, the code before and after the loop cannot “see” it. In pre-2015 JavaScript, only functions created new scopes, so old-style bindings, created with the var keyword, are visible throughout the whole function that they appear in—or throughout the global scope, if they are not in a function.

+ +
let x = 10;
+if (true) {
+  let y = 20;
+  var z = 30;
+  console.log(x + y + z);
+  // → 60
+}
+// y is not visible here
+console.log(x + z);
+// → 40
+ +

Each scope can “look out” into the scope around it, so x is visible inside the block in the example. The exception is when multiple bindings have the same name—in that case, code can see only the innermost one. For example, when the code inside the halve function refers to n, it is seeing its own n, not the global n.

+ +
const halve = function(n) {
+  return n / 2;
+};
+
+let n = 10;
+console.log(halve(100));
+// → 50
+console.log(n);
+// → 10
+ +

Nested scope

+ +

JavaScript distinguishes not just global and local bindings. Blocks and functions can be created inside other blocks and functions, producing multiple degrees of locality.

+ +

For example, this function—which outputs the ingredients needed to make a batch of hummus—has another function inside it:

+ +
const hummus = function(factor) {
+  const ingredient = function(amount, unit, name) {
+    let ingredientAmount = amount * factor;
+    if (ingredientAmount > 1) {
+      unit += "s";
+    }
+    console.log(`${ingredientAmount} ${unit} ${name}`);
+  };
+  ingredient(1, "can", "chickpeas");
+  ingredient(0.25, "cup", "tahini");
+  ingredient(0.25, "cup", "lemon juice");
+  ingredient(1, "clove", "garlic");
+  ingredient(2, "tablespoon", "olive oil");
+  ingredient(0.5, "teaspoon", "cumin");
+};
+ +

The code inside the ingredient function can see the factor binding from the outer function. But its local bindings, such as unit or ingredientAmount, are not visible in the outer function.

+ +

The set of bindings visible inside a block is determined by the place of that block in the program text. Each local scope can also see all the local scopes that contain it, and all scopes can see the global scope. This approach to binding visibility is called lexical scoping.

+ +

Functions as values

+ +

A function binding usually simply acts as a name for a specific piece of the program. Such a binding is defined once and never changed. This makes it easy to confuse the function and its name.

+ +

But the two are different. A function value can do all the things that other values can do—you can use it in arbitrary expressions, not just call it. It is possible to store a function value in a new binding, pass it as an argument to a function, and so on. Similarly, a binding that holds a function is still just a regular binding and can, if not constant, be assigned a new value, like so:

+ +
let launchMissiles = function() {
+  missileSystem.launch("now");
+};
+if (safeMode) {
+  launchMissiles = function() {/* do nothing */};
+}
+ +

In Chapter 5, we will discuss the interesting things that can be done by passing around function values to other functions.

+ +

Declaration notation

+ +

There is a slightly shorter way to create a function binding. When the function keyword is used at the start of a statement, it works differently.

+ +
function square(x) {
+  return x * x;
+}
+ +

This is a function declaration. The statement defines the binding square and points it at the given function. It is slightly easier to write and doesn’t require a semicolon after the function.

+ +

There is one subtlety with this form of function definition.

+ +
console.log("The future says:", future());
+
+function future() {
+  return "You'll never have flying cars";
+}
+ +

The preceding code works, even though the function is defined below the code that uses it. Function declarations are not part of the regular top-to-bottom flow of control. They are conceptually moved to the top of their scope and can be used by all the code in that scope. This is sometimes useful because it offers the freedom to order code in a way that seems meaningful, without worrying about having to define all functions before they are used.

+ +

Arrow functions

+ +

There’s a third notation for functions, which looks very different from the others. Instead of the function keyword, it uses an arrow (=>) made up of an equal sign and a greater-than character (not to be confused with the greater-than-or-equal operator, which is written >=).

+ +
const power = (base, exponent) => {
+  let result = 1;
+  for (let count = 0; count < exponent; count++) {
+    result *= base;
+  }
+  return result;
+};
+ +

The arrow comes after the list of parameters and is followed by the function’s body. It expresses something like “this input (the parameters) produces this result (the body)”.

+ +

When there is only one parameter name, you can omit the parentheses around the parameter list. If the body is a single expression, rather than a block in braces, that expression will be returned from the function. So, these two definitions of square do the same thing:

+ +
const square1 = (x) => { return x * x; };
+const square2 = x => x * x;
+ +

When an arrow function has no parameters at all, its parameter list is just an empty set of parentheses.

+ +
const horn = () => {
+  console.log("Toot");
+};
+ +

There’s no deep reason to have both arrow functions and function expressions in the language. Apart from a minor detail, which we’ll discuss in Chapter 6, they do the same thing. Arrow functions were added in 2015, mostly to make it possible to write small function expressions in a less verbose way. We’ll be using them a lot in Chapter 5.

+ +

The call stack

+ +

The way control flows through functions is somewhat involved. Let’s take a closer look at it. Here is a simple program that makes a few function calls:

+ +
function greet(who) {
+  console.log("Hello " + who);
+}
+greet("Harry");
+console.log("Bye");
+ +

A run through this program goes roughly like this: the call to greet causes control to jump to the start of that function (line 2). The function calls console.log, which takes control, does its job, and then returns control to line 2. There it reaches the end of the greet function, so it returns to the place that called it, which is line 4. The line after that calls console.log again. After that returns, the program reaches its end.

+ +

We could show the flow of control schematically like this:

+ +
not in function
+   in greet
+        in console.log
+   in greet
+not in function
+   in console.log
+not in function
+ +

Because a function has to jump back to the place that called it when it returns, the computer must remember the context from which the call happened. In one case, console.log has to return to the greet function when it is done. In the other case, it returns to the end of the program.

+ +

The place where the computer stores this context is the call +stack. Every time a function is called, the current context is stored on top of this stack. When a function returns, it removes the top context from the stack and uses that context to continue execution.

+ +

Storing this stack requires space in the computer’s memory. When the stack grows too big, the computer will fail with a message like “out of stack space” or “too much recursion”. The following code illustrates this by asking the computer a really hard question that causes an infinite back-and-forth between two functions. Rather, it would be infinite, if the computer had an infinite stack. As it is, we will run out of space, or “blow the stack”.

+ +
function chicken() {
+  return egg();
+}
+function egg() {
+  return chicken();
+}
+console.log(chicken() + " came first.");
+// → ??
+ +

Optional Arguments

+ +

The following code is allowed and executes without any problem:

+ +
function square(x) { return x * x; }
+console.log(square(4, true, "hedgehog"));
+// → 16
+ +

We defined square with only one parameter. Yet when we call it with three, the language doesn’t complain. It ignores the extra arguments and computes the square of the first one.

+ +

JavaScript is extremely broad-minded about the number of arguments you pass to a function. If you pass too many, the extra ones are ignored. If you pass too few, the missing parameters get assigned the value undefined.

+ +

The downside of this is that it is possible—likely, even—that you’ll accidentally pass the wrong number of arguments to functions. And no one will tell you about it.

+ +

The upside is that this behavior can be used to allow a function to be called with different numbers of arguments. For example, this minus function tries to imitate the - operator by acting on either one or two arguments:

+ +
function minus(a, b) {
+  if (b === undefined) return -a;
+  else return a - b;
+}
+
+console.log(minus(10));
+// → -10
+console.log(minus(10, 5));
+// → 5
+ +

If you write an = operator after a parameter, followed by an expression, the value of that expression will replace the argument when it is not given.

+ +

For example, this version of power makes its second argument optional. If you don’t provide it or pass the value undefined, it will default to two, and the function will behave like square.

+ +
function power(base, exponent = 2) {
+  let result = 1;
+  for (let count = 0; count < exponent; count++) {
+    result *= base;
+  }
+  return result;
+}
+
+console.log(power(4));
+// → 16
+console.log(power(2, 6));
+// → 64
+ +

In the next chapter, we will see a way in which a function body can get at the whole list of arguments it was passed. This is helpful because it makes it possible for a function to accept any number of arguments. For example, console.log does this—it outputs all of the values it is given.

+ +
console.log("C", "O", 2);
+// → C O 2
+ +

Closure

+ +

The ability to treat functions as values, combined with the fact that local bindings are re-created every time a function is called, brings up an interesting question. What happens to local bindings when the function call that created them is no longer active?

+ +

The following code shows an example of this. It defines a function, wrapValue, that creates a local binding. It then returns a function that accesses and returns this local binding.

+ +
function wrapValue(n) {
+  let local = n;
+  return () => local;
+}
+
+let wrap1 = wrapValue(1);
+let wrap2 = wrapValue(2);
+console.log(wrap1());
+// → 1
+console.log(wrap2());
+// → 2
+ +

This is allowed and works as you’d hope—both instances of the binding can still be accessed. This situation is a good demonstration of the fact that local bindings are created anew for every call, and different calls can’t trample on one another’s local bindings.

+ +

This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called closure. A function that references bindings from local scopes around it is called a closure. This behavior not only frees you from having to worry about lifetimes of bindings but also makes it possible to use function values in some creative ways.

+ +

With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount.

+ +
function multiplier(factor) {
+  return number => number * factor;
+}
+
+let twice = multiplier(2);
+console.log(twice(5));
+// → 10
+ +

The explicit local binding from the wrapValue example isn’t really needed since a parameter is itself a local binding.

+ +

Thinking about programs like this takes some practice. A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees the environment in which it was created, not the environment in which it is called.

+ +

In the example, multiplier is called and creates an environment in which its factor parameter is bound to 2. The function value it returns, which is stored in twice, remembers this environment. So when that is called, it multiplies its argument by 2.

+ +

Recursion

+ +

It is perfectly okay for a function to call itself, as long as it doesn’t do it so often that it overflows the stack. A function that calls itself is called recursive. Recursion allows some functions to be written in a different style. Take, for example, this alternative implementation of power:

+ +
function power(base, exponent) {
+  if (exponent == 0) {
+    return 1;
+  } else {
+    return base * power(base, exponent - 1);
+  }
+}
+
+console.log(power(2, 3));
+// → 8
+ +

This is rather close to the way mathematicians define exponentiation and arguably describes the concept more clearly than the looping variant. The function calls itself multiple times with ever smaller exponents to achieve the repeated multiplication.

+ +

But this implementation has one problem: in typical JavaScript implementations, it’s about three times slower than the looping version. Running through a simple loop is generally cheaper than calling a function multiple times.

+ +

The dilemma of speed versus elegance is an interesting one. You can see it as a kind of continuum between human-friendliness and machine-friendliness. Almost any program can be made faster by making it bigger and more convoluted. The programmer has to decide on an appropriate balance.

+ +

In the case of the power function, the inelegant (looping) version is still fairly simple and easy to read. It doesn’t make much sense to replace it with the recursive version. Often, though, a program deals with such complex concepts that giving up some efficiency in order to make the program more straightforward is helpful.

+ +

Worrying about efficiency can be a distraction. It’s yet another factor that complicates program design, and when you’re doing something that’s already difficult, that extra thing to worry about can be paralyzing.

+ +

Therefore, always start by writing something that’s correct and easy to understand. If you’re worried that it’s too slow—which it usually isn’t since most code simply isn’t executed often enough to take any significant amount of time—you can measure afterward and improve it if necessary.

+ +

Recursion is not always just an inefficient alternative to looping. Some problems really are easier to solve with recursion than with loops. Most often these are problems that require exploring or processing several “branches”, each of which might branch out again into even more branches.

+ +

Consider this puzzle: by starting from the number 1 and repeatedly either adding 5 or multiplying by 3, an infinite set of numbers can be produced. How would you write a function that, given a number, tries to find a sequence of such additions and multiplications that produces that number?

+ +

For example, the number 13 could be reached by first multiplying by 3 and then adding 5 twice, whereas the number 15 cannot be reached at all.

+ +

Here is a recursive solution:

+ +
function findSolution(target) {
+  function find(current, history) {
+    if (current == target) {
+      return history;
+    } else if (current > target) {
+      return null;
+    } else {
+      return find(current + 5, `(${history} + 5)`) ||
+             find(current * 3, `(${history} * 3)`);
+    }
+  }
+  return find(1, "1");
+}
+
+console.log(findSolution(24));
+// → (((1 * 3) + 5) * 3)
+ +

Note that this program doesn’t necessarily find the shortest sequence of operations. It is satisfied when it finds any sequence at all.

+ +

It is okay if you don’t see how it works right away. Let’s work through it, since it makes for a great exercise in recursive thinking.

+ +

The inner function find does the actual recursing. It takes two arguments: the current number and a string that records how we reached this number. If it finds a solution, it returns a string that shows how to get to the target. If no solution can be found starting from this number, it returns null.

+ +

To do this, the function performs one of three actions. If the current number is the target number, the current history is a way to reach that target, so it is returned. If the current number is greater than the target, there’s no sense in further exploring this branch because both adding and multiplying will only make the number bigger, so it returns null. Finally, if we’re still below the target number, the function tries both possible paths that start from the current number by calling itself twice, once for addition and once for multiplication. If the first call returns something that is not null, it is returned. Otherwise, the second call is returned, regardless of whether it produces a string or null.

+ +

To better understand how this function produces the effect we’re looking for, let’s look at all the calls to find that are made when searching for a solution for the number 13.

+ +
find(1, "1")
+  find(6, "(1 + 5)")
+    find(11, "((1 + 5) + 5)")
+      find(16, "(((1 + 5) + 5) + 5)")
+        too big
+      find(33, "(((1 + 5) + 5) * 3)")
+        too big
+    find(18, "((1 + 5) * 3)")
+      too big
+  find(3, "(1 * 3)")
+    find(8, "((1 * 3) + 5)")
+      find(13, "(((1 * 3) + 5) + 5)")
+        found!
+ +

The indentation indicates the depth of the call stack. The first time find is called, it starts by calling itself to explore the solution that starts with (1 + 5). That call will further recurse to explore every continued solution that yields a number less than or equal to the target number. Since it doesn’t find one that hits the target, it returns null back to the first call. There the || operator causes the call that explores (1 * 3) to happen. This search has more luck—its first recursive call, through yet another recursive call, hits upon the target number. That innermost call returns a string, and each of the || operators in the intermediate calls passes that string along, ultimately returning the solution.

+ +

Growing functions

+ +

There are two more or less natural ways for functions to be introduced into programs.

+ +

The first is that you find yourself writing similar code multiple times. You’d prefer not to do that. Having more code means more space for mistakes to hide and more material to read for people trying to understand the program. So you take the repeated functionality, find a good name for it, and put it into a function.

+ +

The second way is that you find you need some functionality that you haven’t written yet and that sounds like it deserves its own function. You’ll start by naming the function, and then you’ll write its body. You might even start writing code that uses the function before you actually define the function itself.

+ +

How difficult it is to find a good name for a function is a good indication of how clear a concept it is that you’re trying to wrap. Let’s go through an example.

+ +

We want to write a program that prints two numbers: the numbers of cows and chickens on a farm, with the words Cows and Chickens after them and zeros padded before both numbers so that they are always three digits long.

+ +
007 Cows
+011 Chickens
+ +

This asks for a function of two arguments—the number of cows and the number of chickens. Let’s get coding.

+ +
function printFarmInventory(cows, chickens) {
+  let cowString = String(cows);
+  while (cowString.length < 3) {
+    cowString = "0" + cowString;
+  }
+  console.log(`${cowString} Cows`);
+  let chickenString = String(chickens);
+  while (chickenString.length < 3) {
+    chickenString = "0" + chickenString;
+  }
+  console.log(`${chickenString} Chickens`);
+}
+printFarmInventory(7, 11);
+ +

Writing .length after a string expression will give us the length of that string. Thus, the while loops keep adding zeros in front of the number strings until they are at least three characters long.

+ +

Mission accomplished! But just as we are about to send the farmer the code (along with a hefty invoice), she calls and tells us she’s also started keeping pigs, and couldn’t we please extend the software to also print pigs?

+ +

We sure can. But just as we’re in the process of copying and pasting those four lines one more time, we stop and reconsider. There has to be a better way. Here’s a first attempt:

+ +
function printZeroPaddedWithLabel(number, label) {
+  let numberString = String(number);
+  while (numberString.length < 3) {
+    numberString = "0" + numberString;
+  }
+  console.log(`${numberString} ${label}`);
+}
+
+function printFarmInventory(cows, chickens, pigs) {
+  printZeroPaddedWithLabel(cows, "Cows");
+  printZeroPaddedWithLabel(chickens, "Chickens");
+  printZeroPaddedWithLabel(pigs, "Pigs");
+}
+
+printFarmInventory(7, 11, 3);
+ +

It works! But that name, printZeroPaddedWithLabel, is a little awkward. It conflates three things—printing, zero-padding, and adding a label—into a single function.

+ +

Instead of lifting out the repeated part of our program wholesale, let’s try to pick out a single concept.

+ +
function zeroPad(number, width) {
+  let string = String(number);
+  while (string.length < width) {
+    string = "0" + string;
+  }
+  return string;
+}
+
+function printFarmInventory(cows, chickens, pigs) {
+  console.log(`${zeroPad(cows, 3)} Cows`);
+  console.log(`${zeroPad(chickens, 3)} Chickens`);
+  console.log(`${zeroPad(pigs, 3)} Pigs`);
+}
+
+printFarmInventory(7, 16, 3);
+ +

A function with a nice, obvious name like zeroPad makes it easier for someone who reads the code to figure out what it does. And such a function is useful in more situations than just this specific program. For example, you could use it to help print nicely aligned tables of numbers.

+ +

How smart and versatile should our function be? We could write anything, from a terribly simple function that can only pad a number to be three characters wide to a complicated generalized number-formatting system that handles fractional numbers, negative numbers, alignment of decimal dots, padding with different characters, and so on.

+ +

A useful principle is to not add cleverness unless you are absolutely sure you’re going to need it. It can be tempting to write general “frameworks” for every bit of functionality you come across. Resist that urge. You won’t get any real work done—you’ll just be writing code that you never use.

+ +

Functions and side effects

+ +

Functions can be roughly divided into those that are called for their side effects and those that are called for their return value. (Though it is definitely also possible to both have side effects and return a value.)

+ +

The first helper function in the farm example, printZeroPaddedWithLabel, is called for its side effect: it prints a line. The second version, zeroPad, is called for its return value. It is no coincidence that the second is useful in more situations than the first. Functions that create values are easier to combine in new ways than functions that directly perform side effects.

+ +

A pure function is a specific kind of value-producing function that not only has no side effects but also doesn’t rely on side effects from other code—for example, it doesn’t read global bindings whose value might change. A pure function has the pleasant property that, when called with the same arguments, it always produces the same value (and doesn’t do anything else). A call to such a function can be substituted by its return value without changing the meaning of the code. When you are not sure that a pure function is working correctly, you can test it by simply calling it and know that if it works in that context, it will work in any context. Nonpure functions tend to require more scaffolding to test.

+ +

Still, there’s no need to feel bad when writing functions that are not pure or to wage a holy war to purge them from your code. Side effects are often useful. There’d be no way to write a pure version of console.log, for example, and console.log is good to have. Some operations are also easier to express in an efficient way when we use side effects, so computing speed can be a reason to avoid purity.

+ +

Summary

+ +

This chapter taught you how to write your own functions. The function keyword, when used as an expression, can create a function value. When used as a statement, it can be used to declare a binding and give it a function as its value. Arrow functions are yet another way to create functions.

+ +
// Define f to hold a function value
+const f = function(a) {
+  console.log(a + 2);
+};
+
+// Declare g to be a function
+function g(a, b) {
+  return a * b * 3.5;
+}
+
+// A less verbose function value
+let h = a => a % 3;
+ +

A key aspect in understanding functions is understanding scopes. Each block creates a new scope. Parameters and bindings declared in a given scope are local and not visible from the outside. Bindings declared with var behave differently—they end up in the nearest function scope or the global scope.

+ +

Separating the tasks your program performs into different functions is helpful. You won’t have to repeat yourself as much, and functions can help organize a program by grouping code into pieces that do specific things.

+ +

Exercises

+ +

Minimum

+ +

The previous chapter introduced the standard function Math.min that returns its smallest argument. We can build something like that now. Write a function min that takes two arguments and returns their minimum.

+ +
// Your code here.
+
+console.log(min(0, 10));
+// → 0
+console.log(min(0, -10));
+// → -10
+ +
+ +

If you have trouble putting braces and parentheses in the right place to get a valid function definition, start by copying one of the examples in this chapter and modifying it.

+ +

A function may contain multiple return statements.

+ +
+ +

Recursion

+ +

We’ve seen that % (the remainder operator) can be used to test whether a number is even or odd by using % 2 to see whether it’s divisible by two. Here’s another way to define whether a positive whole number is even or odd:

+ +
    + +
  • + +

    Zero is even.

  • + +
  • + +

    One is odd.

  • + +
  • + +

    For any other number N, its evenness is the same as N - 2.

+ +

Define a recursive function isEven corresponding to this description. The function should accept a single parameter (a positive, whole number) and return a Boolean.

+ +

Test it on 50 and 75. See how it behaves on -1. Why? Can you think of a way to fix this?

+ +
// Your code here.
+
+console.log(isEven(50));
+// → true
+console.log(isEven(75));
+// → false
+console.log(isEven(-1));
+// → ??
+ +
+ +

Your function will likely look somewhat similar to the inner find function in the recursive findSolution example in this chapter, with an if/else if/else chain that tests which of the three cases applies. The final else, corresponding to the third case, makes the recursive call. Each of the branches should contain a return statement or in some other way arrange for a specific value to be returned.

+ +

When given a negative number, the function will recurse again and again, passing itself an ever more negative number, thus getting further and further away from returning a result. It will eventually run out of stack space and abort.

+ +
+ +

Bean counting

+ +

You can get the Nth character, or letter, from a string by writing "string"[N]. The returned value will be a string containing only one character (for example, "b"). The first character has position 0, which causes the last one to be found at position string.length - 1. In other words, a two-character string has length 2, and its characters have positions 0 and 1.

+ +

Write a function countBs that takes a string as its only argument and returns a number that indicates how many uppercase “B” characters there are in the string.

+ +

Next, write a function called countChar that behaves like countBs, except it takes a second argument that indicates the character that is to be counted (rather than counting only uppercase “B” characters). Rewrite countBs to make use of this new function.

+ +
// Your code here.
+
+console.log(countBs("BBC"));
+// → 2
+console.log(countChar("kakkerlak", "k"));
+// → 4
+ +
+ +

Your function will need a loop that looks at every character in the string. It can run an index from zero to one below its length (< string.length). If the character at the current position is the same as the one the function is looking for, it adds 1 to a counter variable. Once the loop has finished, the counter can be returned.

+ +

Take care to make all the bindings used in the function local to the function by properly declaring them with the let or const keyword.

+ +
+
diff --git a/docs/04_data.html b/docs/04_data.html new file mode 100644 index 000000000..2d2e07ffc --- /dev/null +++ b/docs/04_data.html @@ -0,0 +1,794 @@ + + + + + Data Structures: Objects and Arrays :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 4Data Structures: Objects and Arrays

+ +
+ +

On two occasions I have been asked, ‘Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?’ [...] I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.

+ +
Charles Babbage, Passages from the Life of a Philosopher (1864)
+ +
Picture of a weresquirrel
+ +

Numbers, Booleans, and strings are the atoms that data structures are built from. Many types of information require more than one atom, though. Objects allow us to group values—including other objects—to build more complex structures.

+ +

The programs we have built so far have been limited by the fact that they were operating only on simple data types. This chapter will introduce basic data structures. By the end of it, you’ll know enough to start writing useful programs.

+ +

The chapter will work through a more or less realistic programming example, introducing concepts as they apply to the problem at hand. The example code will often build on functions and bindings that were introduced earlier in the text.

+ +

The weresquirrel

+ +

Every now and then, usually between 8 p.m. and 10 p.m., Jacques finds himself transforming into a small furry rodent with a bushy tail.

+ +

On one hand, Jacques is quite glad that he doesn’t have classic lycanthropy. Turning into a squirrel does cause fewer problems than turning into a wolf. Instead of having to worry about accidentally eating the neighbor (that would be awkward), he worries about being eaten by the neighbor’s cat. After two occasions where he woke up on a precariously thin branch in the crown of an oak, naked and disoriented, he has taken to locking the doors and windows of his room at night and putting a few walnuts on the floor to keep himself busy.

+ +

That takes care of the cat and tree problems. But Jacques would prefer to get rid of his condition entirely. The irregular occurrences of the transformation make him suspect that they might be triggered by something. For a while, he believed that it happened only on days when he had been near oak trees. But avoiding oak trees did not stop the problem.

+ +

Switching to a more scientific approach, Jacques has started keeping a daily log of everything he does on a given day and whether he changed form. With this data he hopes to narrow down the conditions that trigger the transformations.

+ +

The first thing he needs is a data structure to store this information.

+ +

Data sets

+ +

To work with a chunk of digital data, we’ll first have to find a way to represent it in our machine’s memory. Say, for example, that we want to represent a collection of the numbers 2, 3, 5, 7, and 11.

+ +

We could get creative with strings—after all, strings can have any length, so we can put a lot of data into them—and use "2 3 5 7 11" as our representation. But this is awkward. You’d have to somehow extract the digits and convert them back to numbers to access them.

+ +

Fortunately, JavaScript provides a data type specifically for storing sequences of values. It is called an array and is written as a list of values between square brackets, separated by commas.

+ +
let listOfNumbers = [2, 3, 5, 7, 11];
+console.log(listOfNumbers[2]);
+// → 5
+console.log(listOfNumbers[0]);
+// → 2
+console.log(listOfNumbers[2 - 1]);
+// → 3
+ +

The notation for getting at the elements inside an array also uses square brackets. A pair of square brackets immediately after an expression, with another expression inside of them, will look up the element in the left-hand expression that corresponds to the index given by the expression in the brackets.

+ +

The first index of an array is zero, not one. So the first element is retrieved with listOfNumbers[0]. Zero-based counting has a long tradition in technology and in certain ways makes a lot of sense, but it takes some getting used to. Think of the index as the amount of items to skip, counting from the start of the array.

+ +

Properties

+ +

We’ve seen a few suspicious-looking expressions like myString.length (to get the length of a string) and Math.max (the maximum function) in past chapters. These are expressions that access a property of some value. In the first case, we access the length property of the value in myString. In the second, we access the property named max in the Math object (which is a collection of mathematics-related constants and functions).

+ +

Almost all JavaScript values have properties. The exceptions are null and undefined. If you try to access a property on one of these nonvalues, you get an error.

+ +
null.length;
+// → TypeError: null has no properties
+ +

The two main ways to access properties in JavaScript are with a dot and with square brackets. Both value.x and value[x] access a property on value—but not necessarily the same property. The difference is in how x is interpreted. When using a dot, the word after the dot is the literal name of the property. When using square brackets, the expression between the brackets is evaluated to get the property name. Whereas value.x fetches the property of value named “x”, value[x] tries to evaluate the expression x and uses the result, converted to a string, as the property name.

+ +

So if you know that the property you are interested in is called color, you say value.color. If you want to extract the property named by the value held in the binding i, you say value[i]. Property names are strings. They can be any string, but the dot notation works only with names that look like valid binding names. So if you want to access a property named 2 or John Doe, you must use square brackets: value[2] or value["John Doe"].

+ +

The elements in an array are stored as the array’s properties, using numbers as property names. Because you can’t use the dot notation with numbers and usually want to use a binding that holds the index anyway, you have to use the bracket notation to get at them.

+ +

The length property of an array tells us how many elements it has. This property name is a valid binding name, and we know its name in advance, so to find the length of an array, you typically write array.length because that’s easier to write than array["length"].

+ +

Methods

+ +

Both string and array objects contain, in addition to the length property, a number of properties that hold function values.

+ +
let doh = "Doh";
+console.log(typeof doh.toUpperCase);
+// → function
+console.log(doh.toUpperCase());
+// → DOH
+ +

Every string has a toUpperCase property. When called, it will return a copy of the string in which all letters have been converted to uppercase. There is also toLowerCase, going the other way.

+ +

Interestingly, even though the call to toUpperCase does not pass any arguments, the function somehow has access to the string "Doh", the value whose property we called. How this works is described in Chapter 6.

+ +

Properties that contain functions are generally called methods of the value they belong to, as in “toUpperCase is a method of a string”.

+ +

This example demonstrates two methods you can use to manipulate arrays:

+ +
let sequence = [1, 2, 3];
+sequence.push(4);
+sequence.push(5);
+console.log(sequence);
+// → [1, 2, 3, 4, 5]
+console.log(sequence.pop());
+// → 5
+console.log(sequence);
+// → [1, 2, 3, 4]
+ +

The push method adds values to the end of an array, and the pop method does the opposite, removing the last value in the array and returning it.

+ +

These somewhat silly names are the traditional terms for operations on a stack. A stack, in programming, is a data structure that allows you to push values into it and pop them out again in the opposite order so that the thing that was added last is removed first. These are common in programming—you might remember the function call +stack from the previous chapter, which is an instance of the same idea.

+ +

Objects

+ +

Back to the weresquirrel. A set of daily log entries can be represented as an array. But the entries do not consist of just a number or a string—each entry needs to store a list of activities and a Boolean value that indicates whether Jacques turned into a squirrel or not. Ideally, we would like to group these together into a single value and then put those grouped values into an array of log entries.

+ +

Values of the type object are arbitrary collections of properties. One way to create an object is by using braces as an expression.

+ +
let day1 = {
+  squirrel: false,
+  events: ["work", "touched tree", "pizza", "running"]
+};
+console.log(day1.squirrel);
+// → false
+console.log(day1.wolf);
+// → undefined
+day1.wolf = false;
+console.log(day1.wolf);
+// → false
+ +

Inside the braces, there is a list of properties separated by commas. Each property has a name followed by a colon and a value. When an object is written over multiple lines, indenting it like in the example helps with readability. Properties whose names aren’t valid binding names or valid numbers have to be quoted.

+ +
let descriptions = {
+  work: "Went to work",
+  "touched tree": "Touched a tree"
+};
+ +

This means that braces have two meanings in JavaScript. At the start of a statement, they start a block of statements. In any other position, they describe an object. Fortunately, it is rarely useful to start a statement with an object in braces, so the ambiguity between these two is not much of a problem.

+ +

Reading a property that doesn’t exist will give you the value undefined.

+ +

It is possible to assign a value to a property expression with the = operator. This will replace the property’s value if it already existed or create a new property on the object if it didn’t.

+ +

To briefly return to our tentacle model of bindings—property bindings are similar. They grasp values, but other bindings and properties might be holding onto those same values. You may think of objects as octopuses with any number of tentacles, each of which has a name tattooed on it.

+ +

The delete operator cuts off a tentacle from such an octopus. It is a unary operator that, when applied to an object property, will remove the named property from the object. This is not a common thing to do, but it is possible.

+ +
let anObject = {left: 1, right: 2};
+console.log(anObject.left);
+// → 1
+delete anObject.left;
+console.log(anObject.left);
+// → undefined
+console.log("left" in anObject);
+// → false
+console.log("right" in anObject);
+// → true
+ +

The binary in operator, when applied to a string and an object, tells you whether that object has a property with that name. The difference between setting a property to undefined and actually deleting it is that, in the first case, the object still has the property (it just doesn’t have a very interesting value), whereas in the second case the property is no longer present and in will return false.

+ +

To find out what properties an object has, you can use the Object.keys function. You give it an object, and it returns an array of strings—the object’s property names.

+ +
console.log(Object.keys({x: 0, y: 0, z: 2}));
+// → ["x", "y", "z"]
+ +

There’s an Object.assign function that copies all properties from one object into another.

+ +
let objectA = {a: 1, b: 2};
+Object.assign(objectA, {b: 3, c: 4});
+console.log(objectA);
+// → {a: 1, b: 3, c: 4}
+ +

Arrays, then, are just a kind of object specialized for storing sequences of things. If you evaluate typeof [], it produces "object". You can see them as long, flat octopuses with all their tentacles in a neat row, labeled with numbers.

+ +

We will represent the journal that Jacques keeps as an array of objects.

+ +
let journal = [
+  {events: ["work", "touched tree", "pizza",
+            "running", "television"],
+   squirrel: false},
+  {events: ["work", "ice cream", "cauliflower",
+            "lasagna", "touched tree", "brushed teeth"],
+   squirrel: false},
+  {events: ["weekend", "cycling", "break", "peanuts",
+            "beer"],
+   squirrel: true},
+  /* and so on... */
+];
+ +

Mutability

+ +

We will get to actual programming real soon now. First there’s one more piece of theory to understand.

+ +

We saw that object values can be modified. The types of values discussed in earlier chapters, such as numbers, strings, and Booleans, are all immutable—it is impossible to change values of those types. You can combine them and derive new values from them, but when you take a specific string value, that value will always remain the same. The text inside it cannot be changed. If you have a string that contains "cat", it is not possible for other code to change a character in your string to make it spell "rat".

+ +

Objects work differently. You can change their properties, causing a single object value to have different content at different times.

+ +

When we have two numbers, 120 and 120, we can consider them precisely the same number, whether or not they refer to the same physical bits. With objects, there is a difference between having two references to the same object and having two different objects that contain the same properties. Consider the following code:

+ +
let object1 = {value: 10};
+let object2 = object1;
+let object3 = {value: 10};
+
+console.log(object1 == object2);
+// → true
+console.log(object1 == object3);
+// → false
+
+object1.value = 15;
+console.log(object2.value);
+// → 15
+console.log(object3.value);
+// → 10
+ +

The object1 and object2 bindings grasp the same object, which is why changing object1 also changes the value of object2. They are said to have the same identity. The binding object3 points to a different object, which initially contains the same properties as object1 but lives a separate life.

+ +

Bindings can also be changeable or constant, but this is separate from the way their values behave. Even though number values don’t change, you can use a let binding to keep track of a changing number by changing the value the binding points at. Similarly, though a const binding to an object can itself not be changed and will continue to point at the same object, the contents of that object might change.

+ +
const score = {visitors: 0, home: 0};
+// This is okay
+score.visitors = 1;
+// This isn't allowed
+score = {visitors: 1, home: 1};
+ +

When you compare objects with JavaScript’s == operator, it compares by identity: it will produce true only if both objects are precisely the same value. Comparing different objects will return false, even if they have identical properties. There is no “deep” comparison operation built into JavaScript, which compares objects by contents, but it is possible to write it yourself (which is one of the exercises at the end of this chapter).

+ +

The lycanthrope’s log

+ +

So, Jacques starts up his JavaScript interpreter and sets up the environment he needs to keep his journal.

+ +
let journal = [];
+
+function addEntry(events, squirrel) {
+  journal.push({events, squirrel});
+}
+ +

Note that the object added to the journal looks a little odd. Instead of declaring properties like events: events, it just gives a property name. This is shorthand that means the same thing—if a property name in brace notation isn’t followed by a value, its value is taken from the binding with the same name.

+ +

So then, every evening at 10 p.m.—or sometimes the next morning, after climbing down from the top shelf of his bookcase—Jacques records the day.

+ +
addEntry(["work", "touched tree", "pizza", "running",
+          "television"], false);
+addEntry(["work", "ice cream", "cauliflower", "lasagna",
+          "touched tree", "brushed teeth"], false);
+addEntry(["weekend", "cycling", "break", "peanuts",
+          "beer"], true);
+ +

Once he has enough data points, he intends to use statistics to find out which of these events may be related to the squirrelifications.

+ +

Correlation is a measure of dependence between statistical variables. A statistical variable is not quite the same as a programming variable. In statistics you typically have a set of measurements, and each variable is measured for every measurement. Correlation between variables is usually expressed as a value that ranges from -1 to 1. Zero correlation means the variables are not related. A correlation of one indicates that the two are perfectly related—if you know one, you also know the other. Negative one also means that the variables are perfectly related but that they are opposites—when one is true, the other is false.

+ +

To compute the measure of correlation between two Boolean variables, we can use the phi coefficient (ϕ). This is a formula whose input is a frequency table containing the number of times the different combinations of the variables were observed. The output of the formula is a number between -1 and 1 that describes the correlation.

+ +

We could take the event of eating pizza and put that in a frequency table like this, where each number indicates the amount of times that combination occurred in our measurements:

Eating pizza versus turning into a squirrel
+ +

If we call that table n, we can compute ϕ using the following formula:

+ + + +
ϕ = +
n11n00 − + n10n01
+
+ n1•n0•n•1n•0 +
+
+
+ + +

(If at this point you’re putting the book down to focus on a terrible flashback to 10th grade math class—hold on! I do not intend to torture you with endless pages of cryptic notation—it’s just this one formula for now. And even with this one, all we do is turn it into JavaScript.)

+ +

The notation n01 indicates the number of measurements where the first variable (squirrelness) is false (0) and the second variable (pizza) is true (1). In the pizza table, n01 is 9.

+ +

The value n1• refers to the sum of all measurements where the first variable is true, which is 5 in the example table. Likewise, n•0 refers to the sum of the measurements where the second variable is false.

+ +

So for the pizza table, the part above the division line (the dividend) would be 1×76−4×9 = 40, and the part below it (the divisor) would be the square root of 5×85×10×80, or √340000. This comes out to ϕ ≈ 0.069, which is tiny. Eating pizza does not appear to have influence on the transformations.

+ +

Computing correlation

+ +

We can represent a two-by-two table in JavaScript with a four-element array ([76, 9, 4, 1]). We could also use other representations, such as an array containing two two-element arrays ([[76, 9], [4, 1]]) or an object with property names like "11" and "01", but the flat array is simple and makes the expressions that access the table pleasantly short. We’ll interpret the indices to the array as two-bit binary numbers, where the leftmost (most significant) digit refers to the squirrel variable and the rightmost (least significant) digit refers to the event variable. For example, the binary number 10 refers to the case where Jacques did turn into a squirrel, but the event (say, “pizza”) didn’t occur. This happened four times. And since binary 10 is 2 in decimal notation, we will store this number at index 2 of the array.

+ +

This is the function that computes the ϕ coefficient from such an array:

+ +
function phi(table) {
+  return (table[3] * table[0] - table[2] * table[1]) /
+    Math.sqrt((table[2] + table[3]) *
+              (table[0] + table[1]) *
+              (table[1] + table[3]) *
+              (table[0] + table[2]));
+}
+
+console.log(phi([76, 9, 4, 1]));
+// → 0.068599434
+ +

This is a direct translation of the ϕ formula into JavaScript. Math.sqrt is the square root function, as provided by the Math object in a standard JavaScript environment. We have to add two fields from the table to get fields like n1• because the sums of rows or columns are not stored directly in our data structure.

+ +

Jacques kept his journal for three months. The resulting data set is available in the coding sandbox for this chapter, where it is stored in the JOURNAL binding and in a downloadable file.

+ +

To extract a two-by-two table for a specific event from the journal, we must loop over all the entries and tally how many times the event occurs in relation to squirrel transformations.

+ +
function tableFor(event, journal) {
+  let table = [0, 0, 0, 0];
+  for (let i = 0; i < journal.length; i++) {
+    let entry = journal[i], index = 0;
+    if (entry.events.includes(event)) index += 1;
+    if (entry.squirrel) index += 2;
+    table[index] += 1;
+  }
+  return table;
+}
+
+console.log(tableFor("pizza", JOURNAL));
+// → [76, 9, 4, 1]
+ +

Arrays have an includes method that checks whether a given value exists in the array. The function uses that to determine whether the event name it is interested in is part of the event list for a given day.

+ +

The body of the loop in tableFor figures out which box in the table each journal entry falls into by checking whether the entry contains the specific event it’s interested in and whether the event happens alongside a squirrel incident. The loop then adds one to the correct box in the table.

+ +

We now have the tools we need to compute individual correlations. The only step remaining is to find a correlation for every type of event that was recorded and see whether anything stands out.

+ +

Array loops

+ +

In the tableFor function, there’s a loop like this:

+ +
for (let i = 0; i < JOURNAL.length; i++) {
+  let entry = JOURNAL[i];
+  // Do something with entry
+}
+ +

This kind of loop is common in classical JavaScript—going over arrays one element at a time is something that comes up a lot, and to do that you’d run a counter over the length of the array and pick out each element in turn.

+ +

There is a simpler way to write such loops in modern JavaScript.

+ +
for (let entry of JOURNAL) {
+  console.log(`${entry.events.length} events.`);
+}
+ +

When a for loop looks like this, with the word of after a variable definition, it will loop over the elements of the value given after of. This works not only for arrays but also for strings and some other data structures. We’ll discuss how it works in Chapter 6.

+ +

The final analysis

+ +

We need to compute a correlation for every type of event that occurs in the data set. To do that, we first need to find every type of event.

+ +
function journalEvents(journal) {
+  let events = [];
+  for (let entry of journal) {
+    for (let event of entry.events) {
+      if (!events.includes(event)) {
+        events.push(event);
+      }
+    }
+  }
+  return events;
+}
+
+console.log(journalEvents(JOURNAL));
+// → ["carrot", "exercise", "weekend", "bread", …]
+ +

By going over all the events and adding those that aren’t already in there to the events array, the function collects every type of event.

+ +

Using that, we can see all the correlations.

+ +
for (let event of journalEvents(JOURNAL)) {
+  console.log(event + ":", phi(tableFor(event, JOURNAL)));
+}
+// → carrot:   0.0140970969
+// → exercise: 0.0685994341
+// → weekend:  0.1371988681
+// → bread:   -0.0757554019
+// → pudding: -0.0648203724
+// and so on...
+ +

Most correlations seem to lie close to zero. Eating carrots, bread, or pudding apparently does not trigger squirrel-lycanthropy. It does seem to occur somewhat more often on weekends. Let’s filter the results to show only correlations greater than 0.1 or less than -0.1.

+ +
for (let event of journalEvents(JOURNAL)) {
+  let correlation = phi(tableFor(event, JOURNAL));
+  if (correlation > 0.1 || correlation < -0.1) {
+    console.log(event + ":", correlation);
+  }
+}
+// → weekend:        0.1371988681
+// → brushed teeth: -0.3805211953
+// → candy:          0.1296407447
+// → work:          -0.1371988681
+// → spaghetti:      0.2425356250
+// → reading:        0.1106828054
+// → peanuts:        0.5902679812
+ +

Aha! There are two factors with a correlation that’s clearly stronger than the others. Eating peanuts has a strong positive effect on the chance of turning into a squirrel, whereas brushing his teeth has a significant negative effect.

+ +

Interesting. Let’s try something.

+ +
for (let entry of JOURNAL) {
+  if (entry.events.includes("peanuts") &&
+     !entry.events.includes("brushed teeth")) {
+    entry.events.push("peanut teeth");
+  }
+}
+console.log(phi(tableFor("peanut teeth", JOURNAL)));
+// → 1
+ +

That’s a strong result. The phenomenon occurs precisely when Jacques eats peanuts and fails to brush his teeth. If only he weren’t such a slob about dental hygiene, he’d have never even noticed his affliction.

+ +

Knowing this, Jacques stops eating peanuts altogether and finds that his transformations don’t come back.

+ +

For a few years, things go great for Jacques. But at some point he loses his job. Because he lives in a nasty country where having no job means having no medical services, he is forced to take employment with a circus where he performs as The Incredible Squirrelman, stuffing his mouth with peanut butter before every show.

+ +

One day, fed up with this pitiful existence, Jacques fails to change back into his human form, hops through a crack in the circus tent, and vanishes into the forest. He is never seen again.

+ +

Further arrayology

+ +

Before finishing the chapter, I want to introduce you to a few more object-related concepts. I’ll start by introducing some generally useful array methods.

+ +

We saw push and pop, which add and remove elements at the end of an array, earlier in this chapter. The corresponding methods for adding and removing things at the start of an array are called unshift and shift.

+ +
let todoList = [];
+function remember(task) {
+  todoList.push(task);
+}
+function getTask() {
+  return todoList.shift();
+}
+function rememberUrgently(task) {
+  todoList.unshift(task);
+}
+ +

That program manages a queue of tasks. You add tasks to the end of the queue by calling remember("groceries"), and when you’re ready to do something, you call getTask() to get (and remove) the front item from the queue. The rememberUrgently function also adds a task but adds it to the front instead of the back of the queue.

+ +

To search for a specific value, arrays provide an indexOf method. The method searches through the array from the start to the end and returns the index at which the requested value was found—or -1 if it wasn’t found. To search from the end instead of the start, there’s a similar method called lastIndexOf.

+ +
console.log([1, 2, 3, 2, 1].indexOf(2));
+// → 1
+console.log([1, 2, 3, 2, 1].lastIndexOf(2));
+// → 3
+ +

Both indexOf and lastIndexOf take an optional second argument that indicates where to start searching.

+ +

Another fundamental array method is slice, which takes start and end indices and returns an array that has only the elements between them. The start index is inclusive, the end index exclusive.

+ +
console.log([0, 1, 2, 3, 4].slice(2, 4));
+// → [2, 3]
+console.log([0, 1, 2, 3, 4].slice(2));
+// → [2, 3, 4]
+ +

When the end index is not given, slice will take all of the elements after the start index. You can also omit the start index to copy the entire array.

+ +

The concat method can be used to glue arrays together to create a new array, similar to what the + operator does for strings.

+ +

The following example shows both concat and slice in action. It takes an array and an index, and it returns a new array that is a copy of the original array with the element at the given index removed.

+ +
function remove(array, index) {
+  return array.slice(0, index)
+    .concat(array.slice(index + 1));
+}
+console.log(remove(["a", "b", "c", "d", "e"], 2));
+// → ["a", "b", "d", "e"]
+ +

If you pass concat an argument that is not an array, that value will be added to the new array as if it were a one-element array.

+ +

Strings and their properties

+ +

We can read properties like length and toUpperCase from string values. But if you try to add a new property, it doesn’t stick.

+ +
let kim = "Kim";
+kim.age = 88;
+console.log(kim.age);
+// → undefined
+ +

Values of type string, number, and Boolean are not objects, and though the language doesn’t complain if you try to set new properties on them, it doesn’t actually store those properties. As mentioned earlier, such values are immutable and cannot be changed.

+ +

But these types do have built-in properties. Every string value has a number of methods. Some very useful ones are slice and indexOf, which resemble the array methods of the same name.

+ +
console.log("coconuts".slice(4, 7));
+// → nut
+console.log("coconut".indexOf("u"));
+// → 5
+ +

One difference is that a string’s indexOf can search for a string containing more than one character, whereas the corresponding array method looks only for a single element.

+ +
console.log("one two three".indexOf("ee"));
+// → 11
+ +

The trim method removes whitespace (spaces, newlines, tabs, and similar characters) from the start and end of a string.

+ +
console.log("  okay \n ".trim());
+// → okay
+ +

The zeroPad function from the previous chapter also exists as a method. It is called padStart and takes the desired length and padding character as arguments.

+ +
console.log(String(6).padStart(3, "0"));
+// → 006
+ +

You can split a string on every occurrence of another string with split and join it again with join.

+ +
let sentence = "Secretarybirds specialize in stomping";
+let words = sentence.split(" ");
+console.log(words);
+// → ["Secretarybirds", "specialize", "in", "stomping"]
+console.log(words.join(". "));
+// → Secretarybirds. specialize. in. stomping
+ +

A string can be repeated with the repeat method, which creates a new string containing multiple copies of the original string, glued together.

+ +
console.log("LA".repeat(3));
+// → LALALA
+ +

We have already seen the string type’s length property. Accessing the individual characters in a string looks like accessing array elements (with a caveat that we’ll discuss in Chapter 5).

+ +
let string = "abc";
+console.log(string.length);
+// → 3
+console.log(string[1]);
+// → b
+ +

Rest parameters

+ +

It can be useful for a function to accept any number of arguments. For example, Math.max computes the maximum of all the arguments it is given.

+ +

To write such a function, you put three dots before the function’s last parameter, like this:

+ +
function max(...numbers) {
+  let result = -Infinity;
+  for (let number of numbers) {
+    if (number > result) result = number;
+  }
+  return result;
+}
+console.log(max(4, 1, 9, -2));
+// → 9
+ +

When such a function is called, the rest parameter is bound to an array containing all further arguments. If there are other parameters before it, their values aren’t part of that array. When, as in max, it is the only parameter, it will hold all arguments.

+ +

You can use a similar three-dot notation to call a function with an array of arguments.

+ +
let numbers = [5, 1, 7];
+console.log(max(...numbers));
+// → 7
+ +

This “spreads” out the array into the function call, passing its elements as separate arguments. It is possible to include an array like that along with other arguments, as in max(9, ...numbers, 2).

+ +

Square bracket array notation similarly allows the triple-dot operator to spread another array into the new array.

+ +
let words = ["never", "fully"];
+console.log(["will", ...words, "understand"]);
+// → ["will", "never", "fully", "understand"]
+ +

The Math object

+ +

As we’ve seen, Math is a grab bag of number-related utility functions, such as Math.max (maximum), Math.min (minimum), and Math.sqrt (square root).

+ +

The Math object is used as a container to group a bunch of related functionality. There is only one Math object, and it is almost never useful as a value. Rather, it provides a namespace so that all these functions and values do not have to be global bindings.

+ +

Having too many global bindings “pollutes” the namespace. The more names have been taken, the more likely you are to accidentally overwrite the value of some existing binding. For example, it’s not unlikely to want to name something max in one of your programs. Since JavaScript’s built-in max function is tucked safely inside the Math object, we don’t have to worry about overwriting it.

+ +

Many languages will stop you, or at least warn you, when you are defining a binding with a name that is already taken. JavaScript does this for bindings you declared with let or const but—perversely—not for standard bindings nor for bindings declared with var or function.

+ +

Back to the Math object. If you need to do trigonometry, Math can help. It contains cos (cosine), sin (sine), and tan (tangent), as well as their inverse functions, acos, asin, and atan, respectively. The number π (pi)—or at least the closest approximation that fits in a JavaScript number—is available as Math.PI. There is an old programming tradition of writing the names of constant values in all caps.

+ +
function randomPointOnCircle(radius) {
+  let angle = Math.random() * 2 * Math.PI;
+  return {x: radius * Math.cos(angle),
+          y: radius * Math.sin(angle)};
+}
+console.log(randomPointOnCircle(2));
+// → {x: 0.3667, y: 1.966}
+ +

If sines and cosines are not something you are familiar with, don’t worry. When they are used in this book, in Chapter 14, I’ll explain them.

+ +

The previous example used Math.random. This is a function that returns a new pseudorandom number between zero (inclusive) and one (exclusive) every time you call it.

+ +
console.log(Math.random());
+// → 0.36993729369714856
+console.log(Math.random());
+// → 0.727367032552138
+console.log(Math.random());
+// → 0.40180766698904335
+ +

Though computers are deterministic machines—they always react the same way if given the same input—it is possible to have them produce numbers that appear random. To do that, the machine keeps some hidden value, and whenever you ask for a new random number, it performs complicated computations on this hidden value to create a new value. It stores a new value and returns some number derived from it. That way, it can produce ever new, hard-to-predict numbers in a way that seems random.

+ +

If we want a whole random number instead of a fractional one, we can use Math.floor (which rounds down to the nearest whole number) on the result of Math.random.

+ +
console.log(Math.floor(Math.random() * 10));
+// → 2
+ +

Multiplying the random number by 10 gives us a number greater than or equal to 0 and below 10. Since Math.floor rounds down, this expression will produce, with equal chance, any number from 0 through 9.

+ +

There are also the functions Math.ceil (for “ceiling”, which rounds up to a whole number), Math.round (to the nearest whole number), and Math.abs, which takes the absolute value of a number, meaning it negates negative values but leaves positive ones as they are.

+ +

Destructuring

+ +

Let’s go back to the phi function for a moment.

+ +
function phi(table) {
+  return (table[3] * table[0] - table[2] * table[1]) /
+    Math.sqrt((table[2] + table[3]) *
+              (table[0] + table[1]) *
+              (table[1] + table[3]) *
+              (table[0] + table[2]));
+}
+ +

One of the reasons this function is awkward to read is that we have a binding pointing at our array, but we’d much prefer to have bindings for the elements of the array, that is, let n00 = table[0] and so on. Fortunately, there is a succinct way to do this in JavaScript.

+ +
function phi([n00, n01, n10, n11]) {
+  return (n11 * n00 - n10 * n01) /
+    Math.sqrt((n10 + n11) * (n00 + n01) *
+              (n01 + n11) * (n00 + n10));
+}
+ +

This also works for bindings created with let, var, or const. If you know the value you are binding is an array, you can use square brackets to “look inside” of the value, binding its contents.

+ +

A similar trick works for objects, using braces instead of square brackets.

+ +
let {name} = {name: "Faraji", age: 23};
+console.log(name);
+// → Faraji
+ +

Note that if you try to destructure null or undefined, you get an error, much as you would if you directly try to access a property of those values.

+ +

JSON

+ +

Because properties only grasp their value, rather than contain it, objects and arrays are stored in the computer’s memory as sequences of bits holding the addresses—the place in memory—of their contents. So an array with another array inside of it consists of (at least) one memory region for the inner array, and another for the outer array, containing (among other things) a binary number that represents the position of the inner array.

+ +

If you want to save data in a file for later or send it to another computer over the network, you have to somehow convert these tangles of memory addresses to a description that can be stored or sent. You could send over your entire computer memory along with the address of the value you’re interested in, I suppose, but that doesn’t seem like the best approach.

+ +

What we can do is serialize the data. That means it is converted into a flat description. A popular serialization format is called JSON (pronounced “Jason”), which stands for JavaScript Object Notation. It is widely used as a data storage and communication format on the Web, even in languages other than JavaScript.

+ +

JSON looks similar to JavaScript’s way of writing arrays and objects, with a few restrictions. All property names have to be surrounded by double quotes, and only simple data expressions are allowed—no function calls, bindings, or anything that involves actual computation. Comments are not allowed in JSON.

+ +

A journal entry might look like this when represented as JSON data:

+ +
{
+  "squirrel": false,
+  "events": ["work", "touched tree", "pizza", "running"]
+}
+ +

JavaScript gives us the functions JSON.stringify and JSON.parse to convert data to and from this format. The first takes a JavaScript value and returns a JSON-encoded string. The second takes such a string and converts it to the value it encodes.

+ +
let string = JSON.stringify({squirrel: false,
+                             events: ["weekend"]});
+console.log(string);
+// → {"squirrel":false,"events":["weekend"]}
+console.log(JSON.parse(string).events);
+// → ["weekend"]
+ +

Summary

+ +

Objects and arrays (which are a specific kind of object) provide ways to group several values into a single value. Conceptually, this allows us to put a bunch of related things in a bag and run around with the bag, instead of wrapping our arms around all of the individual things and trying to hold on to them separately.

+ +

Most values in JavaScript have properties, the exceptions being null and undefined. Properties are accessed using value.prop or value["prop"]. Objects tend to use names for their properties and store more or less a fixed set of them. Arrays, on the other hand, usually contain varying amounts of conceptually identical values and use numbers (starting from 0) as the names of their properties.

+ +

There are some named properties in arrays, such as length and a number of methods. Methods are functions that live in properties and (usually) act on the value they are a property of.

+ +

You can iterate over arrays using a special kind of for loop—for (let element of array).

+ +

Exercises

+ +

The sum of a range

+ +

The introduction of this book alluded to the following as a nice way to compute the sum of a range of numbers:

+ +
console.log(sum(range(1, 10)));
+ +

Write a range function that takes two arguments, start and end, and returns an array containing all the numbers from start up to (and including) end.

+ +

Next, write a sum function that takes an array of numbers and returns the sum of these numbers. Run the example program and see whether it does indeed return 55.

+ +

As a bonus assignment, modify your range function to take an optional third argument that indicates the “step” value used when building the array. If no step is given, the elements go up by increments of one, corresponding to the old behavior. The function call range(1, 10, 2) should return [1, 3, 5, 7, 9]. Make sure it also works with negative step values so that range(5, 2, -1) produces [5, 4, 3, 2].

+ +
// Your code here.
+
+console.log(range(1, 10));
+// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+console.log(range(5, 2, -1));
+// → [5, 4, 3, 2]
+console.log(sum(range(1, 10)));
+// → 55
+ +
+ +

Building up an array is most easily done by first initializing a binding to [] (a fresh, empty array) and repeatedly calling its push method to add a value. Don’t forget to return the array at the end of the function.

+ +

Since the end boundary is inclusive, you’ll need to use the <= operator rather than < to check for the end of your loop.

+ +

The step parameter can be an optional parameter that defaults (using the = operator) to 1.

+ +

Having range understand negative step values is probably best done by writing two separate loops—one for counting up and one for counting down—because the comparison that checks whether the loop is finished needs to be >= rather than <= when counting downward.

+ +

It might also be worthwhile to use a different default step, namely, -1, when the end of the range is smaller than the start. That way, range(5, 2) returns something meaningful, rather than getting stuck in an infinite loop. It is possible to refer to previous parameters in the default value of a parameter.

+ +
+ +

Reversing an array

+ +

Arrays have a reverse method that changes the array by inverting the order in which its elements appear. For this exercise, write two functions, reverseArray and reverseArrayInPlace. The first, reverseArray, takes an array as argument and produces a new array that has the same elements in the inverse order. The second, reverseArrayInPlace, does what the reverse method does: it modifies the array given as argument by reversing its elements. Neither may use the standard reverse method.

+ +

Thinking back to the notes about side effects and pure functions in the previous chapter, which variant do you expect to be useful in more situations? Which one runs faster?

+ +
// Your code here.
+
+console.log(reverseArray(["A", "B", "C"]));
+// → ["C", "B", "A"];
+let arrayValue = [1, 2, 3, 4, 5];
+reverseArrayInPlace(arrayValue);
+console.log(arrayValue);
+// → [5, 4, 3, 2, 1]
+ +
+ +

There are two obvious ways to implement reverseArray. The first is to simply go over the input array from front to back and use the unshift method on the new array to insert each element at its start. The second is to loop over the input array backwards and use the push method. Iterating over an array backwards requires a (somewhat awkward) for specification, like (let i = array.length - 1; i >= 0; i--).

+ +

Reversing the array in place is harder. You have to be careful not to overwrite elements that you will later need. Using reverseArray or otherwise copying the whole array (array.slice(0) is a good way to copy an array) works but is cheating.

+ +

The trick is to swap the first and last elements, then the second and second-to-last, and so on. You can do this by looping over half the length of the array (use Math.floor to round down—you don’t need to touch the middle element in an array with an odd number of elements) and swapping the element at position i with the one at position array.length - 1 - i. You can use a local binding to briefly hold on to one of the elements, overwrite that one with its mirror image, and then put the value from the local binding in the place where the mirror image used to be.

+ +
+ +

A list

+ +

Objects, as generic blobs of values, can be used to build all sorts of data structures. A common data structure is the list (not to be confused with array). A list is a nested set of objects, with the first object holding a reference to the second, the second to the third, and so on.

+ +
let list = {
+  value: 1,
+  rest: {
+    value: 2,
+    rest: {
+      value: 3,
+      rest: null
+    }
+  }
+};
+ +

The resulting objects form a chain, like this:

A linked list
+ +

A nice thing about lists is that they can share parts of their structure. For example, if I create two new values {value: 0, rest: list} and {value: -1, rest: list} (with list referring to the binding defined earlier), they are both independent lists, but they share the structure that makes up their last three elements. The original list is also still a valid three-element list.

+ +

Write a function arrayToList that builds up a list structure like the one shown when given [1, 2, 3] as argument. Also write a listToArray function that produces an array from a list. Then add a helper function prepend, which takes an element and a list and creates a new list that adds the element to the front of the input list, and nth, which takes a list and a number and returns the element at the given position in the list (with zero referring to the first element) or undefined when there is no such element.

+ +

If you haven’t already, also write a recursive version of nth.

+ +
// Your code here.
+
+console.log(arrayToList([10, 20]));
+// → {value: 10, rest: {value: 20, rest: null}}
+console.log(listToArray(arrayToList([10, 20, 30])));
+// → [10, 20, 30]
+console.log(prepend(10, prepend(20, null)));
+// → {value: 10, rest: {value: 20, rest: null}}
+console.log(nth(arrayToList([10, 20, 30]), 1));
+// → 20
+ +
+ +

Building up a list is easier when done back to front. So arrayToList could iterate over the array backwards (see the previous exercise) and, for each element, add an object to the list. You can use a local binding to hold the part of the list that was built so far and use an assignment like list = {value: X, rest: list} to add an element.

+ +

To run over a list (in listToArray and nth), a for loop specification like this can be used:

+ +
for (let node = list; node; node = node.rest) {}
+ +

Can you see how that works? Every iteration of the loop, node points to the current sublist, and the body can read its value property to get the current element. At the end of an iteration, node moves to the next sublist. When that is null, we have reached the end of the list, and the loop is finished.

+ +

The recursive version of nth will, similarly, look at an ever smaller part of the “tail” of the list and at the same time count down the index until it reaches zero, at which point it can return the value property of the node it is looking at. To get the zeroth element of a list, you simply take the value property of its head node. To get element N + 1, you take the Nth element of the list that’s in this list’s rest property.

+ +
+ +

Deep comparison

+ +

The == operator compares objects by identity. But sometimes you’d prefer to compare the values of their actual properties.

+ +

Write a function deepEqual that takes two values and returns true only if they are the same value or are objects with the same properties, where the values of the properties are equal when compared with a recursive call to deepEqual.

+ +

To find out whether values should be compared directly (use the === operator for that) or have their properties compared, you can use the typeof operator. If it produces "object" for both values, you should do a deep comparison. But you have to take one silly exception into account: because of a historical accident, typeof null also produces "object".

+ +

The Object.keys function will be useful when you need to go over the properties of objects to compare them.

+ +
// Your code here.
+
+let obj = {here: {is: "an"}, object: 2};
+console.log(deepEqual(obj, obj));
+// → true
+console.log(deepEqual(obj, {here: 1, object: 2}));
+// → false
+console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
+// → true
+ +
+ +

Your test for whether you are dealing with a real object will look something like typeof x == "object" && x != null. Be careful to compare properties only when both arguments are objects. In all other cases you can just immediately return the result of applying ===.

+ +

Use Object.keys to go over the properties. You need to test whether both objects have the same set of property names and whether those properties have identical values. One way to do that is to ensure that both objects have the same number of properties (the lengths of the property lists are the same). And then, when looping over one of the object’s properties to compare them, always first make sure the other actually has a property by that name. If they have the same number of properties and all properties in one also exist in the other, they have the same set of property names.

+ +

Returning the correct value from the function is best done by immediately returning false when a mismatch is found and returning true at the end of the function.

+ +
+
diff --git a/docs/05_higher_order.html b/docs/05_higher_order.html new file mode 100644 index 000000000..966bd9b43 --- /dev/null +++ b/docs/05_higher_order.html @@ -0,0 +1,511 @@ + + + + + Higher-Order Functions :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 5Higher-Order Functions

+ +
+ +

Tzu-li and Tzu-ssu were boasting about the size of their latest programs. ‘Two-hundred thousand lines,’ said Tzu-li, ‘not counting comments!’ Tzu-ssu responded, ‘Pssh, mine is almost a million lines already.’ Master Yuan-Ma said, ‘My best program has five hundred lines.’ Hearing this, Tzu-li and Tzu-ssu were enlightened.

+ +
Master Yuan-Ma, The Book of Programming
+ +
+ +
+ +

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.

+ +
C.A.R. Hoare, 1980 ACM Turing Award Lecture
+ +
Letters from different scripts
+ +

A large program is a costly program, and not just because of the time it takes to build. Size almost always involves complexity, and complexity confuses programmers. Confused programmers, in turn, introduce mistakes (bugs) into programs. A large program then provides a lot of space for these bugs to hide, making them hard to find.

+ +

Let’s briefly go back to the final two example programs in the introduction. The first is self-contained and six lines long.

+ +
let total = 0, count = 1;
+while (count <= 10) {
+  total += count;
+  count += 1;
+}
+console.log(total);
+ +

The second relies on two external functions and is one line long.

+ +
console.log(sum(range(1, 10)));
+ +

Which one is more likely to contain a bug?

+ +

If we count the size of the definitions of sum and range, the second program is also big—even bigger than the first. But still, I’d argue that it is more likely to be correct.

+ +

It is more likely to be correct because the solution is expressed in a vocabulary that corresponds to the problem being solved. Summing a range of numbers isn’t about loops and counters. It is about ranges and sums.

+ +

The definitions of this vocabulary (the functions sum and range) will still involve loops, counters, and other incidental details. But because they are expressing simpler concepts than the program as a whole, they are easier to get right.

+ +

Abstraction

+ +

In the context of programming, these kinds of vocabularies are usually called abstractions. Abstractions hide details and give us the ability to talk about problems at a higher (or more abstract) level.

+ +

As an analogy, compare these two recipes for pea soup. The first one goes like this:

+ +
+ +

Put 1 cup of dried peas per person into a container. Add water until the peas are well covered. Leave the peas in water for at least 12 hours. Take the peas out of the water and put them in a cooking pan. Add 4 cups of water per person. Cover the pan and keep the peas simmering for two hours. Take half an onion per person. Cut it into pieces with a knife. Add it to the peas. Take a stalk of celery per person. Cut it into pieces with a knife. Add it to the peas. Take a carrot per person. Cut it into pieces. With a knife! Add it to the peas. Cook for 10 more minutes.

+ +
+ +

And this is the second recipe:

+ +
+ +

Per person: 1 cup dried split peas, half a chopped onion, a stalk of celery, and a carrot.

+ +

Soak peas for 12 hours. Simmer for 2 hours in 4 cups of water (per person). Chop and add vegetables. Cook for 10 more minutes.

+ +
+ +

The second is shorter and easier to interpret. But you do need to understand a few more cooking-related words such as soak, simmer, chop, and, I guess, vegetable.

+ +

When programming, we can’t rely on all the words we need to be waiting for us in the dictionary. Thus, we might fall into the pattern of the first recipe—work out the precise steps the computer has to perform, one by one, blind to the higher-level concepts that they express.

+ +

It is a useful skill, in programming, to notice when you are working at too low a level of abstraction.

+ +

Abstracting repetition

+ +

Plain functions, as we’ve seen them so far, are a good way to build abstractions. But sometimes they fall short.

+ +

It is common for a program to do something a given number of times. You can write a for loop for that, like this:

+ +
for (let i = 0; i < 10; i++) {
+  console.log(i);
+}
+ +

Can we abstract “doing something N times” as a function? Well, it’s easy to write a function that calls console.log N times.

+ +
function repeatLog(n) {
+  for (let i = 0; i < n; i++) {
+    console.log(i);
+  }
+}
+ +

But what if we want to do something other than logging the numbers? Since “doing something” can be represented as a function and functions are just values, we can pass our action as a function value.

+ +
function repeat(n, action) {
+  for (let i = 0; i < n; i++) {
+    action(i);
+  }
+}
+
+repeat(3, console.log);
+// → 0
+// → 1
+// → 2
+ +

We don’t have to pass a predefined function to repeat. Often, it is easier to create a function value on the spot instead.

+ +
let labels = [];
+repeat(5, i => {
+  labels.push(`Unit ${i + 1}`);
+});
+console.log(labels);
+// → ["Unit 1", "Unit 2", "Unit 3", "Unit 4", "Unit 5"]
+ +

This is structured a little like a for loop—it first describes the kind of loop and then provides a body. However, the body is now written as a function value, which is wrapped in the parentheses of the call to repeat. This is why it has to be closed with the closing brace and closing parenthesis. In cases like this example, where the body is a single small expression, you could also omit the braces and write the loop on a single line.

+ +

Higher-order functions

+ +

Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions. Since we have already seen that functions are regular values, there is nothing particularly remarkable about the fact that such functions exist. The term comes from mathematics, where the distinction between functions and other values is taken more seriously.

+ +

Higher-order functions allow us to abstract over actions, not just values. They come in several forms. For example, we can have functions that create new functions.

+ +
function greaterThan(n) {
+  return m => m > n;
+}
+let greaterThan10 = greaterThan(10);
+console.log(greaterThan10(11));
+// → true
+ +

And we can have functions that change other functions.

+ +
function noisy(f) {
+  return (...args) => {
+    console.log("calling with", args);
+    let result = f(...args);
+    console.log("called with", args, ", returned", result);
+    return result;
+  };
+}
+noisy(Math.min)(3, 2, 1);
+// → calling with [3, 2, 1]
+// → called with [3, 2, 1] , returned 1
+ +

We can even write functions that provide new types of control +flow.

+ +
function unless(test, then) {
+  if (!test) then();
+}
+
+repeat(3, n => {
+  unless(n % 2 == 1, () => {
+    console.log(n, "is even");
+  });
+});
+// → 0 is even
+// → 2 is even
+ +

There is a built-in array method, forEach, that provides something like a for/of loop as a higher-order function.

+ +
["A", "B"].forEach(l => console.log(l));
+// → A
+// → B
+ +

Script data set

+ +

One area where higher-order functions shine is data processing. To process data, we’ll need some actual data. This chapter will use a data set about scripts—writing systems such as Latin, Cyrillic, or Arabic.

+ +

Remember Unicode from Chapter 1, the system that assigns a number to each character in written language? Most of these characters are associated with a specific script. The standard contains 140 different scripts—81 are still in use today, and 59 are historic.

+ +

Though I can fluently read only Latin characters, I appreciate the fact that people are writing texts in at least 80 other writing systems, many of which I wouldn’t even recognize. For example, here’s a sample of Tamil handwriting:

Tamil handwriting
+ +

The example data set contains some pieces of information about the 140 scripts defined in Unicode. It is available in the coding sandbox for this chapter as the SCRIPTS binding. The binding contains an array of objects, each of which describes a script.

+ +
{
+  name: "Coptic",
+  ranges: [[994, 1008], [11392, 11508], [11513, 11520]],
+  direction: "ltr",
+  year: -200,
+  living: false,
+  link: "https://en.wikipedia.org/wiki/Coptic_alphabet"
+}
+ +

Such an object tells us the name of the script, the Unicode ranges assigned to it, the direction in which it is written, the (approximate) origin time, whether it is still in use, and a link to more information. The direction may be "ltr" for left to right, "rtl" for right to left (the way Arabic and Hebrew text are written), or "ttb" for top to bottom (as with Mongolian writing).

+ +

The ranges property contains an array of Unicode character ranges, each of which is a two-element array containing a lower bound and an upper bound. Any character codes within these ranges are assigned to the script. The lower bound is inclusive (code 994 is a Coptic character), and the upper bound is non-inclusive (code 1008 isn’t).

+ +

Filtering arrays

+ +

To find the scripts in the data set that are still in use, the following function might be helpful. It filters out the elements in an array that don’t pass a test.

+ +
function filter(array, test) {
+  let passed = [];
+  for (let element of array) {
+    if (test(element)) {
+      passed.push(element);
+    }
+  }
+  return passed;
+}
+
+console.log(filter(SCRIPTS, script => script.living));
+// → [{name: "Adlam", …}, …]
+ +

The function uses the argument named test, a function value, to fill a “gap” in the computation—the process of deciding which elements to collect.

+ +

Note how the filter function, rather than deleting elements from the existing array, builds up a new array with only the elements that pass the test. This function is pure. It does not modify the array it is given.

+ +

Like forEach, filter is a standard array method. The example defined the function only to show what it does internally. From now on, we’ll use it like this instead:

+ +
console.log(SCRIPTS.filter(s => s.direction == "ttb"));
+// → [{name: "Mongolian", …}, …]
+ +

Transforming with map

+ +

Say we have an array of objects representing scripts, produced by filtering the SCRIPTS array somehow. But we want an array of names, which is easier to inspect.

+ +

The map method transforms an array by applying a function to all of its elements and building a new array from the returned values. The new array will have the same length as the input array, but its content will have been mapped to a new form by the function.

+ +
function map(array, transform) {
+  let mapped = [];
+  for (let element of array) {
+    mapped.push(transform(element));
+  }
+  return mapped;
+}
+
+let rtlScripts = SCRIPTS.filter(s => s.direction == "rtl");
+console.log(map(rtlScripts, s => s.name));
+// → ["Adlam", "Arabic", "Imperial Aramaic", …]
+ +

Like forEach and filter, map is a standard array method.

+ +

Summarizing with reduce

+ +

Another common thing to do with arrays is to compute a single value from them. Our recurring example, summing a collection of numbers, is an instance of this. Another example is finding the script with the most characters.

+ +

The higher-order operation that represents this pattern is called reduce (sometimes also called fold). It builds a value by repeatedly taking a single element from the array and combining it with the current value. When summing numbers, you’d start with the number zero and, for each element, add that to the sum.

+ +

The parameters to reduce are, apart from the array, a combining function and a start value. This function is a little less straightforward than filter and map, so take a close look at it:

+ +
function reduce(array, combine, start) {
+  let current = start;
+  for (let element of array) {
+    current = combine(current, element);
+  }
+  return current;
+}
+
+console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0));
+// → 10
+ +

The standard array method reduce, which of course corresponds to this function, has an added convenience. If your array contains at least one element, you are allowed to leave off the start argument. The method will take the first element of the array as its start value and start reducing at the second element.

+ +
console.log([1, 2, 3, 4].reduce((a, b) => a + b));
+// → 10
+ +

To use reduce (twice) to find the script with the most characters, we can write something like this:

+ +
function characterCount(script) {
+  return script.ranges.reduce((count, [from, to]) => {
+    return count + (to - from);
+  }, 0);
+}
+
+console.log(SCRIPTS.reduce((a, b) => {
+  return characterCount(a) < characterCount(b) ? b : a;
+}));
+// → {name: "Han", …}
+ +

The characterCount function reduces the ranges assigned to a script by summing their sizes. Note the use of destructuring in the parameter list of the reducer function. The second call to reduce then uses this to find the largest script by repeatedly comparing two scripts and returning the larger one.

+ +

The Han script has more than 89,000 characters assigned to it in the Unicode standard, making it by far the biggest writing system in the data set. Han is a script (sometimes) used for Chinese, Japanese, and Korean text. Those languages share a lot of characters, though they tend to write them differently. The (U.S.-based) Unicode Consortium decided to treat them as a single writing system to save character codes. This is called Han unification and still makes some people very angry.

+ +

Composability

+ +

Consider how we would have written the previous example (finding the biggest script) without higher-order functions. The code is not that much worse.

+ +
let biggest = null;
+for (let script of SCRIPTS) {
+  if (biggest == null ||
+      characterCount(biggest) < characterCount(script)) {
+    biggest = script;
+  }
+}
+console.log(biggest);
+// → {name: "Han", …}
+ +

There are a few more bindings, and the program is four lines longer. But it is still very readable.

+ +

Higher-order functions start to shine when you need to compose operations. As an example, let’s write code that finds the average year of origin for living and dead scripts in the data set.

+ +
function average(array) {
+  return array.reduce((a, b) => a + b) / array.length;
+}
+
+console.log(Math.round(average(
+  SCRIPTS.filter(s => s.living).map(s => s.year))));
+// → 1165
+console.log(Math.round(average(
+  SCRIPTS.filter(s => !s.living).map(s => s.year))));
+// → 204
+ +

So the dead scripts in Unicode are, on average, older than the living ones. This is not a terribly meaningful or surprising statistic. But I hope you’ll agree that the code used to compute it isn’t hard to read. You can see it as a pipeline: we start with all scripts, filter out the living (or dead) ones, take the years from those, average them, and round the result.

+ +

You could definitely also write this computation as one big loop.

+ +
let total = 0, count = 0;
+for (let script of SCRIPTS) {
+  if (script.living) {
+    total += script.year;
+    count += 1;
+  }
+}
+console.log(Math.round(total / count));
+// → 1165
+ +

But it is harder to see what was being computed and how. And because intermediate results aren’t represented as coherent values, it’d be a lot more work to extract something like average into a separate function.

+ +

In terms of what the computer is actually doing, these two approaches are also quite different. The first will build up new arrays when running filter and map, whereas the second computes only some numbers, doing less work. You can usually afford the readable approach, but if you’re processing huge arrays, and doing so many times, the less abstract style might be worth the extra speed.

+ +

Strings and character codes

+ +

One use of the data set would be figuring out what script a piece of text is using. Let’s go through a program that does this.

+ +

Remember that each script has an array of character code ranges associated with it. So given a character code, we could use a function like this to find the corresponding script (if any):

+ +
function characterScript(code) {
+  for (let script of SCRIPTS) {
+    if (script.ranges.some(([from, to]) => {
+      return code >= from && code < to;
+    })) {
+      return script;
+    }
+  }
+  return null;
+}
+
+console.log(characterScript(121));
+// → {name: "Latin", …}
+ +

The some method is another higher-order function. It takes a test function and tells you whether that function returns true for any of the elements in the array.

+ +

But how do we get the character codes in a string?

+ +

In Chapter 1 I mentioned that JavaScript strings are encoded as a sequence of 16-bit numbers. These are called code +units. A Unicode character code was initially supposed to fit within such a unit (which gives you a little over 65,000 characters). When it became clear that wasn’t going to be enough, many people balked at the need to use more memory per character. To address these concerns, UTF-16, the format used by JavaScript strings, was invented. It describes most common characters using a single 16-bit code unit but uses a pair of two such units for others.

+ +

UTF-16 is generally considered a bad idea today. It seems almost intentionally designed to invite mistakes. It’s easy to write programs that pretend code units and characters are the same thing. And if your language doesn’t use two-unit characters, that will appear to work just fine. But as soon as someone tries to use such a program with some less common Chinese characters, it breaks. Fortunately, with the advent of emoji, everybody has started using two-unit characters, and the burden of dealing with such problems is more fairly distributed.

+ +

Unfortunately, obvious operations on JavaScript strings, such as getting their length through the length property and accessing their content using square brackets, deal only with code units.

+ +
// Two emoji characters, horse and shoe
+let horseShoe = "🐴👟";
+console.log(horseShoe.length);
+// → 4
+console.log(horseShoe[0]);
+// → (Invalid half-character)
+console.log(horseShoe.charCodeAt(0));
+// → 55357 (Code of the half-character)
+console.log(horseShoe.codePointAt(0));
+// → 128052 (Actual code for horse emoji)
+ +

JavaScript’s charCodeAt method gives you a code unit, not a full character code. The codePointAt method, added later, does give a full Unicode character. So we could use that to get characters from a string. But the argument passed to codePointAt is still an index into the sequence of code units. So to run over all characters in a string, we’d still need to deal with the question of whether a character takes up one or two code units.

+ +

In the previous chapter, I mentioned that a for/of loop can also be used on strings. Like codePointAt, this type of loop was introduced at a time where people were acutely aware of the problems with UTF-16. When you use it to loop over a string, it gives you real characters, not code units.

+ +
let roseDragon = "🌹🐉";
+for (let char of roseDragon) {
+  console.log(char);
+}
+// → 🌹
+// → 🐉
+ +

If you have a character (which will be a string of one or two code units), you can use codePointAt(0) to get its code.

+ +

Recognizing text

+ +

We have a characterScript function and a way to correctly loop over characters. The next step is to count the characters that belong to each script. The following counting abstraction will be useful there:

+ +
function countBy(items, groupName) {
+  let counts = [];
+  for (let item of items) {
+    let name = groupName(item);
+    let known = counts.findIndex(c => c.name == name);
+    if (known == -1) {
+      counts.push({name, count: 1});
+    } else {
+      counts[known].count++;
+    }
+  }
+  return counts;
+}
+
+console.log(countBy([1, 2, 3, 4, 5], n => n > 2));
+// → [{name: false, count: 2}, {name: true, count: 3}]
+ +

The countBy function expects a collection (anything that we can loop over with for/of) and a function that computes a group name for a given element. It returns an array of objects, each of which names a group and tells you the number of elements that were found in that group.

+ +

It uses another array method—findIndex. This method is somewhat like indexOf, but instead of looking for a specific value, it finds the first value for which the given function returns true. Like indexOf, it returns -1 when no such element is found.

+ +

Using countBy, we can write the function that tells us which scripts are used in a piece of text.

+ +
function textScripts(text) {
+  let scripts = countBy(text, char => {
+    let script = characterScript(char.codePointAt(0));
+    return script ? script.name : "none";
+  }).filter(({name}) => name != "none");
+
+  let total = scripts.reduce((n, {count}) => n + count, 0);
+  if (total == 0) return "No scripts found";
+
+  return scripts.map(({name, count}) => {
+    return `${Math.round(count * 100 / total)}% ${name}`;
+  }).join(", ");
+}
+
+console.log(textScripts('英国的狗说"woof", 俄罗斯的狗说"тяв"'));
+// → 61% Han, 22% Latin, 17% Cyrillic
+ +

The function first counts the characters by name, using characterScript to assign them a name and falling back to the string "none" for characters that aren’t part of any script. The filter call drops the entry for "none" from the resulting array since we aren’t interested in those characters.

+ +

To be able to compute percentages, we first need the total number of characters that belong to a script, which we can compute with reduce. If no such characters are found, the function returns a specific string. Otherwise, it transforms the counting entries into readable strings with map and then combines them with join.

+ +

Summary

+ +

Being able to pass function values to other functions is a deeply useful aspect of JavaScript. It allows us to write functions that model computations with “gaps” in them. The code that calls these functions can fill in the gaps by providing function values.

+ +

Arrays provide a number of useful higher-order methods. You can use forEach to loop over the elements in an array. The filter method returns a new array containing only the elements that pass the predicate function. Transforming an array by putting each element through a function is done with map. You can use reduce to combine all the elements in an array into a single value. The some method tests whether any element matches a given predicate function. And findIndex finds the position of the first element that matches a predicate.

+ +

Exercises

+ +

Flattening

+ +

Use the reduce method in combination with the concat method to “flatten” an array of arrays into a single array that has all the elements of the original arrays.

+ +
let arrays = [[1, 2, 3], [4, 5], [6]];
+// Your code here.
+// → [1, 2, 3, 4, 5, 6]
+ +

Your own loop

+ +

Write a higher-order function loop that provides something like a for loop statement. It takes a value, a test function, an update function, and a body function. Each iteration, it first runs the test function on the current loop value and stops if that returns false. Then it calls the body function, giving it the current value. Finally, it calls the update function to create a new value and starts from the beginning.

+ +

When defining the function, you can use a regular loop to do the actual looping.

+ +
// Your code here.
+
+loop(3, n => n > 0, n => n - 1, console.log);
+// → 3
+// → 2
+// → 1
+ +

Everything

+ +

Analogous to the some method, arrays also have an every method. This one returns true when the given function returns true for every element in the array. In a way, some is a version of the || operator that acts on arrays, and every is like the && operator.

+ +

Implement every as a function that takes an array and a predicate function as parameters. Write two versions, one using a loop and one using the some method.

+ +
function every(array, test) {
+  // Your code here.
+}
+
+console.log(every([1, 3, 5], n => n < 10));
+// → true
+console.log(every([2, 4, 16], n => n < 10));
+// → false
+console.log(every([], n => n < 10));
+// → true
+ +
+ +

Like the && operator, the every method can stop evaluating further elements as soon as it has found one that doesn’t match. So the loop-based version can jump out of the loop—with break or return—as soon as it runs into an element for which the predicate function returns false. If the loop runs to its end without finding such an element, we know that all elements matched and we should return true.

+ +

To build every on top of some, we can apply De Morgan’s +laws, which state that a && b equals !(!a || !b). This can be generalized to arrays, where all elements in the array match if there is no element in the array that does not match.

+ +
+ +

Dominant writing direction

+ +

Write a function that computes the dominant writing direction in a string of text. Remember that each script object has a direction property that can be "ltr" (left to right), "rtl" (right to left), or "ttb" (top to bottom).

+ +

The dominant direction is the direction of a majority of the characters that have a script associated with them. The characterScript and countBy functions defined earlier in the chapter are probably useful here.

+ +
function dominantDirection(text) {
+  // Your code here.
+}
+
+console.log(dominantDirection("Hello!"));
+// → ltr
+console.log(dominantDirection("Hey, مساء الخير"));
+// → rtl
+ +
+ +

Your solution might look a lot like the first half of the textScripts example. You again have to count characters by a criterion based on characterScript and then filter out the part of the result that refers to uninteresting (script-less) characters.

+ +

Finding the direction with the highest character count can be done with reduce. If it’s not clear how, refer to the example earlier in the chapter, where reduce was used to find the script with the most characters.

+ +
+
diff --git a/docs/06_object.html b/docs/06_object.html new file mode 100644 index 000000000..6d63632f3 --- /dev/null +++ b/docs/06_object.html @@ -0,0 +1,660 @@ + + + + + The Secret Life of Objects :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 6The Secret Life of Objects

+ +
+ +

An abstract data type is realized by writing a special kind of program […] which defines the type in terms of the operations which can be performed on it.

+ +
Barbara Liskov, Programming with Abstract Data Types
+ +
Picture of a rabbit with its proto-rabbit
+ +

Chapter 4 introduced JavaScript’s objects. In programming culture, we have a thing called object-oriented programming, a set of techniques that use objects (and related concepts) as the central principle of program organization.

+ +

Though no one really agrees on its precise definition, object-oriented programming has shaped the design of many programming languages, including JavaScript. This chapter will describe the way these ideas can be applied in JavaScript.

+ +

Encapsulation

+ +

The core idea in object-oriented programming is to divide programs into smaller pieces and make each piece responsible for managing its own state.

+ +

This way, some knowledge about the way a piece of the program works can be kept local to that piece. Someone working on the rest of the program does not have to remember or even be aware of that knowledge. Whenever these local details change, only the code directly around it needs to be updated.

+ +

Different pieces of such a program interact with each other through interfaces, limited sets of functions or bindings that provide useful functionality at a more abstract level, hiding their precise implementation.

+ +

Such program pieces are modeled using objects. Their interface consists of a specific set of methods and properties. Properties that are part of the interface are called public. The others, which outside code should not be touching, are called private.

+ +

Many languages provide a way to distinguish public and private properties and prevent outside code from accessing the private ones altogether. JavaScript, once again taking the minimalist approach, does not—not yet at least. There is work underway to add this to the language.

+ +

Even though the language doesn’t have this distinction built in, JavaScript programmers are successfully using this idea. Typically, the available interface is described in documentation or comments. It is also common to put an underscore (_) character at the start of property names to indicate that those properties are private.

+ +

Separating interface from implementation is a great idea. It is usually called encapsulation.

+ +

Methods

+ +

Methods are nothing more than properties that hold function values. This is a simple method:

+ +
let rabbit = {};
+rabbit.speak = function(line) {
+  console.log(`The rabbit says '${line}'`);
+};
+
+rabbit.speak("I'm alive.");
+// → The rabbit says 'I'm alive.'
+ +

Usually a method needs to do something with the object it was called on. When a function is called as a method—looked up as a property and immediately called, as in object.method()—the binding called this in its body automatically points at the object that it was called on.

+ +
function speak(line) {
+  console.log(`The ${this.type} rabbit says '${line}'`);
+}
+let whiteRabbit = {type: "white", speak};
+let hungryRabbit = {type: "hungry", speak};
+
+whiteRabbit.speak("Oh my ears and whiskers, " +
+                  "how late it's getting!");
+// → The white rabbit says 'Oh my ears and whiskers, how
+//   late it's getting!'
+hungryRabbit.speak("I could use a carrot right now.");
+// → The hungry rabbit says 'I could use a carrot right now.'
+ +

You can think of this as an extra parameter that is passed in a different way. If you want to pass it explicitly, you can use a function’s call method, which takes the this value as its first argument and treats further arguments as normal parameters.

+ +
speak.call(hungryRabbit, "Burp!");
+// → The hungry rabbit says 'Burp!'
+ +

Since each function has its own this binding, whose value depends on the way it is called, you cannot refer to the this of the wrapping scope in a regular function defined with the function keyword.

+ +

Arrow functions are different—they do not bind their own this but can see the this binding of the scope around them. Thus, you can do something like the following code, which references this from inside a local function:

+ +
function normalize() {
+  console.log(this.coords.map(n => n / this.length));
+}
+normalize.call({coords: [0, 2, 3], length: 5});
+// → [0, 0.4, 0.6]
+ +

If I had written the argument to map using the function keyword, the code wouldn’t work.

+ +

Prototypes

+ +

Watch closely.

+ +
let empty = {};
+console.log(empty.toString);
+// → function toString(){…}
+console.log(empty.toString());
+// → [object Object]
+ +

I pulled a property out of an empty object. Magic!

+ +

Well, not really. I have simply been withholding information about the way JavaScript objects work. In addition to their set of properties, most objects also have a prototype. A prototype is another object that is used as a fallback source of properties. When an object gets a request for a property that it does not have, its prototype will be searched for the property, then the prototype’s prototype, and so on.

+ +

So who is the prototype of that empty object? It is the great ancestral prototype, the entity behind almost all objects, Object.prototype.

+ +
console.log(Object.getPrototypeOf({}) ==
+            Object.prototype);
+// → true
+console.log(Object.getPrototypeOf(Object.prototype));
+// → null
+ +

As you guess, Object.getPrototypeOf returns the prototype of an object.

+ +

The prototype relations of JavaScript objects form a tree-shaped structure, and at the root of this structure sits Object.prototype. It provides a few methods that show up in all objects, such as toString, which converts an object to a string representation.

+ +

Many objects don’t directly have Object.prototype as their prototype but instead have another object that provides a different set of default properties. Functions derive from Function.prototype, and arrays derive from Array.prototype.

+ +
console.log(Object.getPrototypeOf(Math.max) ==
+            Function.prototype);
+// → true
+console.log(Object.getPrototypeOf([]) ==
+            Array.prototype);
+// → true
+ +

Such a prototype object will itself have a prototype, often Object.prototype, so that it still indirectly provides methods like toString.

+ +

You can use Object.create to create an object with a specific prototype.

+ +
let protoRabbit = {
+  speak(line) {
+    console.log(`The ${this.type} rabbit says '${line}'`);
+  }
+};
+let killerRabbit = Object.create(protoRabbit);
+killerRabbit.type = "killer";
+killerRabbit.speak("SKREEEE!");
+// → The killer rabbit says 'SKREEEE!'
+ +

A property like speak(line) in an object expression is a shorthand way of defining a method. It creates a property called speak and gives it a function as its value.

+ +

The “proto” rabbit acts as a container for the properties that are shared by all rabbits. An individual rabbit object, like the killer rabbit, contains properties that apply only to itself—in this case its type—and derives shared properties from its prototype.

+ +

Classes

+ +

JavaScript’s prototype system can be interpreted as a somewhat informal take on an object-oriented concept called classes. A class defines the shape of a type of object—what methods and properties it has. Such an object is called an instance of the class.

+ +

Prototypes are useful for defining properties for which all instances of a class share the same value, such as methods. Properties that differ per instance, such as our rabbits’ type property, need to be stored directly in the objects themselves.

+ +

So to create an instance of a given class, you have to make an object that derives from the proper prototype, but you also have to make sure it, itself, has the properties that instances of this class are supposed to have. This is what a constructor function does.

+ +
function makeRabbit(type) {
+  let rabbit = Object.create(protoRabbit);
+  rabbit.type = type;
+  return rabbit;
+}
+ +

JavaScript provides a way to make defining this type of function easier. If you put the keyword new in front of a function call, the function is treated as a constructor. This means that an object with the right prototype is automatically created, bound to this in the function, and returned at the end of the function.

+ +

The prototype object used when constructing objects is found by taking the prototype property of the constructor function.

+ +
function Rabbit(type) {
+  this.type = type;
+}
+Rabbit.prototype.speak = function(line) {
+  console.log(`The ${this.type} rabbit says '${line}'`);
+};
+
+let weirdRabbit = new Rabbit("weird");
+ +

Constructors (all functions, in fact) automatically get a property named prototype, which by default holds a plain, empty object that derives from Object.prototype. You can overwrite it with a new object if you want. Or you can add properties to the existing object, as the example does.

+ +

By convention, the names of constructors are capitalized so that they can easily be distinguished from other functions.

+ +

It is important to understand the distinction between the way a prototype is associated with a constructor (through its prototype property) and the way objects have a prototype (which can be found with Object.getPrototypeOf). The actual prototype of a constructor is Function.prototype since constructors are functions. Its prototype property holds the prototype used for instances created through it.

+ +
console.log(Object.getPrototypeOf(Rabbit) ==
+            Function.prototype);
+// → true
+console.log(Object.getPrototypeOf(weirdRabbit) ==
+            Rabbit.prototype);
+// → true
+ +

Class notation

+ +

So JavaScript classes are constructor functions with a prototype property. That is how they work, and until 2015, that was how you had to write them. These days, we have a less awkward notation.

+ +
class Rabbit {
+  constructor(type) {
+    this.type = type;
+  }
+  speak(line) {
+    console.log(`The ${this.type} rabbit says '${line}'`);
+  }
+}
+
+let killerRabbit = new Rabbit("killer");
+let blackRabbit = new Rabbit("black");
+ +

The class keyword starts a class declaration, which allows us to define a constructor and a set of methods all in a single place. Any number of methods may be written inside the declaration’s braces. The one named constructor is treated specially. It provides the actual constructor function, which will be bound to the name Rabbit. The others are packaged into that constructor’s prototype. Thus, the earlier class declaration is equivalent to the constructor definition from the previous section. It just looks nicer.

+ +

Class declarations currently allow only methods—properties that hold functions—to be added to the prototype. This can be somewhat inconvenient when you want to save a non-function value in there. The next version of the language will probably improve this. For now, you can create such properties by directly manipulating the prototype after you’ve defined the class.

+ +

Like function, class can be used both in statements and in expressions. When used as an expression, it doesn’t define a binding but just produces the constructor as a value. You are allowed to omit the class name in a class expression.

+ +
let object = new class { getWord() { return "hello"; } };
+console.log(object.getWord());
+// → hello
+ +

Overriding derived properties

+ +

When you add a property to an object, whether it is present in the prototype or not, the property is added to the object itself. If there was already a property with the same name in the prototype, this property will no longer affect the object, as it is now hidden behind the object’s own property.

+ +
Rabbit.prototype.teeth = "small";
+console.log(killerRabbit.teeth);
+// → small
+killerRabbit.teeth = "long, sharp, and bloody";
+console.log(killerRabbit.teeth);
+// → long, sharp, and bloody
+console.log(blackRabbit.teeth);
+// → small
+console.log(Rabbit.prototype.teeth);
+// → small
+ +

The following diagram sketches the situation after this code has run. The Rabbit and Object prototypes lie behind killerRabbit as a kind of backdrop, where properties that are not found in the object itself can be looked up.

Rabbit object prototype schema
+ +

Overriding properties that exist in a prototype can be a useful thing to do. As the rabbit teeth example shows, overriding can be used to express exceptional properties in instances of a more generic class of objects, while letting the nonexceptional objects take a standard value from their prototype.

+ +

Overriding is also used to give the standard function and array prototypes a different toString method than the basic object prototype.

+ +
console.log(Array.prototype.toString ==
+            Object.prototype.toString);
+// → false
+console.log([1, 2].toString());
+// → 1,2
+ +

Calling toString on an array gives a result similar to calling .join(",") on it—it puts commas between the values in the array. Directly calling Object.prototype.toString with an array produces a different string. That function doesn’t know about arrays, so it simply puts the word object and the name of the type between square brackets.

+ +
console.log(Object.prototype.toString.call([1, 2]));
+// → [object Array]
+ +

Maps

+ +

We saw the word map used in the previous chapter for an operation that transforms a data structure by applying a function to its elements. Confusing as it is, in programming the same word is also used for a related but rather different thing.

+ +

A map (noun) is a data structure that associates values (the keys) with other values. For example, you might want to map names to ages. It is possible to use objects for this.

+ +
let ages = {
+  Boris: 39,
+  Liang: 22,
+  Júlia: 62
+};
+
+console.log(`Júlia is ${ages["Júlia"]}`);
+// → Júlia is 62
+console.log("Is Jack's age known?", "Jack" in ages);
+// → Is Jack's age known? false
+console.log("Is toString's age known?", "toString" in ages);
+// → Is toString's age known? true
+ +

Here, the object’s property names are the people’s names, and the property values are their ages. But we certainly didn’t list anybody named toString in our map. Yet, because plain objects derive from Object.prototype, it looks like the property is there.

+ +

As such, using plain objects as maps is dangerous. There are several possible ways to avoid this problem. First, it is possible to create objects with no prototype. If you pass null to Object.create, the resulting object will not derive from Object.prototype and can safely be used as a map.

+ +
console.log("toString" in Object.create(null));
+// → false
+ +

Object property names must be strings. If you need a map whose keys can’t easily be converted to strings—such as objects—you cannot use an object as your map.

+ +

Fortunately, JavaScript comes with a class called Map that is written for this exact purpose. It stores a mapping and allows any type of keys.

+ +
let ages = new Map();
+ages.set("Boris", 39);
+ages.set("Liang", 22);
+ages.set("Júlia", 62);
+
+console.log(`Júlia is ${ages.get("Júlia")}`);
+// → Júlia is 62
+console.log("Is Jack's age known?", ages.has("Jack"));
+// → Is Jack's age known? false
+console.log(ages.has("toString"));
+// → false
+ +

The methods set, get, and has are part of the interface of the Map object. Writing a data structure that can quickly update and search a large set of values isn’t easy, but we don’t have to worry about that. Someone else did it for us, and we can go through this simple interface to use their work.

+ +

If you do have a plain object that you need to treat as a map for some reason, it is useful to know that Object.keys returns only an object’s own keys, not those in the prototype. As an alternative to the in operator, you can use the hasOwnProperty method, which ignores the object’s prototype.

+ +
console.log({x: 1}.hasOwnProperty("x"));
+// → true
+console.log({x: 1}.hasOwnProperty("toString"));
+// → false
+ +

Polymorphism

+ +

When you call the String function (which converts a value to a string) on an object, it will call the toString method on that object to try to create a meaningful string from it. I mentioned that some of the standard prototypes define their own version of toString so they can create a string that contains more useful information than "[object Object]". You can also do that yourself.

+ +
Rabbit.prototype.toString = function() {
+  return `a ${this.type} rabbit`;
+};
+
+console.log(String(blackRabbit));
+// → a black rabbit
+ +

This is a simple instance of a powerful idea. When a piece of code is written to work with objects that have a certain interface—in this case, a toString method—any kind of object that happens to support this interface can be plugged into the code, and it will just work.

+ +

This technique is called polymorphism. Polymorphic code can work with values of different shapes, as long as they support the interface it expects.

+ +

I mentioned in Chapter 4 that a for/of loop can loop over several kinds of data structures. This is another case of polymorphism—such loops expect the data structure to expose a specific interface, which arrays and strings do. And we can also add this interface to your own objects! But before we can do that, we need to know what symbols are.

+ +

Symbols

+ +

It is possible for multiple interfaces to use the same property name for different things. For example, I could define an interface in which the toString method is supposed to convert the object into a piece of yarn. It would not be possible for an object to conform to both that interface and the standard use of toString.

+ +

That would be a bad idea, and this problem isn’t that common. Most JavaScript programmers simply don’t think about it. But the language designers, whose job it is to think about this stuff, have provided us with a solution anyway.

+ +

When I claimed that property names are strings, that wasn’t entirely accurate. They usually are, but they can also be symbols. Symbols are values created with the Symbol function. Unlike strings, newly created symbols are unique—you cannot create the same symbol twice.

+ +
let sym = Symbol("name");
+console.log(sym == Symbol("name"));
+// → false
+Rabbit.prototype[sym] = 55;
+console.log(blackRabbit[sym]);
+// → 55
+ +

The string you pass to Symbol is included when you convert it to a string and can make it easier to recognize a symbol when, for example, showing it in the console. But it has no meaning beyond that—multiple symbols may have the same name.

+ +

Being both unique and usable as property names makes symbols suitable for defining interfaces that can peacefully live alongside other properties, no matter what their names are.

+ +
const toStringSymbol = Symbol("toString");
+Array.prototype[toStringSymbol] = function() {
+  return `${this.length} cm of blue yarn`;
+};
+
+console.log([1, 2].toString());
+// → 1,2
+console.log([1, 2][toStringSymbol]());
+// → 2 cm of blue yarn
+ +

It is possible to include symbol properties in object expressions and classes by using square brackets around the property name. That causes the property name to be evaluated, much like the square bracket property access notation, which allows us to refer to a binding that holds the symbol.

+ +
let stringObject = {
+  [toStringSymbol]() { return "a jute rope"; }
+};
+console.log(stringObject[toStringSymbol]());
+// → a jute rope
+ +

The iterator interface

+ +

The object given to a for/of loop is expected to be iterable. This means it has a method named with the Symbol.iterator symbol (a symbol value defined by the language, stored as a property of the Symbol function).

+ +

When called, that method should return an object that provides a second interface, iterator. This is the actual thing that iterates. It has a next method that returns the next result. That result should be an object with a value property that provides the next value, if there is one, and a done property, which should be true when there are no more results and false otherwise.

+ +

Note that the next, value, and done property names are plain strings, not symbols. Only Symbol.iterator, which is likely to be added to a lot of different objects, is an actual symbol.

+ +

We can directly use this interface ourselves.

+ +
let okIterator = "OK"[Symbol.iterator]();
+console.log(okIterator.next());
+// → {value: "O", done: false}
+console.log(okIterator.next());
+// → {value: "K", done: false}
+console.log(okIterator.next());
+// → {value: undefined, done: true}
+ +

Let’s implement an iterable data structure. We’ll build a matrix class, acting as a two-dimensional array.

+ +
class Matrix {
+  constructor(width, height, element = (x, y) => undefined) {
+    this.width = width;
+    this.height = height;
+    this.content = [];
+
+    for (let y = 0; y < height; y++) {
+      for (let x = 0; x < width; x++) {
+        this.content[y * width + x] = element(x, y);
+      }
+    }
+  }
+
+  get(x, y) {
+    return this.content[y * this.width + x];
+  }
+  set(x, y, value) {
+    this.content[y * this.width + x] = value;
+  }
+}
+ +

The class stores its content in a single array of width × height elements. The elements are stored row by row, so, for example, the third element in the fifth row is (using zero-based indexing) stored at position 4 × width + 2.

+ +

The constructor function takes a width, a height, and an optional element function that will be used to fill in the initial values. There are get and set methods to retrieve and update elements in the matrix.

+ +

When looping over a matrix, you are usually interested in the position of the elements as well as the elements themselves, so we’ll have our iterator produce objects with x, y, and value properties.

+ +
class MatrixIterator {
+  constructor(matrix) {
+    this.x = 0;
+    this.y = 0;
+    this.matrix = matrix;
+  }
+
+  next() {
+    if (this.y == this.matrix.height) return {done: true};
+
+    let value = {x: this.x,
+                 y: this.y,
+                 value: this.matrix.get(this.x, this.y)};
+    this.x++;
+    if (this.x == this.matrix.width) {
+      this.x = 0;
+      this.y++;
+    }
+    return {value, done: false};
+  }
+}
+ +

The class tracks the progress of iterating over a matrix in its x and y properties. The next method starts by checking whether the bottom of the matrix has been reached. If it hasn’t, it first creates the object holding the current value and then updates its position, moving to the next row if necessary.

+ +

Let’s set up the Matrix class to be iterable. Throughout this book, I’ll occasionally use after-the-fact prototype manipulation to add methods to classes so that the individual pieces of code remain small and self-contained. In a regular program, where there is no need to split the code into small pieces, you’d declare these methods directly in the class instead.

+ +
Matrix.prototype[Symbol.iterator] = function() {
+  return new MatrixIterator(this);
+};
+ +

We can now loop over a matrix with for/of.

+ +
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
+for (let {x, y, value} of matrix) {
+  console.log(x, y, value);
+}
+// → 0 0 value 0,0
+// → 1 0 value 1,0
+// → 0 1 value 0,1
+// → 1 1 value 1,1
+ +

Getters, setters, and statics

+ +

Interfaces often consist mostly of methods, but it is also okay to include properties that hold non-function values. For example, Map objects have a size property that tells you how many keys are stored in them.

+ +

It is not even necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called getters, and they are defined by writing get in front of the method name in an object expression or class declaration.

+ +
let varyingSize = {
+  get size() {
+    return Math.floor(Math.random() * 100);
+  }
+};
+
+console.log(varyingSize.size);
+// → 73
+console.log(varyingSize.size);
+// → 49
+ +

Whenever someone reads from this object’s size property, the associated method is called. You can do a similar thing when a property is written to, using a setter.

+ +
class Temperature {
+  constructor(celsius) {
+    this.celsius = celsius;
+  }
+  get fahrenheit() {
+    return this.celsius * 1.8 + 32;
+  }
+  set fahrenheit(value) {
+    this.celsius = (value - 32) / 1.8;
+  }
+
+  static fromFahrenheit(value) {
+    return new Temperature((value - 32) / 1.8);
+  }
+}
+
+let temp = new Temperature(22);
+console.log(temp.fahrenheit);
+// → 71.6
+temp.fahrenheit = 86;
+console.log(temp.celsius);
+// → 30
+ +

The Temperature class allows you to read and write the temperature in either degrees Celsius or degrees Fahrenheit, but internally it stores only Celsius and automatically converts to and from Celsius in the fahrenheit getter and setter.

+ +

Sometimes you want to attach some properties directly to your constructor function, rather than to the prototype. Such methods won’t have access to a class instance but can, for example, be used to provide additional ways to create instances.

+ +

Inside a class declaration, methods that have static written before their name are stored on the constructor. So the Temperature class allows you to write Temperature.fromFahrenheit(100) to create a temperature using degrees Fahrenheit.

+ +

Inheritance

+ +

Some matrices are known to be symmetric. If you mirror a symmetric matrix around its top-left-to-bottom-right diagonal, it stays the same. In other words, the value stored at x,y is always the same as that at y,x.

+ +

Imagine we need a data structure like Matrix but one that enforces the fact that the matrix is and remains symmetrical. We could write it from scratch, but that would involve repeating some code very similar to what we already wrote.

+ +

JavaScript’s prototype system makes it possible to create a new class, much like the old class, but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the set method.

+ +

In object-oriented programming terms, this is called inheritance. The new class inherits properties and behavior from the old class.

+ +
class SymmetricMatrix extends Matrix {
+  constructor(size, element = (x, y) => undefined) {
+    super(size, size, (x, y) => {
+      if (x < y) return element(y, x);
+      else return element(x, y);
+    });
+  }
+
+  set(x, y, value) {
+    super.set(x, y, value);
+    if (x != y) {
+      super.set(y, x, value);
+    }
+  }
+}
+
+let matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`);
+console.log(matrix.get(2, 3));
+// → 3,2
+ +

The use of the word extends indicates that this class shouldn’t be directly based on the default Object prototype but on some other class. This is called the superclass. The derived class is the subclass.

+ +

To initialize a SymmetricMatrix instance, the constructor calls its superclass’s constructor through the super keyword. This is necessary because if this new object is to behave (roughly) like a Matrix, it is going to need the instance properties that matrices have. To ensure the matrix is symmetrical, the constructor wraps the element function to swap the coordinates for values below the diagonal.

+ +

The set method again uses super but this time not to call the constructor but to call a specific method from the superclass’s set of methods. We are redefining set but do want to use the original behavior. Because this.set refers to the new set method, calling that wouldn’t work. Inside class methods, super provides a way to call methods as they were defined in the superclass.

+ +

Inheritance allows us to build slightly different data types from existing data types with relatively little work. It is a fundamental part of the object-oriented tradition, alongside encapsulation and polymorphism. But while the latter two are now generally regarded as wonderful ideas, inheritance is more controversial.

+ +

Whereas encapsulation and polymorphism can be used to separate pieces of code from each other, reducing the tangledness of the overall program, inheritance fundamentally ties classes together, creating more tangle. When inheriting from a class, you usually have to know more about how it works than when simply using it. Inheritance can be a useful tool, and I use it now and then in my own programs, but it shouldn’t be the first tool you reach for, and you probably shouldn’t actively go looking for opportunities to construct class hierarchies (family trees of classes).

+ +

The instanceof operator

+ +

It is occasionally useful to know whether an object was derived from a specific class. For this, JavaScript provides a binary operator called instanceof.

+ +
console.log(
+  new SymmetricMatrix(2) instanceof SymmetricMatrix);
+// → true
+console.log(new SymmetricMatrix(2) instanceof Matrix);
+// → true
+console.log(new Matrix(2, 2) instanceof SymmetricMatrix);
+// → false
+console.log([1] instanceof Array);
+// → true
+ +

The operator will see through inherited types, so a SymmetricMatrix is an instance of Matrix. The operator can also be applied to standard constructors like Array. Almost every object is an instance of Object.

+ +

Summary

+ +

So objects do more than just hold their own properties. They have prototypes, which are other objects. They’ll act as if they have properties they don’t have as long as their prototype has that property. Simple objects have Object.prototype as their prototype.

+ +

Constructors, which are functions whose names usually start with a capital letter, can be used with the new operator to create new objects. The new object’s prototype will be the object found in the prototype property of the constructor. You can make good use of this by putting the properties that all values of a given type share into their prototype. There’s a class notation that provides a clear way to define a constructor and its prototype.

+ +

You can define getters and setters to secretly call methods every time an object’s property is accessed. Static methods are methods stored in a class’s constructor, rather than its prototype.

+ +

The instanceof operator can, given an object and a constructor, tell you whether that object is an instance of that constructor.

+ +

One useful thing to do with objects is to specify an interface for them and tell everybody that they are supposed to talk to your object only through that interface. The rest of the details that make up your object are now encapsulated, hidden behind the interface.

+ +

More than one type may implement the same interface. Code written to use an interface automatically knows how to work with any number of different objects that provide the interface. This is called polymorphism.

+ +

When implementing multiple classes that differ in only some details, it can be helpful to write the new classes as subclasses of an existing class, inheriting part of its behavior.

+ +

Exercises

+ +

A vector type

+ +

Write a class Vec that represents a vector in two-dimensional space. It takes x and y parameters (numbers), which it should save to properties of the same name.

+ +

Give the Vec prototype two methods, plus and minus, that take another vector as a parameter and return a new vector that has the sum or difference of the two vectors’ (this and the parameter) x and y values.

+ +

Add a getter property length to the prototype that computes the length of the vector—that is, the distance of the point (x, y) from the origin (0, 0).

+ +
// Your code here.
+
+console.log(new Vec(1, 2).plus(new Vec(2, 3)));
+// → Vec{x: 3, y: 5}
+console.log(new Vec(1, 2).minus(new Vec(2, 3)));
+// → Vec{x: -1, y: -1}
+console.log(new Vec(3, 4).length);
+// → 5
+ +
+ +

Look back to the Rabbit class example if you’re unsure how class declarations look.

+ +

Adding a getter property to the constructor can be done by putting the word get before the method name. To compute the distance from (0, 0) to (x, y), you can use the Pythagorean theorem, which says that the square of the distance we are looking for is equal to the square of the x-coordinate plus the square of the y-coordinate. Thus, √(x2 + y2) is the number you want, and Math.sqrt is the way you compute a square root in JavaScript.

+ +
+ +

Groups

+ +

The standard JavaScript environment provides another data structure called Set. Like an instance of Map, a set holds a collection of values. Unlike Map, it does not associate other values with those—it just tracks which values are part of the set. A value can be part of a set only once—adding it again doesn’t have any effect.

+ +

Write a class called Group (since Set is already taken). Like Set, it has add, delete, and has methods. Its constructor creates an empty group, add adds a value to the group (but only if it isn’t already a member), delete removes its argument from the group (if it was a member), and has returns a Boolean value indicating whether its argument is a member of the group.

+ +

Use the === operator, or something equivalent such as indexOf, to determine whether two values are the same.

+ +

Give the class a static from method that takes an iterable object as argument and creates a group that contains all the values produced by iterating over it.

+ +
class Group {
+  // Your code here.
+}
+
+let group = Group.from([10, 20]);
+console.log(group.has(10));
+// → true
+console.log(group.has(30));
+// → false
+group.add(10);
+group.delete(10);
+console.log(group.has(10));
+// → false
+ +
+ +

The easiest way to do this is to store an array of group members in an instance property. The includes or indexOf methods can be used to check whether a given value is in the array.

+ +

Your class’s constructor can set the member collection to an empty array. When add is called, it must check whether the given value is in the array or add it, for example with push, otherwise.

+ +

Deleting an element from an array, in delete, is less straightforward, but you can use filter to create a new array without the value. Don’t forget to overwrite the property holding the members with the newly filtered version of the array.

+ +

The from method can use a for/of loop to get the values out of the iterable object and call add to put them into a newly created group.

+ +
+ +

Iterable groups

+ +

Make the Group class from the previous exercise iterable. Refer to the section about the iterator interface earlier in the chapter if you aren’t clear on the exact form of the interface anymore.

+ +

If you used an array to represent the group’s members, don’t just return the iterator created by calling the Symbol.iterator method on the array. That would work, but it defeats the purpose of this exercise.

+ +

It is okay if your iterator behaves strangely when the group is modified during iteration.

+ +
// Your code here (and the code from the previous exercise)
+
+for (let value of Group.from(["a", "b", "c"])) {
+  console.log(value);
+}
+// → a
+// → b
+// → c
+ +
+ +

It is probably worthwhile to define a new class GroupIterator. Iterator instances should have a property that tracks the current position in the group. Every time next is called, it checks whether it is done and, if not, moves past the current value and returns it.

+ +

The Group class itself gets a method named by Symbol.iterator that, when called, returns a new instance of the iterator class for that group.

+ +
+ +

Borrowing a method

+ +

Earlier in the chapter I mentioned that an object’s hasOwnProperty can be used as a more robust alternative to the in operator when you want to ignore the prototype’s properties. But what if your map needs to include the word "hasOwnProperty"? You won’t be able to call that method anymore because the object’s own property hides the method value.

+ +

Can you think of a way to call hasOwnProperty on an object that has its own property by that name?

+ +
let map = {one: true, two: true, hasOwnProperty: true};
+
+// Fix this call
+console.log(map.hasOwnProperty("one"));
+// → true
+ +
+ +

Remember that methods that exist on plain objects come from Object.prototype.

+ +

Also remember that you can call a function with a specific this binding by using its call method.

+ +
+
diff --git a/docs/07_robot.html b/docs/07_robot.html new file mode 100644 index 000000000..115bc414b --- /dev/null +++ b/docs/07_robot.html @@ -0,0 +1,383 @@ + + + + + Project: A Robot :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 7Project: A Robot

+ +
+ +

[...] the question of whether Machines Can Think [...] is about as relevant as the question of whether Submarines Can Swim.

+ +
Edsger Dijkstra, The Threats to Computing Science
+ +
Picture of a package-delivery robot
+ +

In “project” chapters, I’ll stop pummeling you with new theory for a brief moment, and instead we’ll work through a program together. Theory is necessary to learn to program, but reading and understanding actual programs is just as important.

+ +

Our project in this chapter is to build an automaton, a little program that performs a task in a virtual world. Our automaton will be a mail-delivery robot picking up and dropping off parcels.

+ +

Meadowfield

+ +

The village of Meadowfield isn’t very big. It consists of 11 places with 14 roads between them. It can be described with this array of roads:

+ +
const roads = [
+  "Alice's House-Bob's House",   "Alice's House-Cabin",
+  "Alice's House-Post Office",   "Bob's House-Town Hall",
+  "Daria's House-Ernie's House", "Daria's House-Town Hall",
+  "Ernie's House-Grete's House", "Grete's House-Farm",
+  "Grete's House-Shop",          "Marketplace-Farm",
+  "Marketplace-Post Office",     "Marketplace-Shop",
+  "Marketplace-Town Hall",       "Shop-Town Hall"
+];
The village of Meadowfield
+ +

The network of roads in the village forms a graph. A graph is a collection of points (places in the village) with lines between them (roads). This graph will be the world that our robot moves through.

+ +

The array of strings isn’t very easy to work with. What we’re interested in is the destinations that we can reach from a given place. Let’s convert the list of roads to a data structure that, for each place, tells us what can be reached from there.

+ +
function buildGraph(edges) {
+  let graph = Object.create(null);
+  function addEdge(from, to) {
+    if (graph[from] == null) {
+      graph[from] = [to];
+    } else {
+      graph[from].push(to);
+    }
+  }
+  for (let [from, to] of edges.map(r => r.split("-"))) {
+    addEdge(from, to);
+    addEdge(to, from);
+  }
+  return graph;
+}
+
+const roadGraph = buildGraph(roads);
+ +

Given an array of edges, buildGraph creates a map object that, for each node, stores an array of connected nodes.

+ +

It uses the split method to go from the road strings, which have the form "Start-End", to two-element arrays containing the start and end as separate strings.

+ +

The task

+ +

Our robot will be moving around the village. There are parcels in various places, each addressed to some other place. The robot picks up parcels when it comes to them and delivers them when it arrives at their destinations.

+ +

The automaton must decide, at each point, where to go next. It has finished its task when all parcels have been delivered.

+ +

To be able to simulate this process, we must define a virtual world that can describe it. This model tells us where the robot is and where the parcels are. When the robot has decided to move somewhere, we need to update the model to reflect the new situation.

+ +

If you’re thinking in terms of object-oriented programming, your first impulse might be to start defining objects for the various elements in the world: a class for the robot, one for a parcel, maybe one for places. These could then hold properties that describe their current state, such as the pile of parcels at a location, which we could change when updating the world.

+ +

This is wrong.

+ +

At least, it usually is. The fact that something sounds like an object does not automatically mean that it should be an object in your program. Reflexively writing classes for every concept in your application tends to leave you with a collection of interconnected objects that each have their own internal, changing state. Such programs are often hard to understand and thus easy to break.

+ +

Instead, let’s condense the village’s state down to the minimal set of values that define it. There’s the robot’s current location and the collection of undelivered parcels, each of which has a current location and a destination address. That’s it.

+ +

And while we’re at it, let’s make it so that we don’t change this state when the robot moves but rather compute a new state for the situation after the move.

+ +
class VillageState {
+  constructor(place, parcels) {
+    this.place = place;
+    this.parcels = parcels;
+  }
+
+  move(destination) {
+    if (!roadGraph[this.place].includes(destination)) {
+      return this;
+    } else {
+      let parcels = this.parcels.map(p => {
+        if (p.place != this.place) return p;
+        return {place: destination, address: p.address};
+      }).filter(p => p.place != p.address);
+      return new VillageState(destination, parcels);
+    }
+  }
+}
+ +

The move method is where the action happens. It first checks whether there is a road going from the current place to the destination, and if not, it returns the old state since this is not a valid move.

+ +

Then it creates a new state with the destination as the robot’s new place. But it also needs to create a new set of parcels—parcels that the robot is carrying (that are at the robot’s current place) need to be moved along to the new place. And parcels that are addressed to the new place need to be delivered—that is, they need to be removed from the set of undelivered parcels. The call to map takes care of the moving, and the call to filter does the delivering.

+ +

Parcel objects aren’t changed when they are moved but re-created. The move method gives us a new village state but leaves the old one entirely intact.

+ +
let first = new VillageState(
+  "Post Office",
+  [{place: "Post Office", address: "Alice's House"}]
+);
+let next = first.move("Alice's House");
+
+console.log(next.place);
+// → Alice's House
+console.log(next.parcels);
+// → []
+console.log(first.place);
+// → Post Office
+ +

The move causes the parcel to be delivered, and this is reflected in the next state. But the initial state still describes the situation where the robot is at the post office and the parcel is undelivered.

+ +

Persistent data

+ +

Data structures that don’t change are called immutable or persistent. They behave a lot like strings and numbers in that they are who they are and stay that way, rather than containing different things at different times.

+ +

In JavaScript, just about everything can be changed, so working with values that are supposed to be persistent requires some restraint. There is a function called Object.freeze that changes an object so that writing to its properties is ignored. You could use that to make sure your objects aren’t changed, if you want to be careful. Freezing does require the computer to do some extra work, and having updates ignored is just about as likely to confuse someone as having them do the wrong thing. So I usually prefer to just tell people that a given object shouldn’t be messed with and hope they remember it.

+ +
let object = Object.freeze({value: 5});
+object.value = 10;
+console.log(object.value);
+// → 5
+ +

Why am I going out of my way to not change objects when the language is obviously expecting me to?

+ +

Because it helps me understand my programs. This is about complexity management again. When the objects in my system are fixed, stable things, I can consider operations on them in isolation—moving to Alice’s house from a given start state always produces the same new state. When objects change over time, that adds a whole new dimension of complexity to this kind of reasoning.

+ +

For a small system like the one we are building in this chapter, we could handle that bit of extra complexity. But the most important limit on what kind of systems we can build is how much we can understand. Anything that makes your code easier to understand makes it possible to build a more ambitious system.

+ +

Unfortunately, although understanding a system built on persistent data structures is easier, designing one, especially when your programming language isn’t helping, can be a little harder. We’ll look for opportunities to use persistent data structures in this book, but we’ll also be using changeable ones.

+ +

Simulation

+ +

A delivery robot looks at the world and decides in which direction it wants to move. As such, we could say that a robot is a function that takes a VillageState object and returns the name of a nearby place.

+ +

Because we want robots to be able to remember things, so that they can make and execute plans, we also pass them their memory and allow them to return a new memory. Thus, the thing a robot returns is an object containing both the direction it wants to move in and a memory value that will be given back to it the next time it is called.

+ +
function runRobot(state, robot, memory) {
+  for (let turn = 0;; turn++) {
+    if (state.parcels.length == 0) {
+      console.log(`Done in ${turn} turns`);
+      break;
+    }
+    let action = robot(state, memory);
+    state = state.move(action.direction);
+    memory = action.memory;
+    console.log(`Moved to ${action.direction}`);
+  }
+}
+ +

Consider what a robot has to do to “solve” a given state. It must pick up all parcels by visiting every location that has a parcel and deliver them by visiting every location that a parcel is addressed to, but only after picking up the parcel.

+ +

What is the dumbest strategy that could possibly work? The robot could just walk in a random direction every turn. That means, with great likelihood, it will eventually run into all parcels and then also at some point reach the place where they should be delivered.

+ +

Here’s what that could look like:

+ +
function randomPick(array) {
+  let choice = Math.floor(Math.random() * array.length);
+  return array[choice];
+}
+
+function randomRobot(state) {
+  return {direction: randomPick(roadGraph[state.place])};
+}
+ +

Remember that Math.random() returns a number between zero and one—but always below one. Multiplying such a number by the length of an array and then applying Math.floor to it gives us a random index for the array.

+ +

Since this robot does not need to remember anything, it ignores its second argument (remember that JavaScript functions can be called with extra arguments without ill effects) and omits the memory property in its returned object.

+ +

To put this sophisticated robot to work, we’ll first need a way to create a new state with some parcels. A static method (written here by directly adding a property to the constructor) is a good place to put that functionality.

+ +
VillageState.random = function(parcelCount = 5) {
+  let parcels = [];
+  for (let i = 0; i < parcelCount; i++) {
+    let address = randomPick(Object.keys(roadGraph));
+    let place;
+    do {
+      place = randomPick(Object.keys(roadGraph));
+    } while (place == address);
+    parcels.push({place, address});
+  }
+  return new VillageState("Post Office", parcels);
+};
+ +

We don’t want any parcels that are sent from the same place that they are addressed to. For this reason, the do loop keeps picking new places when it gets one that’s equal to the address.

+ +

Let’s start up a virtual world.

+ +
runRobot(VillageState.random(), randomRobot);
+// → Moved to Marketplace
+// → Moved to Town Hall
+// → …
+// → Done in 63 turns
+ +

It takes the robot a lot of turns to deliver the parcels because it isn’t planning ahead very well. We’ll address that soon.

+ +

For a more pleasant perspective on the simulation, you can use the runRobotAnimation function that’s available in this chapter’s programming environment. This runs the simulation, but instead of outputting text, it shows you the robot moving around the village map.

+ +
runRobotAnimation(VillageState.random(), randomRobot);
+ +

The way runRobotAnimation is implemented will remain a mystery for now, but after you’ve read the later chapters of this book, which discuss JavaScript integration in web browsers, you’ll be able to guess how it works.

+ +

The mail truck’s route

+ +

We should be able to do a lot better than the random robot. An easy improvement would be to take a hint from the way real-world mail delivery works. If we find a route that passes all places in the village, the robot could run that route twice, at which point it is guaranteed to be done. Here is one such route (starting from the post office):

+ +
const mailRoute = [
+  "Alice's House", "Cabin", "Alice's House", "Bob's House",
+  "Town Hall", "Daria's House", "Ernie's House",
+  "Grete's House", "Shop", "Grete's House", "Farm",
+  "Marketplace", "Post Office"
+];
+ +

To implement the route-following robot, we’ll need to make use of robot memory. The robot keeps the rest of its route in its memory and drops the first element every turn.

+ +
function routeRobot(state, memory) {
+  if (memory.length == 0) {
+    memory = mailRoute;
+  }
+  return {direction: memory[0], memory: memory.slice(1)};
+}
+ +

This robot is a lot faster already. It’ll take a maximum of 26 turns (twice the 13-step route) but usually less.

+ +
runRobotAnimation(VillageState.random(), routeRobot, []);
+ +

Pathfinding

+ +

Still, I wouldn’t really call blindly following a fixed route intelligent behavior. The robot could work more efficiently if it adjusted its behavior to the actual work that needs to be done.

+ +

To do that, it has to be able to deliberately move toward a given parcel or toward the location where a parcel has to be delivered. Doing that, even when the goal is more than one move away, will require some kind of route-finding function.

+ +

The problem of finding a route through a graph is a typical search problem. We can tell whether a given solution (a route) is a valid solution, but we can’t directly compute the solution the way we could for 2 + 2. Instead, we have to keep creating potential solutions until we find one that works.

+ +

The number of possible routes through a graph is infinite. But when searching for a route from A to B, we are interested only in the ones that start at A. We also don’t care about routes that visit the same place twice—those are definitely not the most efficient route anywhere. So that cuts down on the number of routes that the route finder has to consider.

+ +

In fact, we are mostly interested in the shortest route. So we want to make sure we look at short routes before we look at longer ones. A good approach would be to “grow” routes from the starting point, exploring every reachable place that hasn’t been visited yet, until a route reaches the goal. That way, we’ll only explore routes that are potentially interesting, and we’ll find the shortest route (or one of the shortest routes, if there are more than one) to the goal.

+ +

Here is a function that does this:

+ +
function findRoute(graph, from, to) {
+  let work = [{at: from, route: []}];
+  for (let i = 0; i < work.length; i++) {
+    let {at, route} = work[i];
+    for (let place of graph[at]) {
+      if (place == to) return route.concat(place);
+      if (!work.some(w => w.at == place)) {
+        work.push({at: place, route: route.concat(place)});
+      }
+    }
+  }
+}
+ +

The exploring has to be done in the right order—the places that were reached first have to be explored first. We can’t immediately explore a place as soon as we reach it because that would mean places reached from there would also be explored immediately, and so on, even though there may be other, shorter paths that haven’t yet been explored.

+ +

Therefore, the function keeps a work list. This is an array of places that should be explored next, along with the route that got us there. It starts with just the start position and an empty route.

+ +

The search then operates by taking the next item in the list and exploring that, which means all roads going from that place are looked at. If one of them is the goal, a finished route can be returned. Otherwise, if we haven’t looked at this place before, a new item is added to the list. If we have looked at it before, since we are looking at short routes first, we’ve found either a longer route to that place or one precisely as long as the existing one, and we don’t need to explore it.

+ +

You can visually imagine this as a web of known routes crawling out from the start location, growing evenly on all sides (but never tangling back into itself). As soon as the first thread reaches the goal location, that thread is traced back to the start, giving us our route.

+ +

Our code doesn’t handle the situation where there are no more work items on the work list because we know that our graph is connected, meaning that every location can be reached from all other locations. We’ll always be able to find a route between two points, and the search can’t fail.

+ +
function goalOrientedRobot({place, parcels}, route) {
+  if (route.length == 0) {
+    let parcel = parcels[0];
+    if (parcel.place != place) {
+      route = findRoute(roadGraph, place, parcel.place);
+    } else {
+      route = findRoute(roadGraph, place, parcel.address);
+    }
+  }
+  return {direction: route[0], memory: route.slice(1)};
+}
+ +

This robot uses its memory value as a list of directions to move in, just like the route-following robot. Whenever that list is empty, it has to figure out what to do next. It takes the first undelivered parcel in the set and, if that parcel hasn’t been picked up yet, plots a route toward it. If the parcel has been picked up, it still needs to be delivered, so the robot creates a route toward the delivery address instead.

+ +

Let’s see how it does.

+ +
runRobotAnimation(VillageState.random(),
+                  goalOrientedRobot, []);
+ +

This robot usually finishes the task of delivering 5 parcels in about 16 turns. That’s slightly better than routeRobot but still definitely not optimal.

+ +

Exercises

+ +

Measuring a robot

+ +

It’s hard to objectively compare robots by just letting them solve a few scenarios. Maybe one robot just happened to get easier tasks or the kind of tasks that it is good at, whereas the other didn’t.

+ +

Write a function compareRobots that takes two robots (and their starting memory). It should generate 100 tasks and let each of the robots solve each of these tasks. When done, it should output the average number of steps each robot took per task.

+ +

For the sake of fairness, make sure you give each task to both robots, rather than generating different tasks per robot.

+ +
function compareRobots(robot1, memory1, robot2, memory2) {
+  // Your code here
+}
+
+compareRobots(routeRobot, [], goalOrientedRobot, []);
+ +
+ +

You’ll have to write a variant of the runRobot function that, instead of logging the events to the console, returns the number of steps the robot took to complete the task.

+ +

Your measurement function can then, in a loop, generate new states and count the steps each of the robots takes. When it has generated enough measurements, it can use console.log to output the average for each robot, which is the total number of steps taken divided by the number of measurements.

+ +
+ +

Robot efficiency

+ +

Can you write a robot that finishes the delivery task faster than goalOrientedRobot? If you observe that robot’s behavior, what obviously stupid things does it do? How could those be improved?

+ +

If you solved the previous exercise, you might want to use your compareRobots function to verify whether you improved the robot.

+ +
// Your code here
+
+runRobotAnimation(VillageState.random(), yourRobot, memory);
+ +
+ +

The main limitation of goalOrientedRobot is that it considers only one parcel at a time. It will often walk back and forth across the village because the parcel it happens to be looking at happens to be at the other side of the map, even if there are others much closer.

+ +

One possible solution would be to compute routes for all packages and then take the shortest one. Even better results can be obtained, if there are multiple shortest routes, by preferring the ones that go to pick up a package instead of delivering a package.

+ +
+ +

Persistent group

+ +

Most data structures provided in a standard JavaScript environment aren’t very well suited for persistent use. Arrays have slice and concat methods, which allow us to easily create new arrays without damaging the old one. But Set, for example, has no methods for creating a new set with an item added or removed.

+ +

Write a new class PGroup, similar to the Group class from Chapter 6, which stores a set of values. Like Group, it has add, delete, and has methods.

+ +

Its add method, however, should return a new PGroup instance with the given member added and leave the old one unchanged. Similarly, delete creates a new instance without a given member.

+ +

The class should work for values of any type, not just strings. It does not have to be efficient when used with large amounts of values.

+ +

The constructor shouldn’t be part of the class’s interface (though you’ll definitely want to use it internally). Instead, there is an empty instance, PGroup.empty, that can be used as a starting value.

+ +

Why do you need only one PGroup.empty value, rather than having a function that creates a new, empty map every time?

+ +
class PGroup {
+  // Your code here
+}
+
+let a = PGroup.empty.add("a");
+let ab = a.add("b");
+let b = ab.delete("a");
+
+console.log(b.has("b"));
+// → true
+console.log(a.has("b"));
+// → false
+console.log(b.has("a"));
+// → false
+ +
+ +

The most convenient way to represent the set of member values is still as an array since arrays are easy to copy.

+ +

When a value is added to the group, you can create a new group with a copy of the original array that has the value added (for example, using concat). When a value is deleted, you filter it from the array.

+ +

The class’s constructor can take such an array as argument and store it as the instance’s (only) property. This array is never updated.

+ +

To add a property (empty) to a constructor that is not a method, you have to add it to the constructor after the class definition, as a regular property.

+ +

You need only one empty instance because all empty groups are the same and instances of the class don’t change. You can create many different groups from that single empty group without affecting it.

+ +
+
diff --git a/docs/08_error.html b/docs/08_error.html new file mode 100644 index 000000000..109e7120b --- /dev/null +++ b/docs/08_error.html @@ -0,0 +1,485 @@ + + + + + Bugs and Errors :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 8Bugs and Errors

+ +
+ +

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

+ +
Brian Kernighan and P.J. Plauger, The Elements of Programming Style
+ +
Picture of a collection of bugs
+ +

Flaws in computer programs are usually called bugs. It makes programmers feel good to imagine them as little things that just happen to crawl into our work. In reality, of course, we put them there ourselves.

+ +

If a program is crystallized thought, you can roughly categorize bugs into those caused by the thoughts being confused and those caused by mistakes introduced while converting a thought to code. The former type is generally harder to diagnose and fix than the latter.

+ +

Language

+ +

Many mistakes could be pointed out to us automatically by the computer, if it knew enough about what we’re trying to do. But here JavaScript’s looseness is a hindrance. Its concept of bindings and properties is vague enough that it will rarely catch typos before actually running the program. And even then, it allows you to do some clearly nonsensical things without complaint, such as computing true * "monkey".

+ +

There are some things that JavaScript does complain about. Writing a program that does not follow the language’s grammar will immediately make the computer complain. Other things, such as calling something that’s not a function or looking up a property on an undefined value, will cause an error to be reported when the program tries to perform the action.

+ +

But often, your nonsense computation will merely produce NaN (not a number) or an undefined value, while the program happily continues, convinced that it’s doing something meaningful. The mistake will manifest itself only later, after the bogus value has traveled through several functions. It might not trigger an error at all but silently cause the program’s output to be wrong. Finding the source of such problems can be difficult.

+ +

The process of finding mistakes—bugs—in programs is called debugging.

+ +

Strict mode

+ +

JavaScript can be made a little stricter by enabling strict mode. This is done by putting the string "use strict" at the top of a file or a function body. Here’s an example:

+ +
function canYouSpotTheProblem() {
+  "use strict";
+  for (counter = 0; counter < 10; counter++) {
+    console.log("Happy happy");
+  }
+}
+
+canYouSpotTheProblem();
+// → ReferenceError: counter is not defined
+ +

Normally, when you forget to put let in front of your binding, as with counter in the example, JavaScript quietly creates a global binding and uses that. In strict mode, an error is reported instead. This is very helpful. It should be noted, though, that this doesn’t work when the binding in question already exists as a global binding. In that case, the loop will still quietly overwrite the value of the binding.

+ +

Another change in strict mode is that the this binding holds the value undefined in functions that are not called as methods. When making such a call outside of strict mode, this refers to the global scope object, which is an object whose properties are the global bindings. So if you accidentally call a method or constructor incorrectly in strict mode, JavaScript will produce an error as soon as it tries to read something from this, rather than happily writing to the global scope.

+ +

For example, consider the following code, which calls a constructor function without the new keyword so that its this will not refer to a newly constructed object:

+ +
function Person(name) { this.name = name; }
+let ferdinand = Person("Ferdinand"); // oops
+console.log(name);
+// → Ferdinand
+ +

So the bogus call to Person succeeded but returned an undefined value and created the global binding name. In strict mode, the result is different.

+ +
"use strict";
+function Person(name) { this.name = name; }
+let ferdinand = Person("Ferdinand"); // forgot new
+// → TypeError: Cannot set property 'name' of undefined
+ +

We are immediately told that something is wrong. This is helpful.

+ +

Fortunately, constructors created with the class notation will always complain if they are called without new, making this less of a problem even in non-strict mode.

+ +

Strict mode does a few more things. It disallows giving a function multiple parameters with the same name and removes certain problematic language features entirely (such as the with statement, which is so wrong it is not further discussed in this book).

+ +

In short, putting "use strict" at the top of your program rarely hurts and might help you spot a problem.

+ +

Types

+ +

Some languages want to know the types of all your bindings and expressions before even running a program. They will tell you right away when a type is used in an inconsistent way. JavaScript considers types only when actually running the program, and even there often tries to implicitly convert values to the type it expects, so it’s not much help.

+ +

Still, types provide a useful framework for talking about programs. A lot of mistakes come from being confused about the kind of value that goes into or comes out of a function. If you have that information written down, you’re less likely to get confused.

+ +

You could add a comment like the following before the goalOrientedRobot function from the previous chapter to describe its type:

+ +
// (VillageState, Array) → {direction: string, memory: Array}
+function goalOrientedRobot(state, memory) {
+  // ...
+}
+ +

There are a number of different conventions for annotating JavaScript programs with types.

+ +

One thing about types is that they need to introduce their own complexity to be able to describe enough code to be useful. What do you think would be the type of the randomPick function that returns a random element from an array? You’d need to introduce a type +variable, T, which can stand in for any type, so that you can give randomPick a type like ([T]) → T (function from an array of Ts to a T).

+ +

When the types of a program are known, it is possible for the computer to check them for you, pointing out mistakes before the program is run. There are several JavaScript dialects that add types to the language and check them. The most popular one is called TypeScript. If you are interested in adding more rigor to your programs, I recommend you give it a try.

+ +

In this book, we’ll continue using raw, dangerous, untyped JavaScript code.

+ +

Testing

+ +

If the language is not going to do much to help us find mistakes, we’ll have to find them the hard way: by running the program and seeing whether it does the right thing.

+ +

Doing this by hand, again and again, is a really bad idea. Not only is it annoying, it also tends to be ineffective since it takes too much time to exhaustively test everything every time you make a change.

+ +

Computers are good at repetitive tasks, and testing is the ideal repetitive task. Automated testing is the process of writing a program that tests another program. Writing tests is a bit more work than testing manually, but once you’ve done it, you gain a kind of superpower: it takes you only a few seconds to verify that your program still behaves properly in all the situations you wrote tests for. When you break something, you’ll immediately notice, rather than randomly running into it at some later time.

+ +

Tests usually take the form of little labeled programs that verify some aspect of your code. For example, a set of tests for the (standard, probably already tested by someone else) toUpperCase method might look like this:

+ +
function test(label, body) {
+  if (!body()) console.log(`Failed: ${label}`);
+}
+
+test("convert Latin text to uppercase", () => {
+  return "hello".toUpperCase() == "HELLO";
+});
+test("convert Greek text to uppercase", () => {
+  return "Χαίρετε".toUpperCase() == "ΧΑΊΡΕΤΕ";
+});
+test("don't convert case-less characters", () => {
+  return "مرحبا".toUpperCase() == "مرحبا";
+});
+ +

Writing tests like this tends to produce rather repetitive, awkward code. Fortunately, there exist pieces of software that help you build and run collections of tests (test suites) by providing a language (in the form of functions and methods) suited to expressing tests and by outputting informative information when a test fails. These are usually called test runners.

+ +

Some code is easier to test than other code. Generally, the more external objects that the code interacts with, the harder it is to set up the context in which to test it. The style of programming shown in the previous chapter, which uses self-contained persistent values rather than changing objects, tends to be easy to test.

+ +

Debugging

+ +

Once you notice there is something wrong with your program because it misbehaves or produces errors, the next step is to figure out what the problem is.

+ +

Sometimes it is obvious. The error message will point at a specific line of your program, and if you look at the error description and that line of code, you can often see the problem.

+ +

But not always. Sometimes the line that triggered the problem is simply the first place where a flaky value produced elsewhere gets used in an invalid way. If you have been solving the exercises in earlier chapters, you will probably have already experienced such situations.

+ +

The following example program tries to convert a whole number to a string in a given base (decimal, binary, and so on) by repeatedly picking out the last digit and then dividing the number to get rid of this digit. But the strange output that it currently produces suggests that it has a bug.

+ +
function numberToString(n, base = 10) {
+  let result = "", sign = "";
+  if (n < 0) {
+    sign = "-";
+    n = -n;
+  }
+  do {
+    result = String(n % base) + result;
+    n /= base;
+  } while (n > 0);
+  return sign + result;
+}
+console.log(numberToString(13, 10));
+// → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…
+ +

Even if you see the problem already, pretend for a moment that you don’t. We know that our program is malfunctioning, and we want to find out why.

+ +

This is where you must resist the urge to start making random changes to the code to see whether that makes it better. Instead, think. Analyze what is happening and come up with a theory of why it might be happening. Then, make additional observations to test this theory—or, if you don’t yet have a theory, make additional observations to help you come up with one.

+ +

Putting a few strategic console.log calls into the program is a good way to get additional information about what the program is doing. In this case, we want n to take the values 13, 1, and then 0. Let’s write out its value at the start of the loop.

+ +
13
+1.3
+0.13
+0.013
+…
+1.5e-323
+ +

Right. Dividing 13 by 10 does not produce a whole number. Instead of n /= base, what we actually want is n = Math.floor(n / base) so that the number is properly “shifted” to the right.

+ +

An alternative to using console.log to peek into the program’s behavior is to use the debugger capabilities of your browser. Browsers come with the ability to set a breakpoint on a specific line of your code. When the execution of the program reaches a line with a breakpoint, it is paused, and you can inspect the values of bindings at that point. I won’t go into details, as debuggers differ from browser to browser, but look in your browser’s developer +tools or search the Web for more information.

+ +

Another way to set a breakpoint is to include a debugger statement (consisting of simply that keyword) in your program. If the developer tools of your browser are active, the program will pause whenever it reaches such a statement.

+ +

Error propagation

+ +

Not all problems can be prevented by the programmer, unfortunately. If your program communicates with the outside world in any way, it is possible to get malformed input, to become overloaded with work, or to have the network fail.

+ +

If you’re programming only for yourself, you can afford to just ignore such problems until they occur. But if you build something that is going to be used by anybody else, you usually want the program to do better than just crash. Sometimes the right thing to do is take the bad input in stride and continue running. In other cases, it is better to report to the user what went wrong and then give up. But in either situation, the program has to actively do something in response to the problem.

+ +

Say you have a function promptNumber that asks the user for a number and returns it. What should it return if the user inputs “orange”?

+ +

One option is to make it return a special value. Common choices for such values are null, undefined, or -1.

+ +
function promptNumber(question) {
+  let result = Number(prompt(question));
+  if (Number.isNaN(result)) return null;
+  else return result;
+}
+
+console.log(promptNumber("How many trees do you see?"));
+ +

Now any code that calls promptNumber must check whether an actual number was read and, failing that, must somehow recover—maybe by asking again or by filling in a default value. Or it could again return a special value to its caller to indicate that it failed to do what it was asked.

+ +

In many situations, mostly when errors are common and the caller should be explicitly taking them into account, returning a special value is a good way to indicate an error. It does, however, have its downsides. First, what if the function can already return every possible kind of value? In such a function, you’ll have to do something like wrap the result in an object to be able to distinguish success from failure.

+ +
function lastElement(array) {
+  if (array.length == 0) {
+    return {failed: true};
+  } else {
+    return {element: array[array.length - 1]};
+  }
+}
+ +

The second issue with returning special values is that it can lead to awkward code. If a piece of code calls promptNumber 10 times, it has to check 10 times whether null was returned. And if its response to finding null is to simply return null itself, callers of the function will in turn have to check for it, and so on.

+ +

Exceptions

+ +

When a function cannot proceed normally, what we would like to do is just stop what we are doing and immediately jump to a place that knows how to handle the problem. This is what exception handling does.

+ +

Exceptions are a mechanism that makes it possible for code that runs into a problem to raise (or throw) an exception. An exception can be any value. Raising one somewhat resembles a super-charged return from a function: it jumps out of not just the current function but also its callers, all the way down to the first call that started the current execution. This is called unwinding the +stack. You may remember the stack of function calls that was mentioned in Chapter 3. An exception zooms down this stack, throwing away all the call contexts it encounters.

+ +

If exceptions always zoomed right down to the bottom of the stack, they would not be of much use. They’d just provide a novel way to blow up your program. Their power lies in the fact that you can set “obstacles” along the stack to catch the exception as it is zooming down. Once you’ve caught an exception, you can do something with it to address the problem and then continue to run the program.

+ +

Here’s an example:

+ +
function promptDirection(question) {
+  let result = prompt(question);
+  if (result.toLowerCase() == "left") return "L";
+  if (result.toLowerCase() == "right") return "R";
+  throw new Error("Invalid direction: " + result);
+}
+
+function look() {
+  if (promptDirection("Which way?") == "L") {
+    return "a house";
+  } else {
+    return "two angry bears";
+  }
+}
+
+try {
+  console.log("You see", look());
+} catch (error) {
+  console.log("Something went wrong: " + error);
+}
+ +

The throw keyword is used to raise an exception. Catching one is done by wrapping a piece of code in a try block, followed by the keyword catch. When the code in the try block causes an exception to be raised, the catch block is evaluated, with the name in parentheses bound to the exception value. After the catch block finishes—or if the try block finishes without problems—the program proceeds beneath the entire try/catch statement.

+ +

In this case, we used the Error constructor to create our exception value. This is a standard JavaScript constructor that creates an object with a message property. In most JavaScript environments, instances of this constructor also gather information about the call stack that existed when the exception was created, a so-called stack trace. This information is stored in the stack property and can be helpful when trying to debug a problem: it tells us the function where the problem occurred and which functions made the failing call.

+ +

Note that the look function completely ignores the possibility that promptDirection might go wrong. This is the big advantage of exceptions: error-handling code is necessary only at the point where the error occurs and at the point where it is handled. The functions in between can forget all about it.

+ +

Well, almost...

+ +

Cleaning up after exceptions

+ +

The effect of an exception is another kind of control flow. Every action that might cause an exception, which is pretty much every function call and property access, might cause control to suddenly leave your code.

+ +

This means when code has several side effects, even if its “regular” control flow looks like they’ll always all happen, an exception might prevent some of them from taking place.

+ +

Here is some really bad banking code.

+ +
const accounts = {
+  a: 100,
+  b: 0,
+  c: 20
+};
+
+function getAccount() {
+  let accountName = prompt("Enter an account name");
+  if (!accounts.hasOwnProperty(accountName)) {
+    throw new Error(`No such account: ${accountName}`);
+  }
+  return accountName;
+}
+
+function transfer(from, amount) {
+  if (accounts[from] < amount) return;
+  accounts[from] -= amount;
+  accounts[getAccount()] += amount;
+}
+ +

The transfer function transfers a sum of money from a given account to another, asking for the name of the other account in the process. If given an invalid account name, getAccount throws an exception.

+ +

But transfer first removes the money from the account and then calls getAccount before it adds it to another account. If it is broken off by an exception at that point, it’ll just make the money disappear.

+ +

That code could have been written a little more intelligently, for example by calling getAccount before it starts moving money around. But often problems like this occur in more subtle ways. Even functions that don’t look like they will throw an exception might do so in exceptional circumstances or when they contain a programmer mistake.

+ +

One way to address this is to use fewer side effects. Again, a programming style that computes new values instead of changing existing data helps. If a piece of code stops running in the middle of creating a new value, no one ever sees the half-finished value, and there is no problem.

+ +

But that isn’t always practical. So there is another feature that try statements have. They may be followed by a finally block either instead of or in addition to a catch block. A finally block says “no matter what happens, run this code after trying to run the code in the try block.”

+ +
function transfer(from, amount) {
+  if (accounts[from] < amount) return;
+  let progress = 0;
+  try {
+    accounts[from] -= amount;
+    progress = 1;
+    accounts[getAccount()] += amount;
+    progress = 2;
+  } finally {
+    if (progress == 1) {
+      accounts[from] += amount;
+    }
+  }
+}
+ +

This version of the function tracks its progress, and if, when leaving, it notices that it was aborted at a point where it had created an inconsistent program state, it repairs the damage it did.

+ +

Note that even though the finally code is run when an exception is thrown in the try block, it does not interfere with the exception. After the finally block runs, the stack continues unwinding.

+ +

Writing programs that operate reliably even when exceptions pop up in unexpected places is hard. Many people simply don’t bother, and because exceptions are typically reserved for exceptional circumstances, the problem may occur so rarely that it is never even noticed. Whether that is a good thing or a really bad thing depends on how much damage the software will do when it fails.

+ +

Selective catching

+ +

When an exception makes it all the way to the bottom of the stack without being caught, it gets handled by the environment. What this means differs between environments. In browsers, a description of the error typically gets written to the JavaScript console (reachable through the browser’s Tools or Developer menu). Node.js, the browserless JavaScript environment we will discuss in Chapter 20, is more careful about data corruption. It aborts the whole process when an unhandled exception occurs.

+ +

For programmer mistakes, just letting the error go through is often the best you can do. An unhandled exception is a reasonable way to signal a broken program, and the JavaScript console will, on modern browsers, provide you with some information about which function calls were on the stack when the problem occurred.

+ +

For problems that are expected to happen during routine use, crashing with an unhandled exception is a terrible strategy.

+ +

Invalid uses of the language, such as referencing a nonexistent binding, looking up a property on null, or calling something that’s not a function, will also result in exceptions being raised. Such exceptions can also be caught.

+ +

When a catch body is entered, all we know is that something in our try body caused an exception. But we don’t know what did or which exception it caused.

+ +

JavaScript (in a rather glaring omission) doesn’t provide direct support for selectively catching exceptions: either you catch them all or you don’t catch any. This makes it tempting to assume that the exception you get is the one you were thinking about when you wrote the catch block.

+ +

But it might not be. Some other assumption might be violated, or you might have introduced a bug that is causing an exception. Here is an example that attempts to keep on calling promptDirection until it gets a valid answer:

+ +
for (;;) {
+  try {
+    let dir = promtDirection("Where?"); // ← typo!
+    console.log("You chose ", dir);
+    break;
+  } catch (e) {
+    console.log("Not a valid direction. Try again.");
+  }
+}
+ +

The for (;;) construct is a way to intentionally create a loop that doesn’t terminate on its own. We break out of the loop only when a valid direction is given. But we misspelled promptDirection, which will result in an “undefined variable” error. Because the catch block completely ignores its exception value (e), assuming it knows what the problem is, it wrongly treats the binding error as indicating bad input. Not only does this cause an infinite loop, it “buries” the useful error message about the misspelled binding.

+ +

As a general rule, don’t blanket-catch exceptions unless it is for the purpose of “routing” them somewhere—for example, over the network to tell another system that our program crashed. And even then, think carefully about how you might be hiding information.

+ +

So we want to catch a specific kind of exception. We can do this by checking in the catch block whether the exception we got is the one we are interested in and rethrowing it otherwise. But how do we recognize an exception?

+ +

We could compare its message property against the error message we happen to expect. But that’s a shaky way to write code—we’d be using information that’s intended for human consumption (the message) to make a programmatic decision. As soon as someone changes (or translates) the message, the code will stop working.

+ +

Rather, let’s define a new type of error and use instanceof to identify it.

+ +
class InputError extends Error {}
+
+function promptDirection(question) {
+  let result = prompt(question);
+  if (result.toLowerCase() == "left") return "L";
+  if (result.toLowerCase() == "right") return "R";
+  throw new InputError("Invalid direction: " + result);
+}
+ +

The new error class extends Error. It doesn’t define its own constructor, which means that it inherits the Error constructor, which expects a string message as argument. In fact, it doesn’t define anything at all—the class is empty. InputError objects behave like Error objects, except that they have a different class by which we can recognize them.

+ +

Now the loop can catch these more carefully.

+ +
for (;;) {
+  try {
+    let dir = promptDirection("Where?");
+    console.log("You chose ", dir);
+    break;
+  } catch (e) {
+    if (e instanceof InputError) {
+      console.log("Not a valid direction. Try again.");
+    } else {
+      throw e;
+    }
+  }
+}
+ +

This will catch only instances of InputError and let unrelated exceptions through. If you reintroduce the typo, the undefined binding error will be properly reported.

+ +

Assertions

+ +

Assertions are checks inside a program that verify that something is the way it is supposed to be. They are used not to handle situations that can come up in normal operation but to find programmer mistakes.

+ +

If, for example, firstElement is described as a function that should never be called on empty arrays, we might write it like this:

+ +
function firstElement(array) {
+  if (array.length == 0) {
+    throw new Error("firstElement called with []");
+  }
+  return array[0];
+}
+ +

Now, instead of silently returning undefined (which you get when reading an array property that does not exist), this will loudly blow up your program as soon as you misuse it. This makes it less likely for such mistakes to go unnoticed and easier to find their cause when they occur.

+ +

I do not recommend trying to write assertions for every possible kind of bad input. That’d be a lot of work and would lead to very noisy code. You’ll want to reserve them for mistakes that are easy to make (or that you find yourself making).

+ +

Summary

+ +

Mistakes and bad input are facts of life. An important part of programming is finding, diagnosing, and fixing bugs. Problems can become easier to notice if you have an automated test suite or add assertions to your programs.

+ +

Problems caused by factors outside the program’s control should usually be handled gracefully. Sometimes, when the problem can be handled locally, special return values are a good way to track them. Otherwise, exceptions may be preferable.

+ +

Throwing an exception causes the call stack to be unwound until the next enclosing try/catch block or until the bottom of the stack. The exception value will be given to the catch block that catches it, which should verify that it is actually the expected kind of exception and then do something with it. To help address the unpredictable control flow caused by exceptions, finally blocks can be used to ensure that a piece of code always runs when a block finishes.

+ +

Exercises

+ +

Retry

+ +

Say you have a function primitiveMultiply that in 20 percent of cases multiplies two numbers and in the other 80 percent of cases raises an exception of type MultiplicatorUnitFailure. Write a function that wraps this clunky function and just keeps trying until a call succeeds, after which it returns the result.

+ +

Make sure you handle only the exceptions you are trying to handle.

+ +
class MultiplicatorUnitFailure extends Error {}
+
+function primitiveMultiply(a, b) {
+  if (Math.random() < 0.2) {
+    return a * b;
+  } else {
+    throw new MultiplicatorUnitFailure("Klunk");
+  }
+}
+
+function reliableMultiply(a, b) {
+  // Your code here.
+}
+
+console.log(reliableMultiply(8, 8));
+// → 64
+ +
+ +

The call to primitiveMultiply should definitely happen in a try block. The corresponding catch block should rethrow the exception when it is not an instance of MultiplicatorUnitFailure and ensure the call is retried when it is.

+ +

To do the retrying, you can either use a loop that stops only when a call succeeds—as in the look example earlier in this chapter—or use recursion and hope you don’t get a string of failures so long that it overflows the stack (which is a pretty safe bet).

+ +
+ +

The locked box

+ +

Consider the following (rather contrived) object:

+ +
const box = {
+  locked: true,
+  unlock() { this.locked = false; },
+  lock() { this.locked = true;  },
+  _content: [],
+  get content() {
+    if (this.locked) throw new Error("Locked!");
+    return this._content;
+  }
+};
+ +

It is a box with a lock. There is an array in the box, but you can get at it only when the box is unlocked. Directly accessing the private _content property is forbidden.

+ +

Write a function called withBoxUnlocked that takes a function value as argument, unlocks the box, runs the function, and then ensures that the box is locked again before returning, regardless of whether the argument function returned normally or threw an exception.

+ +
const box = {
+  locked: true,
+  unlock() { this.locked = false; },
+  lock() { this.locked = true;  },
+  _content: [],
+  get content() {
+    if (this.locked) throw new Error("Locked!");
+    return this._content;
+  }
+};
+
+function withBoxUnlocked(body) {
+  // Your code here.
+}
+
+withBoxUnlocked(function() {
+  box.content.push("gold piece");
+});
+
+try {
+  withBoxUnlocked(function() {
+    throw new Error("Pirates on the horizon! Abort!");
+  });
+} catch (e) {
+  console.log("Error raised: " + e);
+}
+console.log(box.locked);
+// → true
+ +

For extra points, make sure that if you call withBoxUnlocked when the box is already unlocked, the box stays unlocked.

+ +
+ +

This exercise calls for a finally block. Your function should first unlock the box and then call the argument function from inside a try body. The finally block after it should lock the box again.

+ +

To make sure we don’t lock the box when it wasn’t already locked, check its lock at the start of the function and unlock and lock it only when it started out locked.

+ +
+
diff --git a/docs/09_regexp.html b/docs/09_regexp.html new file mode 100644 index 000000000..7c2b5b7d8 --- /dev/null +++ b/docs/09_regexp.html @@ -0,0 +1,853 @@ + + + + + Regular Expressions :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 9Regular Expressions

+ +
+ +

Some people, when confronted with a problem, think ‘I know, I’ll use regular expressions.’ Now they have two problems.

+ +
Jamie Zawinski
+ +
+ +
+ +

Yuan-Ma said, ‘When you cut against the grain of the wood, much strength is needed. When you program against the grain of the problem, much code is needed.’

+ +
Master Yuan-Ma, The Book of Programming
+ +
A railroad diagram
+ +

Programming tools and techniques survive and spread in a chaotic, evolutionary way. It’s not always the pretty or brilliant ones that win but rather the ones that function well enough within the right niche or that happen to be integrated with another successful piece of technology.

+ +

In this chapter, I will discuss one such tool, regular +expressions. Regular expressions are a way to describe patterns in string data. They form a small, separate language that is part of JavaScript and many other languages and systems.

+ +

Regular expressions are both terribly awkward and extremely useful. Their syntax is cryptic, and the programming interface JavaScript provides for them is clumsy. But they are a powerful tool for inspecting and processing strings. Properly understanding regular expressions will make you a more effective programmer.

+ +

Creating a regular expression

+ +

A regular expression is a type of object. It can be either constructed with the RegExp constructor or written as a literal value by enclosing a pattern in forward slash (/) characters.

+ +
let re1 = new RegExp("abc");
+let re2 = /abc/;
+ +

Both of those regular expression objects represent the same pattern: an a character followed by a b followed by a c.

+ +

When using the RegExp constructor, the pattern is written as a normal string, so the usual rules apply for backslashes.

+ +

The second notation, where the pattern appears between slash characters, treats backslashes somewhat differently. First, since a forward slash ends the pattern, we need to put a backslash before any forward slash that we want to be part of the pattern. In addition, backslashes that aren’t part of special character codes (like \n) will be preserved, rather than ignored as they are in strings, and change the meaning of the pattern. Some characters, such as question marks and plus signs, have special meanings in regular expressions and must be preceded by a backslash if they are meant to represent the character itself.

+ +
let eighteenPlus = /eighteen\+/;
+ +

Testing for matches

+ +

Regular expression objects have a number of methods. The simplest one is test. If you pass it a string, it will return a Boolean telling you whether the string contains a match of the pattern in the expression.

+ +
console.log(/abc/.test("abcde"));
+// → true
+console.log(/abc/.test("abxde"));
+// → false
+ +

A regular expression consisting of only nonspecial characters simply represents that sequence of characters. If abc occurs anywhere in the string we are testing against (not just at the start), test will return true.

+ +

Sets of characters

+ +

Finding out whether a string contains abc could just as well be done with a call to indexOf. Regular expressions allow us to express more complicated patterns.

+ +

Say we want to match any number. In a regular expression, putting a set of characters between square brackets makes that part of the expression match any of the characters between the brackets.

+ +

Both of the following expressions match all strings that contain a digit:

+ +
console.log(/[0123456789]/.test("in 1992"));
+// → true
+console.log(/[0-9]/.test("in 1992"));
+// → true
+ +

Within square brackets, a hyphen (-) between two characters can be used to indicate a range of characters, where the ordering is determined by the character’s Unicode number. Characters 0 to 9 sit right next to each other in this ordering (codes 48 to 57), so [0-9] covers all of them and matches any digit.

+ +

A number of common character groups have their own built-in shortcuts. Digits are one of them: \d means the same thing as [0-9].

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\dAny digit character
\wAn alphanumeric character (“word character”)
\sAny whitespace character (space, tab, newline, and similar)
\DA character that is not a digit
\WA nonalphanumeric character
\SA nonwhitespace character
.Any character except for newline
+ +

So you could match a date and time format like 01-30-2003 15:20 with the following expression:

+ +
let dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/;
+console.log(dateTime.test("01-30-2003 15:20"));
+// → true
+console.log(dateTime.test("30-jan-2003 15:20"));
+// → false
+ +

That looks completely awful, doesn’t it? Half of it is backslashes, producing a background noise that makes it hard to spot the actual pattern expressed. We’ll see a slightly improved version of this expression later.

+ +

These backslash codes can also be used inside square brackets. For example, [\d.] means any digit or a period character. But the period itself, between square brackets, loses its special meaning. The same goes for other special characters, such as +.

+ +

To invert a set of characters—that is, to express that you want to match any character except the ones in the set—you can write a caret (^) character after the opening bracket.

+ +
let notBinary = /[^01]/;
+console.log(notBinary.test("1100100010100110"));
+// → false
+console.log(notBinary.test("1100100010200110"));
+// → true
+ +

Repeating parts of a pattern

+ +

We now know how to match a single digit. What if we want to match a whole number—a sequence of one or more digits?

+ +

When you put a plus sign (+) after something in a regular expression, it indicates that the element may be repeated more than once. Thus, /\d+/ matches one or more digit characters.

+ +
console.log(/'\d+'/.test("'123'"));
+// → true
+console.log(/'\d+'/.test("''"));
+// → false
+console.log(/'\d*'/.test("'123'"));
+// → true
+console.log(/'\d*'/.test("''"));
+// → true
+ +

The star (*) has a similar meaning but also allows the pattern to match zero times. Something with a star after it never prevents a pattern from matching—it’ll just match zero instances if it can’t find any suitable text to match.

+ +

A question mark makes a part of a pattern optional, meaning it may occur zero times or one time. In the following example, the u character is allowed to occur, but the pattern also matches when it is missing.

+ +
let neighbor = /neighbou?r/;
+console.log(neighbor.test("neighbour"));
+// → true
+console.log(neighbor.test("neighbor"));
+// → true
+ +

To indicate that a pattern should occur a precise number of times, use braces. Putting {4} after an element, for example, requires it to occur exactly four times. It is also possible to specify a range this way: {2,4} means the element must occur at least twice and at most four times.

+ +

Here is another version of the date and time pattern that allows both single- and double-digit days, months, and hours. It is also slightly easier to decipher.

+ +
let dateTime = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/;
+console.log(dateTime.test("1-30-2003 8:45"));
+// → true
+ +

You can also specify open-ended ranges when using braces by omitting the number after the comma. So, {5,} means five or more times.

+ +

Grouping subexpressions

+ +

To use an operator like * or + on more than one element at a time, you have to use parentheses. A part of a regular expression that is enclosed in parentheses counts as a single element as far as the operators following it are concerned.

+ +
let cartoonCrying = /boo+(hoo+)+/i;
+console.log(cartoonCrying.test("Boohoooohoohooo"));
+// → true
+ +

The first and second + characters apply only to the second o in boo and hoo, respectively. The third + applies to the whole group (hoo+), matching one or more sequences like that.

+ +

The i at the end of the expression in the example makes this regular expression case insensitive, allowing it to match the uppercase B in the input string, even though the pattern is itself all lowercase.

+ +

Matches and groups

+ +

The test method is the absolute simplest way to match a regular expression. It tells you only whether it matched and nothing else. Regular expressions also have an exec (execute) method that will return null if no match was found and return an object with information about the match otherwise.

+ +
let match = /\d+/.exec("one two 100");
+console.log(match);
+// → ["100"]
+console.log(match.index);
+// → 8
+ +

An object returned from exec has an index property that tells us where in the string the successful match begins. Other than that, the object looks like (and in fact is) an array of strings, whose first element is the string that was matched. In the previous example, this is the sequence of digits that we were looking for.

+ +

String values have a match method that behaves similarly.

+ +
console.log("one two 100".match(/\d+/));
+// → ["100"]
+ +

When the regular expression contains subexpressions grouped with parentheses, the text that matched those groups will also show up in the array. The whole match is always the first element. The next element is the part matched by the first group (the one whose opening parenthesis comes first in the expression), then the second group, and so on.

+ +
let quotedText = /'([^']*)'/;
+console.log(quotedText.exec("she said 'hello'"));
+// → ["'hello'", "hello"]
+ +

When a group does not end up being matched at all (for example, when followed by a question mark), its position in the output array will hold undefined. Similarly, when a group is matched multiple times, only the last match ends up in the array.

+ +
console.log(/bad(ly)?/.exec("bad"));
+// → ["bad", undefined]
+console.log(/(\d)+/.exec("123"));
+// → ["123", "3"]
+ +

Groups can be useful for extracting parts of a string. If we don’t just want to verify whether a string contains a date but also extract it and construct an object that represents it, we can wrap parentheses around the digit patterns and directly pick the date out of the result of exec.

+ +

But first we’ll take a brief detour, in which we discuss the built-in way to represent date and time values in JavaScript.

+ +

The Date class

+ +

JavaScript has a standard class for representing dates—or, rather, points in time. It is called Date. If you simply create a date object using new, you get the current date and time.

+ +
console.log(new Date());
+// → Mon Nov 13 2017 16:19:11 GMT+0100 (CET)
+ +

You can also create an object for a specific time.

+ +
console.log(new Date(2009, 11, 9));
+// → Wed Dec 09 2009 00:00:00 GMT+0100 (CET)
+console.log(new Date(2009, 11, 9, 12, 59, 59, 999));
+// → Wed Dec 09 2009 12:59:59 GMT+0100 (CET)
+ +

JavaScript uses a convention where month numbers start at zero (so December is 11), yet day numbers start at one. This is confusing and silly. Be careful.

+ +

The last four arguments (hours, minutes, seconds, and milliseconds) are optional and taken to be zero when not given.

+ +

Timestamps are stored as the number of milliseconds since the start of 1970, in the UTC time zone. This follows a convention set by “Unix time”, which was invented around that time. You can use negative numbers for times before 1970. The getTime method on a date object returns this number. It is big, as you can imagine.

+ +
console.log(new Date(2013, 11, 19).getTime());
+// → 1387407600000
+console.log(new Date(1387407600000));
+// → Thu Dec 19 2013 00:00:00 GMT+0100 (CET)
+ +

If you give the Date constructor a single argument, that argument is treated as such a millisecond count. You can get the current millisecond count by creating a new Date object and calling getTime on it or by calling the Date.now function.

+ +

Date objects provide methods such as getFullYear, getMonth, getDate, getHours, getMinutes, and getSeconds to extract their components. Besides getFullYear there’s also getYear, which gives you the year minus 1900 (98 or 119) and is mostly useless.

+ +

Putting parentheses around the parts of the expression that we are interested in, we can now create a date object from a string.

+ +
function getDate(string) {
+  let [_, month, day, year] =
+    /(\d{1,2})-(\d{1,2})-(\d{4})/.exec(string);
+  return new Date(year, month - 1, day);
+}
+console.log(getDate("1-30-2003"));
+// → Thu Jan 30 2003 00:00:00 GMT+0100 (CET)
+ +

The _ (underscore) binding is ignored and used only to skip the full match element in the array returned by exec.

+ +

Word and string boundaries

+ +

Unfortunately, getDate will also happily extract the nonsensical date 00-1-3000 from the string "100-1-30000". A match may happen anywhere in the string, so in this case, it’ll just start at the second character and end at the second-to-last character.

+ +

If we want to enforce that the match must span the whole string, we can add the markers ^ and $. The caret matches the start of the input string, whereas the dollar sign matches the end. So, /^\d+$/ matches a string consisting entirely of one or more digits, /^!/ matches any string that starts with an exclamation mark, and /x^/ does not match any string (there cannot be an x before the start of the string).

+ +

If, on the other hand, we just want to make sure the date starts and ends on a word boundary, we can use the marker \b. A word boundary can be the start or end of the string or any point in the string that has a word character (as in \w) on one side and a nonword character on the other.

+ +
console.log(/cat/.test("concatenate"));
+// → true
+console.log(/\bcat\b/.test("concatenate"));
+// → false
+ +

Note that a boundary marker doesn’t match an actual character. It just enforces that the regular expression matches only when a certain condition holds at the place where it appears in the pattern.

+ +

Choice patterns

+ +

Say we want to know whether a piece of text contains not only a number but a number followed by one of the words pig, cow, or chicken, or any of their plural forms.

+ +

We could write three regular expressions and test them in turn, but there is a nicer way. The pipe character (|) denotes a choice between the pattern to its left and the pattern to its right. So I can say this:

+ +
let animalCount = /\b\d+ (pig|cow|chicken)s?\b/;
+console.log(animalCount.test("15 pigs"));
+// → true
+console.log(animalCount.test("15 pigchickens"));
+// → false
+ +

Parentheses can be used to limit the part of the pattern that the pipe operator applies to, and you can put multiple such operators next to each other to express a choice between more than two alternatives.

+ +

The mechanics of matching

+ +

Conceptually, when you use exec or test, the regular expression engine looks for a match in your string by trying to match the expression first from the start of the string, then from the second character, and so on, until it finds a match or reaches the end of the string. It’ll either return the first match that can be found or fail to find any match at all.

+ +

To do the actual matching, the engine treats a regular expression something like a flow diagram. This is the diagram for the livestock expression in the previous example:

Visualization of /\b\d+ (pig|cow|chicken)s?\b/
+ +

Our expression matches if we can find a path from the left side of the diagram to the right side. We keep a current position in the string, and every time we move through a box, we verify that the part of the string after our current position matches that box.

+ +

So if we try to match "the 3 pigs" from position 4, our progress through the flow chart would look like this:

+ +
    + +
  • + +

    At position 4, there is a word boundary, so we can move past the first box.

  • + +
  • + +

    Still at position 4, we find a digit, so we can also move past the second box.

  • + +
  • + +

    At position 5, one path loops back to before the second (digit) box, while the other moves forward through the box that holds a single space character. There is a space here, not a digit, so we must take the second path.

  • + +
  • + +

    We are now at position 6 (the start of pigs) and at the three-way branch in the diagram. We don’t see cow or chicken here, but we do see pig, so we take that branch.

  • + +
  • + +

    At position 9, after the three-way branch, one path skips the s box and goes straight to the final word boundary, while the other path matches an s. There is an s character here, not a word boundary, so we go through the s box.

  • + +
  • + +

    We’re at position 10 (the end of the string) and can match only a word boundary. The end of a string counts as a word boundary, so we go through the last box and have successfully matched this string.

+ +

Backtracking

+ +

The regular expression /\b([01]+b|[\da-f]+h|\d+)\b/ matches either a binary number followed by a b, a hexadecimal number (that is, base 16, with the letters a to f standing for the digits 10 to 15) followed by an h, or a regular decimal number with no suffix character. This is the corresponding diagram:

Visualization of /\b([01]+b|\d+|[\da-f]+h)\b/
+ +

When matching this expression, it will often happen that the top (binary) branch is entered even though the input does not actually contain a binary number. When matching the string "103", for example, it becomes clear only at the 3 that we are in the wrong branch. The string does match the expression, just not the branch we are currently in.

+ +

So the matcher backtracks. When entering a branch, it remembers its current position (in this case, at the start of the string, just past the first boundary box in the diagram) so that it can go back and try another branch if the current one does not work out. For the string "103", after encountering the 3 character, it will start trying the branch for hexadecimal numbers, which fails again because there is no h after the number. So it tries the decimal number branch. This one fits, and a match is reported after all.

+ +

The matcher stops as soon as it finds a full match. This means that if multiple branches could potentially match a string, only the first one (ordered by where the branches appear in the regular expression) is used.

+ +

Backtracking also happens for repetition operators like + and *. If you match /^.*x/ against "abcxe", the .* part will first try to consume the whole string. The engine will then realize that it needs an x to match the pattern. Since there is no x past the end of the string, the star operator tries to match one character less. But the matcher doesn’t find an x after abcx either, so it backtracks again, matching the star operator to just abc. Now it finds an x where it needs it and reports a successful match from positions 0 to 4.

+ +

It is possible to write regular expressions that will do a lot of backtracking. This problem occurs when a pattern can match a piece of input in many different ways. For example, if we get confused while writing a binary-number regular expression, we might accidentally write something like /([01]+)+b/.

Visualization of /([01]+)+b/
+ +

If that tries to match some long series of zeros and ones with no trailing b character, the matcher first goes through the inner loop until it runs out of digits. Then it notices there is no b, so it backtracks one position, goes through the outer loop once, and gives up again, trying to backtrack out of the inner loop once more. It will continue to try every possible route through these two loops. This means the amount of work doubles with each additional character. For even just a few dozen characters, the resulting match will take practically forever.

+ +

The replace method

+ +

String values have a replace method that can be used to replace part of the string with another string.

+ +
console.log("papa".replace("p", "m"));
+// → mapa
+ +

The first argument can also be a regular expression, in which case the first match of the regular expression is replaced. When a g option (for global) is added to the regular expression, all matches in the string will be replaced, not just the first.

+ +
console.log("Borobudur".replace(/[ou]/, "a"));
+// → Barobudur
+console.log("Borobudur".replace(/[ou]/g, "a"));
+// → Barabadar
+ +

It would have been sensible if the choice between replacing one match or all matches was made through an additional argument to replace or by providing a different method, replaceAll. But for some unfortunate reason, the choice relies on a property of the regular expression instead.

+ +

The real power of using regular expressions with replace comes from the fact that we can refer to matched groups in the replacement string. For example, say we have a big string containing the names of people, one name per line, in the format Lastname, Firstname. If we want to swap these names and remove the comma to get a Firstname Lastname format, we can use the following code:

+ +
console.log(
+  "Liskov, Barbara\nMcCarthy, John\nWadler, Philip"
+    .replace(/(\w+), (\w+)/g, "$2 $1"));
+// → Barbara Liskov
+//   John McCarthy
+//   Philip Wadler
+ +

The $1 and $2 in the replacement string refer to the parenthesized groups in the pattern. $1 is replaced by the text that matched against the first group, $2 by the second, and so on, up to $9. The whole match can be referred to with $&.

+ +

It is possible to pass a function—rather than a string—as the second argument to replace. For each replacement, the function will be called with the matched groups (as well as the whole match) as arguments, and its return value will be inserted into the new string.

+ +

Here’s a small example:

+ +
let s = "the cia and fbi";
+console.log(s.replace(/\b(fbi|cia)\b/g,
+            str => str.toUpperCase()));
+// → the CIA and FBI
+ +

Here’s a more interesting one:

+ +
let stock = "1 lemon, 2 cabbages, and 101 eggs";
+function minusOne(match, amount, unit) {
+  amount = Number(amount) - 1;
+  if (amount == 1) { // only one left, remove the 's'
+    unit = unit.slice(0, unit.length - 1);
+  } else if (amount == 0) {
+    amount = "no";
+  }
+  return amount + " " + unit;
+}
+console.log(stock.replace(/(\d+) (\w+)/g, minusOne));
+// → no lemon, 1 cabbage, and 100 eggs
+ +

This takes a string, finds all occurrences of a number followed by an alphanumeric word, and returns a string wherein every such occurrence is decremented by one.

+ +

The (\d+) group ends up as the amount argument to the function, and the (\w+) group gets bound to unit. The function converts amount to a number—which always works since it matched \d+—and makes some adjustments in case there is only one or zero left.

+ +

Greed

+ +

It is possible to use replace to write a function that removes all comments from a piece of JavaScript code. Here is a first attempt:

+ +
function stripComments(code) {
+  return code.replace(/\/\/.*|\/\*[^]*\*\//g, "");
+}
+console.log(stripComments("1 + /* 2 */3"));
+// → 1 + 3
+console.log(stripComments("x = 10;// ten!"));
+// → x = 10;
+console.log(stripComments("1 /* a */+/* b */ 1"));
+// → 1  1
+ +

The part before the or operator matches two slash characters followed by any number of non-newline characters. The part for multiline comments is more involved. We use [^] (any character that is not in the empty set of characters) as a way to match any character. We cannot just use a period here because block comments can continue on a new line, and the period character does not match newline characters.

+ +

But the output for the last line appears to have gone wrong. Why?

+ +

The [^]* part of the expression, as I described in the section on backtracking, will first match as much as it can. If that causes the next part of the pattern to fail, the matcher moves back one character and tries again from there. In the example, the matcher first tries to match the whole rest of the string and then moves back from there. It will find an occurrence of */ after going back four characters and match that. This is not what we wanted—the intention was to match a single comment, not to go all the way to the end of the code and find the end of the last block comment.

+ +

Because of this behavior, we say the repetition operators (+, *, ?, and {}) are greedy, meaning they match as much as they can and backtrack from there. If you put a question mark after them (+?, *?, ??, {}?), they become nongreedy and start by matching as little as possible, matching more only when the remaining pattern does not fit the smaller match.

+ +

And that is exactly what we want in this case. By having the star match the smallest stretch of characters that brings us to a */, we consume one block comment and nothing more.

+ +
function stripComments(code) {
+  return code.replace(/\/\/.*|\/\*[^]*?\*\//g, "");
+}
+console.log(stripComments("1 /* a */+/* b */ 1"));
+// → 1 + 1
+ +

A lot of bugs in regular expression programs can be traced to unintentionally using a greedy operator where a nongreedy one would work better. When using a repetition operator, consider the nongreedy variant first.

+ +

Dynamically creating RegExp objects

+ +

There are cases where you might not know the exact pattern you need to match against when you are writing your code. Say you want to look for the user’s name in a piece of text and enclose it in underscore characters to make it stand out. Since you will know the name only once the program is actually running, you can’t use the slash-based notation.

+ +

But you can build up a string and use the RegExp constructor on that. Here’s an example:

+ +
let name = "harry";
+let text = "Harry is a suspicious character.";
+let regexp = new RegExp("\\b(" + name + ")\\b", "gi");
+console.log(text.replace(regexp, "_$1_"));
+// → _Harry_ is a suspicious character.
+ +

When creating the \b boundary markers, we have to use two backslashes because we are writing them in a normal string, not a slash-enclosed regular expression. The second argument to the RegExp constructor contains the options for the regular expression—in this case, "gi" for global and case insensitive.

+ +

But what if the name is "dea+hl[]rd" because our user is a nerdy teenager? That would result in a nonsensical regular expression that won’t actually match the user’s name.

+ +

To work around this, we can add backslashes before any character that has a special meaning.

+ +
let name = "dea+hl[]rd";
+let text = "This dea+hl[]rd guy is super annoying.";
+let escaped = name.replace(/[\\[.+*?(){|^$]/g, "\\$&");
+let regexp = new RegExp("\\b" + escaped + "\\b", "gi");
+console.log(text.replace(regexp, "_$&_"));
+// → This _dea+hl[]rd_ guy is super annoying.
+ +

The search method

+ +

The indexOf method on strings cannot be called with a regular expression. But there is another method, search, that does expect a regular expression. Like indexOf, it returns the first index on which the expression was found, or -1 when it wasn’t found.

+ +
console.log("  word".search(/\S/));
+// → 2
+console.log("    ".search(/\S/));
+// → -1
+ +

Unfortunately, there is no way to indicate that the match should start at a given offset (like we can with the second argument to indexOf), which would often be useful.

+ +

The lastIndex property

+ +

The exec method similarly does not provide a convenient way to start searching from a given position in the string. But it does provide an inconvenient way.

+ +

Regular expression objects have properties. One such property is source, which contains the string that expression was created from. Another property is lastIndex, which controls, in some limited circumstances, where the next match will start.

+ +

Those circumstances are that the regular expression must have the global (g) or sticky (y) option enabled, and the match must happen through the exec method. Again, a less confusing solution would have been to just allow an extra argument to be passed to exec, but confusion is an essential feature of JavaScript’s regular expression interface.

+ +
let pattern = /y/g;
+pattern.lastIndex = 3;
+let match = pattern.exec("xyzzy");
+console.log(match.index);
+// → 4
+console.log(pattern.lastIndex);
+// → 5
+ +

If the match was successful, the call to exec automatically updates the lastIndex property to point after the match. If no match was found, lastIndex is set back to zero, which is also the value it has in a newly constructed regular expression object.

+ +

The difference between the global and the sticky options is that, when sticky is enabled, the match will succeed only if it starts directly at lastIndex, whereas with global, it will search ahead for a position where a match can start.

+ +
let global = /abc/g;
+console.log(global.exec("xyz abc"));
+// → ["abc"]
+let sticky = /abc/y;
+console.log(sticky.exec("xyz abc"));
+// → null
+ +

When using a shared regular expression value for multiple exec calls, these automatic updates to the lastIndex property can cause problems. Your regular expression might be accidentally starting at an index that was left over from a previous call.

+ +
let digit = /\d/g;
+console.log(digit.exec("here it is: 1"));
+// → ["1"]
+console.log(digit.exec("and now: 1"));
+// → null
+ +

Another interesting effect of the global option is that it changes the way the match method on strings works. When called with a global expression, instead of returning an array similar to that returned by exec, match will find all matches of the pattern in the string and return an array containing the matched strings.

+ +
console.log("Banana".match(/an/g));
+// → ["an", "an"]
+ +

So be cautious with global regular expressions. The cases where they are necessary—calls to replace and places where you want to explicitly use lastIndex—are typically the only places where you want to use them.

+ +

Looping over matches

+ +

A common thing to do is to scan through all occurrences of a pattern in a string, in a way that gives us access to the match object in the loop body. We can do this by using lastIndex and exec.

+ +
let input = "A string with 3 numbers in it... 42 and 88.";
+let number = /\b\d+\b/g;
+let match;
+while (match = number.exec(input)) {
+  console.log("Found", match[0], "at", match.index);
+}
+// → Found 3 at 14
+//   Found 42 at 33
+//   Found 88 at 40
+ +

This makes use of the fact that the value of an assignment expression (=) is the assigned value. So by using match = number.exec(input) as the condition in the while statement, we perform the match at the start of each iteration, save its result in a binding, and stop looping when no more matches are found.

+ +

Parsing an INI file

+ +

To conclude the chapter, we’ll look at a problem that calls for regular expressions. Imagine we are writing a program to automatically collect information about our enemies from the Internet. (We will not actually write that program here, just the part that reads the configuration file. Sorry.) The configuration file looks like this:

+ +
searchengine=https://duckduckgo.com/?q=$1
+spitefulness=9.7
+
+; comments are preceded by a semicolon...
+; each section concerns an individual enemy
+[larry]
+fullname=Larry Doe
+type=kindergarten bully
+website=http://www.geocities.com/CapeCanaveral/11451
+
+[davaeorn]
+fullname=Davaeorn
+type=evil wizard
+outputdir=/home/marijn/enemies/davaeorn
+ +

The exact rules for this format (which is a widely used format, usually called an INI file) are as follows:

+ +
    + +
  • + +

    Blank lines and lines starting with semicolons are ignored.

  • + +
  • + +

    Lines wrapped in [ and ] start a new section.

  • + +
  • + +

    Lines containing an alphanumeric identifier followed by an = character add a setting to the current section.

  • + +
  • + +

    Anything else is invalid.

+ +

Our task is to convert a string like this into an object whose properties hold strings for settings written before the first section header and subobjects for sections, with those subobjects holding the section’s settings.

+ +

Since the format has to be processed line by line, splitting up the file into separate lines is a good start. We saw the split method in Chapter 4. Some operating systems, however, use not just a newline character to separate lines but a carriage return character followed by a newline ("\r\n"). Given that the split method also allows a regular expression as its argument, we can use a regular expression like /\r?\n/ to split in a way that allows both "\n" and "\r\n" between lines.

+ +
function parseINI(string) {
+  // Start with an object to hold the top-level fields
+  let result = {};
+  let section = result;
+  string.split(/\r?\n/).forEach(line => {
+    let match;
+    if (match = line.match(/^(\w+)=(.*)$/)) {
+      section[match[1]] = match[2];
+    } else if (match = line.match(/^\[(.*)\]$/)) {
+      section = result[match[1]] = {};
+    } else if (!/^\s*(;.*)?$/.test(line)) {
+      throw new Error("Line '" + line + "' is not valid.");
+    }
+  });
+  return result;
+}
+
+console.log(parseINI(`
+name=Vasilis
+[address]
+city=Tessaloniki`));
+// → {name: "Vasilis", address: {city: "Tessaloniki"}}
+ +

The code goes over the file’s lines and builds up an object. Properties at the top are stored directly into that object, whereas properties found in sections are stored in a separate section object. The section binding points at the object for the current section.

+ +

There are two kinds of significant lines—section headers or property lines. When a line is a regular property, it is stored in the current section. When it is a section header, a new section object is created, and section is set to point at it.

+ +

Note the recurring use of ^ and $ to make sure the expression matches the whole line, not just part of it. Leaving these out results in code that mostly works but behaves strangely for some input, which can be a difficult bug to track down.

+ +

The pattern if (match = string.match(...)) is similar to the trick of using an assignment as the condition for while. You often aren’t sure that your call to match will succeed, so you can access the resulting object only inside an if statement that tests for this. To not break the pleasant chain of else if forms, we assign the result of the match to a binding and immediately use that assignment as the test for the if statement.

+ +

If a line is not a section header or a property, the function checks whether it is a comment or an empty line using the expression /^\s*(;.*)?$/. Do you see how it works? The part between the parentheses will match comments, and the ? makes sure it also matches lines containing only whitespace. When a line doesn’t match any of the expected forms, the function throws an exception.

+ +

International characters

+ +

Because of JavaScript’s initial simplistic implementation and the fact that this simplistic approach was later set in stone as standard behavior, JavaScript’s regular expressions are rather dumb about characters that do not appear in the English language. For example, as far as JavaScript’s regular expressions are concerned, a “word +character” is only one of the 26 characters in the Latin alphabet (uppercase or lowercase), decimal digits, and, for some reason, the underscore character. Things like é or β, which most definitely are word characters, will not match \w (and will match uppercase \W, the nonword category).

+ +

By a strange historical accident, \s (whitespace) does not have this problem and matches all characters that the Unicode standard considers whitespace, including things like the nonbreaking space and the Mongolian vowel separator.

+ +

Another problem is that, by default, regular expressions work on code units, as discussed in Chapter 5, not actual characters. This means characters that are composed of two code units behave strangely.

+ +
console.log(/🍎{3}/.test("🍎🍎🍎"));
+// → false
+console.log(/<.>/.test("<🌹>"));
+// → false
+console.log(/<.>/u.test("<🌹>"));
+// → true
+ +

The problem is that the 🍎 in the first line is treated as two code units, and the {3} part is applied only to the second one. Similarly, the dot matches a single code unit, not the two that make up the rose emoji.

+ +

You must add a u option (for Unicode) to your regular expression to make it treat such characters properly. The wrong behavior remains the default, unfortunately, because changing that might cause problems for existing code that depends on it.

+ +

Though this was only just standardized and is, at the time of writing, not widely supported yet, it is possible to use \p in a regular expression (that must have the Unicode option enabled) to match all characters to which the Unicode standard assigns a given property.

+ +
console.log(/\p{Script=Greek}/u.test("α"));
+// → true
+console.log(/\p{Script=Arabic}/u.test("α"));
+// → false
+console.log(/\p{Alphabetic}/u.test("α"));
+// → true
+console.log(/\p{Alphabetic}/u.test("!"));
+// → false
+ +

Unicode defines a number of useful properties, though finding the one that you need may not always be trivial. You can use the \p{Property=Value} notation to match any character that has the given value for that property. If the property name is left off, as in \p{Name}, the name is assumed to be either a binary property such as Alphabetic or a category such as Number.

+ +

Summary

+ +

Regular expressions are objects that represent patterns in strings. They use their own language to express these patterns.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
/abc/A sequence of characters
/[abc]/Any character from a set of characters
/[^abc]/Any character not in a set of characters
/[0-9]/Any character in a range of characters
/x+/One or more occurrences of the pattern x
/x+?/One or more occurrences, nongreedy
/x*/Zero or more occurrences
/x?/Zero or one occurrence
/x{2,4}/Two to four occurrences
/(abc)/A group
/a|b|c/Any one of several patterns
/\d/Any digit character
/\w/An alphanumeric character (“word character”)
/\s/Any whitespace character
/./Any character except newlines
/\b/A word boundary
/^/Start of input
/$/End of input
+ +

A regular expression has a method test to test whether a given string matches it. It also has a method exec that, when a match is found, returns an array containing all matched groups. Such an array has an index property that indicates where the match started.

+ +

Strings have a match method to match them against a regular expression and a search method to search for one, returning only the starting position of the match. Their replace method can replace matches of a pattern with a replacement string or function.

+ +

Regular expressions can have options, which are written after the closing slash. The i option makes the match case insensitive. The g option makes the expression global, which, among other things, causes the replace method to replace all instances instead of just the first. The y option makes it sticky, which means that it will not search ahead and skip part of the string when looking for a match. The u option turns on Unicode mode, which fixes a number of problems around the handling of characters that take up two code units.

+ +

Regular expressions are a sharp tool with an awkward handle. They simplify some tasks tremendously but can quickly become unmanageable when applied to complex problems. Part of knowing how to use them is resisting the urge to try to shoehorn things that they cannot cleanly express into them.

+ +

Exercises

+ +

It is almost unavoidable that, in the course of working on these exercises, you will get confused and frustrated by some regular expression’s inexplicable behavior. Sometimes it helps to enter your expression into an online tool like https://debuggex.com to see whether its visualization corresponds to what you intended and to experiment with the way it responds to various input strings.

+ +

Regexp golf

+ +

Code golf is a term used for the game of trying to express a particular program in as few characters as possible. Similarly, regexp golf is the practice of writing as tiny a regular expression as possible to match a given pattern, and only that pattern.

+ +

For each of the following items, write a regular expression to test whether any of the given substrings occur in a string. The regular expression should match only strings containing one of the substrings described. Do not worry about word boundaries unless explicitly mentioned. When your expression works, see whether you can make it any smaller.

+ +
    + +
  1. + +

    car and cat

  2. + +
  3. + +

    pop and prop

  4. + +
  5. + +

    ferret, ferry, and ferrari

  6. + +
  7. + +

    Any word ending in ious

  8. + +
  9. + +

    A whitespace character followed by a period, comma, colon, or semicolon

  10. + +
  11. + +

    A word longer than six letters

  12. + +
  13. + +

    A word without the letter e (or E)

  14. + +
+ +

Refer to the table in the chapter summary for help. Test each solution with a few test strings.

+ +
// Fill in the regular expressions
+
+verify(/.../,
+       ["my car", "bad cats"],
+       ["camper", "high art"]);
+
+verify(/.../,
+       ["pop culture", "mad props"],
+       ["plop", "prrrop"]);
+
+verify(/.../,
+       ["ferret", "ferry", "ferrari"],
+       ["ferrum", "transfer A"]);
+
+verify(/.../,
+       ["how delicious", "spacious room"],
+       ["ruinous", "consciousness"]);
+
+verify(/.../,
+       ["bad punctuation ."],
+       ["escape the period"]);
+
+verify(/.../,
+       ["hottentottententen"],
+       ["no", "hotten totten tenten"]);
+
+verify(/.../,
+       ["red platypus", "wobbling nest"],
+       ["earth bed", "learning ape", "BEET"]);
+
+
+function verify(regexp, yes, no) {
+  // Ignore unfinished exercises
+  if (regexp.source == "...") return;
+  for (let str of yes) if (!regexp.test(str)) {
+    console.log(`Failure to match '${str}'`);
+  }
+  for (let str of no) if (regexp.test(str)) {
+    console.log(`Unexpected match for '${str}'`);
+  }
+}
+ +

Quoting style

+ +

Imagine you have written a story and used single quotation marks throughout to mark pieces of dialogue. Now you want to replace all the dialogue quotes with double quotes, while keeping the single quotes used in contractions like aren’t.

+ +

Think of a pattern that distinguishes these two kinds of quote usage and craft a call to the replace method that does the proper replacement.

+ +
let text = "'I'm the cook,' he said, 'it's my job.'";
+// Change this call.
+console.log(text.replace(/A/g, "B"));
+// → "I'm the cook," he said, "it's my job."
+ +
+ +

The most obvious solution is to replace only quotes with a nonword character on at least one side—something like /\W'|'\W/. But you also have to take the start and end of the line into account.

+ +

In addition, you must ensure that the replacement also includes the characters that were matched by the \W pattern so that those are not dropped. This can be done by wrapping them in parentheses and including their groups in the replacement string ($1, $2). Groups that are not matched will be replaced by nothing.

+ +
+ +

Numbers again

+ +

Write an expression that matches only JavaScript-style numbers. It must support an optional minus or plus sign in front of the number, the decimal dot, and exponent notation—5e-3 or 1E10—again with an optional sign in front of the exponent. Also note that it is not necessary for there to be digits in front of or after the dot, but the number cannot be a dot alone. That is, .5 and 5. are valid JavaScript numbers, but a lone dot isn’t.

+ +
// Fill in this regular expression.
+let number = /^...$/;
+
+// Tests:
+for (let str of ["1", "-1", "+15", "1.55", ".5", "5.",
+                 "1.3e2", "1E-4", "1e+12"]) {
+  if (!number.test(str)) {
+    console.log(`Failed to match '${str}'`);
+  }
+}
+for (let str of ["1a", "+-1", "1.2.3", "1+1", "1e4.5",
+                 ".5.", "1f5", "."]) {
+  if (number.test(str)) {
+    console.log(`Incorrectly accepted '${str}'`);
+  }
+}
+ +
+ +

First, do not forget the backslash in front of the period.

+ +

Matching the optional sign in front of the number, as well as in front of the exponent, can be done with [+\-]? or (\+|-|) (plus, minus, or nothing).

+ +

The more complicated part of the exercise is the problem of matching both "5." and ".5" without also matching ".". For this, a good solution is to use the | operator to separate the two cases—either one or more digits optionally followed by a dot and zero or more digits or a dot followed by one or more digits.

+ +

Finally, to make the e case insensitive, either add an i option to the regular expression or use [eE].

+ +
+
diff --git a/docs/10_modules.html b/docs/10_modules.html new file mode 100644 index 000000000..f9fbf9ed6 --- /dev/null +++ b/docs/10_modules.html @@ -0,0 +1,375 @@ + + + + + Modules :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 10Modules

+ +
+ +

Write code that is easy to delete, not easy to extend.

+ +
Tef, Programming is Terrible
+ +
Picture of a building built from modular pieces
+ +

The ideal program has a crystal-clear structure. The way it works is easy to explain, and each part plays a well-defined role.

+ +

A typical real program grows organically. New pieces of functionality are added as new needs come up. Structuring—and preserving structure—is additional work. It’s work that will pay off only in the future, the next time someone works on the program. So it is tempting to neglect it and allow the parts of the program to become deeply entangled.

+ +

This causes two practical issues. First, understanding such a system is hard. If everything can touch everything else, it is difficult to look at any given piece in isolation. You are forced to build up a holistic understanding of the entire thing. Second, if you want to use any of the functionality from such a program in another situation, rewriting it may be easier than trying to disentangle it from its context.

+ +

The phrase “big ball of mud” is often used for such large, structureless programs. Everything sticks together, and when you try to pick out a piece, the whole thing comes apart, and your hands get dirty.

+ +

Modules

+ +

Modules are an attempt to avoid these problems. A module is a piece of program that specifies which other pieces it relies on and which functionality it provides for other modules to use (its interface).

+ +

Module interfaces have a lot in common with object interfaces, as we saw them in Chapter 6. They make part of the module available to the outside world and keep the rest private. By restricting the ways in which modules interact with each other, the system becomes more like LEGO, where pieces interact through well-defined connectors, and less like mud, where everything mixes with everything.

+ +

The relations between modules are called dependencies. When a module needs a piece from another module, it is said to depend on that module. When this fact is clearly specified in the module itself, it can be used to figure out which other modules need to be present to be able to use a given module and to automatically load dependencies.

+ +

To separate modules in that way, each needs its own private scope.

+ +

Just putting your JavaScript code into different files does not satisfy these requirements. The files still share the same global namespace. They can, intentionally or accidentally, interfere with each other’s bindings. And the dependency structure remains unclear. We can do better, as we’ll see later in the chapter.

+ +

Designing a fitting module structure for a program can be difficult. In the phase where you are still exploring the problem, trying different things to see what works, you might want to not worry about it too much since it can be a big distraction. Once you have something that feels solid, that’s a good time to take a step back and organize it.

+ +

Packages

+ +

One of the advantages of building a program out of separate pieces, and being actually able to run those pieces on their own, is that you might be able to apply the same piece in different programs.

+ +

But how do you set this up? Say I want to use the parseINI function from Chapter 9 in another program. If it is clear what the function depends on (in this case, nothing), I can just copy all the necessary code into my new project and use it. But then, if I find a mistake in that code, I’ll probably fix it in whichever program I’m working with at the time and forget to also fix it in the other program.

+ +

Once you start duplicating code, you’ll quickly find yourself wasting time and energy moving copies around and keeping them up-to-date.

+ +

That’s where packages come in. A package is a chunk of code that can be distributed (copied and installed). It may contain one or more modules and has information about which other packages it depends on. A package also usually comes with documentation explaining what it does so that people who didn’t write it might still be able to use it.

+ +

When a problem is found in a package or a new feature is added, the package is updated. Now the programs that depend on it (which may also be packages) can upgrade to the new version.

+ +

Working in this way requires infrastructure. We need a place to store and find packages and a convenient way to install and upgrade them. In the JavaScript world, this infrastructure is provided by NPM (https://npmjs.org).

+ +

NPM is two things: an online service where one can download (and upload) packages and a program (bundled with Node.js) that helps you install and manage them.

+ +

At the time of writing, there are more than half a million different packages available on NPM. A large portion of those are rubbish, I should mention, but almost every useful, publicly available package can be found on there. For example, an INI file parser, similar to the one we built in Chapter 9, is available under the package name ini.

+ +

Chapter 20 will show how to install such packages locally using the npm command line program.

+ +

Having quality packages available for download is extremely valuable. It means that we can often avoid reinventing a program that 100 people have written before and get a solid, well-tested implementation at the press of a few keys.

+ +

Software is cheap to copy, so once someone has written it, distributing it to other people is an efficient process. But writing it in the first place is work, and responding to people who have found problems in the code, or who want to propose new features, is even more work.

+ +

By default, you own the copyright to the code you write, and other people may use it only with your permission. But because some people are just nice and because publishing good software can help make you a little bit famous among programmers, many packages are published under a license that explicitly allows other people to use it.

+ +

Most code on NPM is licensed this way. Some licenses require you to also publish code that you build on top of the package under the same license. Others are less demanding, just requiring that you keep the license with the code as you distribute it. The JavaScript community mostly uses the latter type of license. When using other people’s packages, make sure you are aware of their license.

+ +

Improvised modules

+ +

Until 2015, the JavaScript language had no built-in module system. Yet people had been building large systems in JavaScript for more than a decade, and they needed modules.

+ +

So they designed their own module systems on top of the language. You can use JavaScript functions to create local scopes and objects to represent module interfaces.

+ +

This is a module for going between day names and numbers (as returned by Date’s getDay method). Its interface consists of weekDay.name and weekDay.number, and it hides its local binding names inside the scope of a function expression that is immediately invoked.

+ +
const weekDay = function() {
+  const names = ["Sunday", "Monday", "Tuesday", "Wednesday",
+                 "Thursday", "Friday", "Saturday"];
+  return {
+    name(number) { return names[number]; },
+    number(name) { return names.indexOf(name); }
+  };
+}();
+
+console.log(weekDay.name(weekDay.number("Sunday")));
+// → Sunday
+ +

This style of modules provides isolation, to a certain degree, but it does not declare dependencies. Instead, it just puts its interface into the global scope and expects its dependencies, if any, to do the same. For a long time this was the main approach used in web programming, but it is mostly obsolete now.

+ +

If we want to make dependency relations part of the code, we’ll have to take control of loading dependencies. Doing that requires being able to execute strings as code. JavaScript can do this.

+ +

Evaluating data as code

+ +

There are several ways to take data (a string of code) and run it as part of the current program.

+ +

The most obvious way is the special operator eval, which will execute a string in the current scope. This is usually a bad idea because it breaks some of the properties that scopes normally have, such as it being easily predictable which binding a given name refers to.

+ +
const x = 1;
+function evalAndReturnX(code) {
+  eval(code);
+  return x;
+}
+
+console.log(evalAndReturnX("var x = 2"));
+// → 2
+console.log(x);
+// → 1
+ +

A less scary way of interpreting data as code is to use the Function constructor. It takes two arguments: a string containing a comma-separated list of argument names and a string containing the function body. It wraps the code in a function value so that it gets its own scope and won’t do odd things with other scopes.

+ +
let plusOne = Function("n", "return n + 1;");
+console.log(plusOne(4));
+// → 5
+ +

This is precisely what we need for a module system. We can wrap the module’s code in a function and use that function’s scope as module scope.

+ +

CommonJS

+ +

The most widely used approach to bolted-on JavaScript modules is called CommonJS modules. Node.js uses it and is the system used by most packages on NPM.

+ +

The main concept in CommonJS modules is a function called require. When you call this with the module name of a dependency, it makes sure the module is loaded and returns its interface.

+ +

Because the loader wraps the module code in a function, modules automatically get their own local scope. All they have to do is call require to access their dependencies and put their interface in the object bound to exports.

+ +

This example module provides a date-formatting function. It uses two packages from NPM—ordinal to convert numbers to strings like "1st" and "2nd", and date-names to get the English names for weekdays and months. It exports a single function, formatDate, which takes a Date object and a template string.

+ +

The template string may contain codes that direct the format, such as YYYY for the full year and Do for the ordinal day of the month. You could give it a string like "MMMM Do YYYY" to get output like “November 22nd 2017”.

+ +
const ordinal = require("ordinal");
+const {days, months} = require("date-names");
+
+exports.formatDate = function(date, format) {
+  return format.replace(/YYYY|M(MMM)?|Do?|dddd/g, tag => {
+    if (tag == "YYYY") return date.getFullYear();
+    if (tag == "M") return date.getMonth();
+    if (tag == "MMMM") return months[date.getMonth()];
+    if (tag == "D") return date.getDate();
+    if (tag == "Do") return ordinal(date.getDate());
+    if (tag == "dddd") return days[date.getDay()];
+  });
+};
+ +

The interface of ordinal is a single function, whereas date-names exports an object containing multiple things—days and months are arrays of names. Destructuring is very convenient when creating bindings for imported interfaces.

+ +

The module adds its interface function to exports so that modules that depend on it get access to it. We could use the module like this:

+ +
const {formatDate} = require("./format-date");
+
+console.log(formatDate(new Date(2017, 9, 13),
+                       "dddd the Do"));
+// → Friday the 13th
+ +

We can define require, in its most minimal form, like this:

+ +
require.cache = Object.create(null);
+
+function require(name) {
+  if (!(name in require.cache)) {
+    let code = readFile(name);
+    let module = {exports: {}};
+    require.cache[name] = module;
+    let wrapper = Function("require, exports, module", code);
+    wrapper(require, module.exports, module);
+  }
+  return require.cache[name].exports;
+}
+ +

In this code, readFile is a made-up function that reads a file and returns its contents as a string. Standard JavaScript provides no such functionality—but different JavaScript environments, such as the browser and Node.js, provide their own ways of accessing files. The example just pretends that readFile exists.

+ +

To avoid loading the same module multiple times, require keeps a store (cache) of already loaded modules. When called, it first checks if the requested module has been loaded and, if not, loads it. This involves reading the module’s code, wrapping it in a function, and calling it.

+ +

The interface of the ordinal package we saw before is not an object but a function. A quirk of the CommonJS modules is that, though the module system will create an empty interface object for you (bound to exports), you can replace that with any value by overwriting module.exports. This is done by many modules to export a single value instead of an interface object.

+ +

By defining require, exports, and module as parameters for the generated wrapper function (and passing the appropriate values when calling it), the loader makes sure that these bindings are available in the module’s scope.

+ +

The way the string given to require is translated to an actual filename or web address differs in different systems. When it starts with "./" or "../", it is generally interpreted as relative to the current module’s filename. So "./format-date" would be the file named format-date.js in the same directory.

+ +

When the name isn’t relative, Node.js will look for an installed package by that name. In the example code in this chapter, we’ll interpret such names as referring to NPM packages. We’ll go into more detail on how to install and use NPM modules in Chapter 20.

+ +

Now, instead of writing our own INI file parser, we can use one from NPM.

+ +
const {parse} = require("ini");
+
+console.log(parse("x = 10\ny = 20"));
+// → {x: "10", y: "20"}
+ +

ECMAScript modules

+ +

CommonJS modules work quite well and, in combination with NPM, have allowed the JavaScript community to start sharing code on a large scale.

+ +

But they remain a bit of a duct-tape hack. The notation is slightly awkward—the things you add to exports are not available in the local scope, for example. And because require is a normal function call taking any kind of argument, not just a string literal, it can be hard to determine the dependencies of a module without running its code.

+ +

This is why the JavaScript standard from 2015 introduces its own, different module system. It is usually called ES modules, where ES stands for ECMAScript. The main concepts of dependencies and interfaces remain the same, but the details differ. For one thing, the notation is now integrated into the language. Instead of calling a function to access a dependency, you use a special import keyword.

+ +
import ordinal from "ordinal";
+import {days, months} from "date-names";
+
+export function formatDate(date, format) { /* ... */ }
+ +

Similarly, the export keyword is used to export things. It may appear in front of a function, class, or binding definition (let, const, or var).

+ +

An ES module’s interface is not a single value but a set of named bindings. The preceding module binds formatDate to a function. When you import from another module, you import the binding, not the value, which means an exporting module may change the value of the binding at any time, and the modules that import it will see its new value.

+ +

When there is a binding named default, it is treated as the module’s main exported value. If you import a module like ordinal in the example, without braces around the binding name, you get its default binding. Such modules can still export other bindings under different names alongside their default export.

+ +

To create a default export, you write export default before an expression, a function declaration, or a class declaration.

+ +
export default ["Winter", "Spring", "Summer", "Autumn"];
+ +

It is possible to rename imported bindings using the word as.

+ +
import {days as dayNames} from "date-names";
+
+console.log(dayNames.length);
+// → 7
+ +

Another important difference is that ES module imports happen before a module’s script starts running. That means import declarations may not appear inside functions or blocks, and the names of dependencies must be quoted strings, not arbitrary expressions.

+ +

At the time of writing, the JavaScript community is in the process of adopting this module style. But it has been a slow process. It took a few years, after the format was specified, for browsers and Node.js to start supporting it. And though they mostly support it now, this support still has issues, and the discussion on how such modules should be distributed through NPM is still ongoing.

+ +

Many projects are written using ES modules and then automatically converted to some other format when published. We are in a transitional period in which two different module systems are used side by side, and it is useful to be able to read and write code in either of them.

+ +

Building and bundling

+ +

In fact, many JavaScript projects aren’t even, technically, written in JavaScript. There are extensions, such as the type checking dialect mentioned in Chapter 8, that are widely used. People also often start using planned extensions to the language long before they have been added to the platforms that actually run JavaScript.

+ +

To make this possible, they compile their code, translating it from their chosen JavaScript dialect to plain old JavaScript—or even to a past version of JavaScript—so that old browsers can run it.

+ +

Including a modular program that consists of 200 different files in a web page produces its own problems. If fetching a single file over the network takes 50 milliseconds, loading the whole program takes 10 seconds, or maybe half that if you can load several files simultaneously. That’s a lot of wasted time. Because fetching a single big file tends to be faster than fetching a lot of tiny ones, web programmers have started using tools that roll their programs (which they painstakingly split into modules) back into a single big file before they publish it to the Web. Such tools are called bundlers.

+ +

And we can go further. Apart from the number of files, the size of the files also determines how fast they can be transferred over the network. Thus, the JavaScript community has invented minifiers. These are tools that take a JavaScript program and make it smaller by automatically removing comments and whitespace, renaming bindings, and replacing pieces of code with equivalent code that take up less space.

+ +

So it is not uncommon for the code that you find in an NPM package or that runs on a web page to have gone through multiple stages of transformation—converted from modern JavaScript to historic JavaScript, from ES module format to CommonJS, bundled, and minified. We won’t go into the details of these tools in this book since they tend to be boring and change rapidly. Just be aware that the JavaScript code you run is often not the code as it was written.

+ +

Module design

+ +

Structuring programs is one of the subtler aspects of programming. Any nontrivial piece of functionality can be modeled in various ways.

+ +

Good program design is subjective—there are trade-offs involved and matters of taste. The best way to learn the value of well-structured design is to read or work on a lot of programs and notice what works and what doesn’t. Don’t assume that a painful mess is “just the way it is”. You can improve the structure of almost everything by putting more thought into it.

+ +

One aspect of module design is ease of use. If you are designing something that is intended to be used by multiple people—or even by yourself, in three months when you no longer remember the specifics of what you did—it is helpful if your interface is simple and predictable.

+ +

That may mean following existing conventions. A good example is the ini package. This module imitates the standard JSON object by providing parse and stringify (to write an INI file) functions, and, like JSON, converts between strings and plain objects. So the interface is small and familiar, and after you’ve worked with it once, you’re likely to remember how to use it.

+ +

Even if there’s no standard function or widely used package to imitate, you can keep your modules predictable by using simple data +structures and doing a single, focused thing. Many of the INI-file parsing modules on NPM provide a function that directly reads such a file from the hard disk and parses it, for example. This makes it impossible to use such modules in the browser, where we don’t have direct file system access, and adds complexity that would have been better addressed by composing the module with some file-reading function.

+ +

This points to another helpful aspect of module design—the ease with which something can be composed with other code. Focused modules that compute values are applicable in a wider range of programs than bigger modules that perform complicated actions with side effects. An INI file reader that insists on reading the file from disk is useless in a scenario where the file’s content comes from some other source.

+ +

Relatedly, stateful objects are sometimes useful or even necessary, but if something can be done with a function, use a function. Several of the INI file readers on NPM provide an interface style that requires you to first create an object, then load the file into your object, and finally use specialized methods to get at the results. This type of thing is common in the object-oriented tradition, and it’s terrible. Instead of making a single function call and moving on, you have to perform the ritual of moving your object through various states. And because the data is now wrapped in a specialized object type, all code that interacts with it has to know about that type, creating unnecessary interdependencies.

+ +

Often defining new data structures can’t be avoided—only a few basic ones are provided by the language standard, and many types of data have to be more complex than an array or a map. But when an array suffices, use an array.

+ +

An example of a slightly more complex data structure is the graph from Chapter 7. There is no single obvious way to represent a graph in JavaScript. In that chapter, we used an object whose properties hold arrays of strings—the other nodes reachable from that node.

+ +

There are several different pathfinding packages on NPM, but none of them uses this graph format. They usually allow the graph’s edges to have a weight, which is the cost or distance associated with it. That isn’t possible in our representation.

+ +

For example, there’s the dijkstrajs package. A well-known approach to pathfinding, quite similar to our findRoute function, is called Dijkstra’s algorithm, after Edsger Dijkstra, who first wrote it down. The js suffix is often added to package names to indicate the fact that they are written in JavaScript. This dijkstrajs package uses a graph format similar to ours, but instead of arrays, it uses objects whose property values are numbers—the weights of the edges.

+ +

So if we wanted to use that package, we’d have to make sure that our graph was stored in the format it expects. All edges get the same weight since our simplified model treats each road as having the same cost (one turn).

+ +
const {find_path} = require("dijkstrajs");
+
+let graph = {};
+for (let node of Object.keys(roadGraph)) {
+  let edges = graph[node] = {};
+  for (let dest of roadGraph[node]) {
+    edges[dest] = 1;
+  }
+}
+
+console.log(find_path(graph, "Post Office", "Cabin"));
+// → ["Post Office", "Alice's House", "Cabin"]
+ +

This can be a barrier to composition—when various packages are using different data structures to describe similar things, combining them is difficult. Therefore, if you want to design for composability, find out what data structures other people are using and, when possible, follow their example.

+ +

Summary

+ +

Modules provide structure to bigger programs by separating the code into pieces with clear interfaces and dependencies. The interface is the part of the module that’s visible from other modules, and the dependencies are the other modules that it makes use of.

+ +

Because JavaScript historically did not provide a module system, the CommonJS system was built on top of it. Then at some point it did get a built-in system, which now coexists uneasily with the CommonJS system.

+ +

A package is a chunk of code that can be distributed on its own. NPM is a repository of JavaScript packages. You can download all kinds of useful (and useless) packages from it.

+ +

Exercises

+ +

A modular robot

+ +

These are the bindings that the project from Chapter 7 creates:

+ +
roads
+buildGraph
+roadGraph
+VillageState
+runRobot
+randomPick
+randomRobot
+mailRoute
+routeRobot
+findRoute
+goalOrientedRobot
+ +

If you were to write that project as a modular program, what modules would you create? Which module would depend on which other module, and what would their interfaces look like?

+ +

Which pieces are likely to be available prewritten on NPM? Would you prefer to use an NPM package or write them yourself?

+ +
+ +

Here’s what I would have done (but again, there is no single right way to design a given module):

+ +

The code used to build the road graph lives in the graph module. Because I’d rather use dijkstrajs from NPM than our own pathfinding code, we’ll make this build the kind of graph data that dijkstrajs expects. This module exports a single function, buildGraph. I’d have buildGraph accept an array of two-element arrays, rather than strings containing hyphens, to make the module less dependent on the input format.

+ +

The roads module contains the raw road data (the roads array) and the roadGraph binding. This module depends on ./graph and exports the road graph.

+ +

The VillageState class lives in the state module. It depends on the ./roads module because it needs to be able to verify that a given road exists. It also needs randomPick. Since that is a three-line function, we could just put it into the state module as an internal helper function. But randomRobot needs it too. So we’d have to either duplicate it or put it into its own module. Since this function happens to exist on NPM in the random-item package, a good solution is to just make both modules depend on that. We can add the runRobot function to this module as well, since it’s small and closely related to state management. The module exports both the VillageState class and the runRobot function.

+ +

Finally, the robots, along with the values they depend on such as mailRoute, could go into an example-robots module, which depends on ./roads and exports the robot functions. To make it possible for goalOrientedRobot to do route-finding, this module also depends on dijkstrajs.

+ +

By offloading some work to NPM modules, the code became a little smaller. Each individual module does something rather simple and can be read on its own. Dividing code into modules also often suggests further improvements to the program’s design. In this case, it seems a little odd that the VillageState and the robots depend on a specific road graph. It might be a better idea to make the graph an argument to the state’s constructor and make the robots read it from the state object—this reduces dependencies (which is always good) and makes it possible to run simulations on different maps (which is even better).

+ +

Is it a good idea to use NPM modules for things that we could have written ourselves? In principle, yes—for nontrivial things like the pathfinding function you are likely to make mistakes and waste time writing them yourself. For tiny functions like random-item, writing them yourself is easy enough. But adding them wherever you need them does tend to clutter your modules.

+ +

However, you should also not underestimate the work involved in finding an appropriate NPM package. And even if you find one, it might not work well or may be missing some feature you need. On top of that, depending on NPM packages means you have to make sure they are installed, you have to distribute them with your program, and you might have to periodically upgrade them.

+ +

So again, this is a trade-off, and you can decide either way depending on how much the packages help you.

+ +
+ +

Roads module

+ +

Write a CommonJS module, based on the example from Chapter 7, that contains the array of roads and exports the graph data structure representing them as roadGraph. It should depend on a module ./graph, which exports a function buildGraph that is used to build the graph. This function expects an array of two-element arrays (the start and end points of the roads).

+ +
// Add dependencies and exports
+
+const roads = [
+  "Alice's House-Bob's House",   "Alice's House-Cabin",
+  "Alice's House-Post Office",   "Bob's House-Town Hall",
+  "Daria's House-Ernie's House", "Daria's House-Town Hall",
+  "Ernie's House-Grete's House", "Grete's House-Farm",
+  "Grete's House-Shop",          "Marketplace-Farm",
+  "Marketplace-Post Office",     "Marketplace-Shop",
+  "Marketplace-Town Hall",       "Shop-Town Hall"
+];
+ +
+ +

Since this is a CommonJS module, you have to use require to import the graph module. That was described as exporting a buildGraph function, which you can pick out of its interface object with a destructuring const declaration.

+ +

To export roadGraph, you add a property to the exports object. Because buildGraph takes a data structure that doesn’t precisely match roads, the splitting of the road strings must happen in your module.

+ +
+ +

Circular dependencies

+ +

A circular dependency is a situation where module A depends on B, and B also, directly or indirectly, depends on A. Many module systems simply forbid this because whichever order you choose for loading such modules, you cannot make sure that each module’s dependencies have been loaded before it runs.

+ +

CommonJS modules allow a limited form of cyclic dependencies. As long as the modules do not replace their default exports object and don’t access each other’s interface until after they finish loading, cyclic dependencies are okay.

+ +

The require function given earlier in this chapter supports this type of dependency cycle. Can you see how it handles cycles? What would go wrong when a module in a cycle does replace its default exports object?

+ +
+ +

The trick is that require adds modules to its cache before it starts loading the module. That way, if any require call made while it is running tries to load it, it is already known, and the current interface will be returned, rather than starting to load the module once more (which would eventually overflow the stack).

+ +

If a module overwrites its module.exports value, any other module that has received its interface value before it finished loading will have gotten hold of the default interface object (which is likely empty), rather than the intended interface value.

+ +
+
diff --git a/docs/11_async.html b/docs/11_async.html new file mode 100644 index 000000000..0fa419ee3 --- /dev/null +++ b/docs/11_async.html @@ -0,0 +1,681 @@ + + + + + Asynchronous Programming :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 11Asynchronous Programming

+ +
+ +

Who can wait quietly while the mud settles?
Who can remain still until the moment of action?

+ +
Laozi, Tao Te Ching
+ +
Picture of two crows on a branch
+ +

The central part of a computer, the part that carries out the individual steps that make up our programs, is called the processor. The programs we have seen so far are things that will keep the processor busy until they have finished their work. The speed at which something like a loop that manipulates numbers can be executed depends pretty much entirely on the speed of the processor.

+ +

But many programs interact with things outside of the processor. For example, they may communicate over a computer network or request data from the hard disk—which is a lot slower than getting it from memory.

+ +

When such a thing is happening, it would be a shame to let the processor sit idle—there might be some other work it could do in the meantime. In part, this is handled by your operating system, which will switch the processor between multiple running programs. But that doesn’t help when we want a single program to be able to make progress while it is waiting for a network request.

+ +

Asynchronicity

+ +

In a synchronous programming model, things happen one at a time. When you call a function that performs a long-running action, it returns only when the action has finished and it can return the result. This stops your program for the time the action takes.

+ +

An asynchronous model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk).

+ +

We can compare synchronous and asynchronous programming using a small example: a program that fetches two resources from the network and then combines results.

+ +

In a synchronous environment, where the request function returns only after it has done its work, the easiest way to perform this task is to make the requests one after the other. This has the drawback that the second request will be started only when the first has finished. The total time taken will be at least the sum of the two response times.

+ +

The solution to this problem, in a synchronous system, is to start additional threads of control. A thread is another running program whose execution may be interleaved with other programs by the operating system—since most modern computers contain multiple processors, multiple threads may even run at the same time, on different processors. A second thread could start the second request, and then both threads wait for their results to come back, after which they resynchronize to combine their results.

+ +

In the following diagram, the thick lines represent time the program spends running normally, and the thin lines represent time spent waiting for the network. In the synchronous model, the time taken by the network is part of the timeline for a given thread of control. In the asynchronous model, starting a network action conceptually causes a split in the timeline. The program that initiated the action continues running, and the action happens alongside it, notifying the program when it is finished.

Control flow for synchronous and asynchronous programming
+ +

Another way to describe the difference is that waiting for actions to finish is implicit in the synchronous model, while it is explicit, under our control, in the asynchronous one.

+ +

Asynchronicity cuts both ways. It makes expressing programs that do not fit the straight-line model of control easier, but it can also make expressing programs that do follow a straight line more awkward. We’ll see some ways to address this awkwardness later in the chapter.

+ +

Both of the important JavaScript programming platforms—browsers and Node.js—make operations that might take a while asynchronous, rather than relying on threads. Since programming with threads is notoriously hard (understanding what a program does is much more difficult when it’s doing multiple things at once), this is generally considered a good thing.

+ +

Crow tech

+ +

Most people are aware of the fact that crows are very smart birds. They can use tools, plan ahead, remember things, and even communicate these things among themselves.

+ +

What most people don’t know is that they are capable of many things that they keep well hidden from us. I’ve been told by a reputable (if somewhat eccentric) expert on corvids that crow technology is not far behind human technology, and they are catching up.

+ +

For example, many crow cultures have the ability to construct computing devices. These are not electronic, as human computing devices are, but operate through the actions of tiny insects, a species closely related to the termite, which has developed a symbiotic relationship with the crows. The birds provide them with food, and in return the insects build and operate their complex colonies that, with the help of the living creatures inside them, perform computations.

+ +

Such colonies are usually located in big, long-lived nests. The birds and insects work together to build a network of bulbous clay structures, hidden between the twigs of the nest, in which the insects live and work.

+ +

To communicate with other devices, these machines use light signals. The crows embed pieces of reflective material in special communication stalks, and the insects aim these to reflect light at another nest, encoding data as a sequence of quick flashes. This means that only nests that have an unbroken visual connection can communicate.

+ +

Our friend the corvid expert has mapped the network of crow nests in the village of Hières-sur-Amby, on the banks of the river Rhône. This map shows the nests and their connections:

A network of crow nests in a small village
+ +

In an astounding example of convergent evolution, crow computers run JavaScript. In this chapter we’ll write some basic networking functions for them.

+ +

Callbacks

+ +

One approach to asynchronous programming is to make functions that perform a slow action take an extra argument, a callback +function. The action is started, and when it finishes, the callback function is called with the result.

+ +

As an example, the setTimeout function, available both in Node.js and in browsers, waits a given number of milliseconds (a second is a thousand milliseconds) and then calls a function.

+ +
setTimeout(() => console.log("Tick"), 500);
+ +

Waiting is not generally a very important type of work, but it can be useful when doing something like updating an animation or checking whether something is taking longer than a given amount of time.

+ +

Performing multiple asynchronous actions in a row using callbacks means that you have to keep passing new functions to handle the continuation of the computation after the actions.

+ +

Most crow nest computers have a long-term data storage bulb, where pieces of information are etched into twigs so that they can be retrieved later. Etching, or finding a piece of data, takes a moment, so the interface to long-term storage is asynchronous and uses callback functions.

+ +

Storage bulbs store pieces of JSON-encodable data under names. A crow might store information about the places where it’s hidden food under the name "food caches", which could hold an array of names that point at other pieces of data, describing the actual cache. To look up a food cache in the storage bulbs of the Big Oak nest, a crow could run code like this:

+ +
import {bigOak} from "./crow-tech";
+
+bigOak.readStorage("food caches", caches => {
+  let firstCache = caches[0];
+  bigOak.readStorage(firstCache, info => {
+    console.log(info);
+  });
+});
+ +

(All binding names and strings have been translated from crow language to English.)

+ +

This style of programming is workable, but the indentation level increases with each asynchronous action because you end up in another function. Doing more complicated things, such as running multiple actions at the same time, can get a little awkward.

+ +

Crow nest computers are built to communicate using request-response pairs. That means one nest sends a message to another nest, which then immediately sends a message back, confirming receipt and possibly including a reply to a question asked in the message.

+ +

Each message is tagged with a type, which determines how it is handled. Our code can define handlers for specific request types, and when such a request comes in, the handler is called to produce a response.

+ +

The interface exported by the "./crow-tech" module provides callback-based functions for communication. Nests have a send method that sends off a request. It expects the name of the target nest, the type of the request, and the content of the request as its first three arguments, and it expects a function to call when a response comes in as its fourth and last argument.

+ +
bigOak.send("Cow Pasture", "note", "Let's caw loudly at 7PM",
+            () => console.log("Note delivered."));
+ +

But to make nests capable of receiving that request, we first have to define a request type named "note". The code that handles the requests has to run not just on this nest-computer but on all nests that can receive messages of this type. We’ll just assume that a crow flies over and installs our handler code on all the nests.

+ +
import {defineRequestType} from "./crow-tech";
+
+defineRequestType("note", (nest, content, source, done) => {
+  console.log(`${nest.name} received note: ${content}`);
+  done();
+});
+ +

The defineRequestType function defines a new type of request. The example adds support for "note" requests, which just sends a note to a given nest. Our implementation calls console.log so that we can verify that the request arrived. Nests have a name property that holds their name.

+ +

The fourth argument given to the handler, done, is a callback function that it must call when it is done with the request. If we had used the handler’s return value as the response value, that would mean that a request handler can’t itself perform asynchronous actions. A function doing asynchronous work typically returns before the work is done, having arranged for a callback to be called when it completes. So we need some asynchronous mechanism—in this case, another callback function—to signal when a response is available.

+ +

In a way, asynchronicity is contagious. Any function that calls a function that works asynchronously must itself be asynchronous, using a callback or similar mechanism to deliver its result. Calling a callback is somewhat more involved and error-prone than simply returning a value, so needing to structure large parts of your program that way is not great.

+ +

Promises

+ +

Working with abstract concepts is often easier when those concepts can be represented by values. In the case of asynchronous actions, you could, instead of arranging for a function to be called at some point in the future, return an object that represents this future event.

+ +

This is what the standard class Promise is for. A promise is an asynchronous action that may complete at some point and produce a value. It is able to notify anyone who is interested when its value is available.

+ +

The easiest way to create a promise is by calling Promise.resolve. This function ensures that the value you give it is wrapped in a promise. If it’s already a promise, it is simply returned—otherwise, you get a new promise that immediately finishes with your value as its result.

+ +
let fifteen = Promise.resolve(15);
+fifteen.then(value => console.log(`Got ${value}`));
+// → Got 15
+ +

To get the result of a promise, you can use its then method. This registers a callback function to be called when the promise resolves and produces a value. You can add multiple callbacks to a single promise, and they will be called, even if you add them after the promise has already resolved (finished).

+ +

But that’s not all the then method does. It returns another promise, which resolves to the value that the handler function returns or, if that returns a promise, waits for that promise and then resolves to its result.

+ +

It is useful to think of promises as a device to move values into an asynchronous reality. A normal value is simply there. A promised value is a value that might already be there or might appear at some point in the future. Computations defined in terms of promises act on such wrapped values and are executed asynchronously as the values become available.

+ +

To create a promise, you can use Promise as a constructor. It has a somewhat odd interface—the constructor expects a function as argument, which it immediately calls, passing it a function that it can use to resolve the promise. It works this way, instead of for example with a resolve method, so that only the code that created the promise can resolve it.

+ +

This is how you’d create a promise-based interface for the readStorage function:

+ +
function storage(nest, name) {
+  return new Promise(resolve => {
+    nest.readStorage(name, result => resolve(result));
+  });
+}
+
+storage(bigOak, "enemies")
+  .then(value => console.log("Got", value));
+ +

This asynchronous function returns a meaningful value. This is the main advantage of promises—they simplify the use of asynchronous functions. Instead of having to pass around callbacks, promise-based functions look similar to regular ones: they take input as arguments and return their output. The only difference is that the output may not be available yet.

+ +

Failure

+ +

Regular JavaScript computations can fail by throwing an exception. Asynchronous computations often need something like that. A network request may fail, or some code that is part of the asynchronous computation may throw an exception.

+ +

One of the most pressing problems with the callback style of asynchronous programming is that it makes it extremely difficult to make sure failures are properly reported to the callbacks.

+ +

A widely used convention is that the first argument to the callback is used to indicate that the action failed, and the second contains the value produced by the action when it was successful. Such callback functions must always check whether they received an exception and make sure that any problems they cause, including exceptions thrown by functions they call, are caught and given to the right function.

+ +

Promises make this easier. They can be either resolved (the action finished successfully) or rejected (it failed). Resolve handlers (as registered with then) are called only when the action is successful, and rejections are automatically propagated to the new promise that is returned by then. And when a handler throws an exception, this automatically causes the promise produced by its then call to be rejected. So if any element in a chain of asynchronous actions fails, the outcome of the whole chain is marked as rejected, and no success handlers are called beyond the point where it failed.

+ +

Much like resolving a promise provides a value, rejecting one also provides one, usually called the reason of the rejection. When an exception in a handler function causes the rejection, the exception value is used as the reason. Similarly, when a handler returns a promise that is rejected, that rejection flows into the next promise. There’s a Promise.reject function that creates a new, immediately rejected promise.

+ +

To explicitly handle such rejections, promises have a catch method that registers a handler to be called when the promise is rejected, similar to how then handlers handle normal resolution. It’s also very much like then in that it returns a new promise, which resolves to the original promise’s value if it resolves normally and to the result of the catch handler otherwise. If a catch handler throws an error, the new promise is also rejected.

+ +

As a shorthand, then also accepts a rejection handler as a second argument, so you can install both types of handlers in a single method call.

+ +

A function passed to the Promise constructor receives a second argument, alongside the resolve function, which it can use to reject the new promise.

+ +

The chains of promise values created by calls to then and catch can be seen as a pipeline through which asynchronous values or failures move. Since such chains are created by registering handlers, each link has a success handler or a rejection handler (or both) associated with it. Handlers that don’t match the type of outcome (success or failure) are ignored. But those that do match are called, and their outcome determines what kind of value comes next—success when it returns a non-promise value, rejection when it throws an exception, and the outcome of a promise when it returns one of those.

+ +
new Promise((_, reject) => reject(new Error("Fail")))
+  .then(value => console.log("Handler 1"))
+  .catch(reason => {
+    console.log("Caught failure " + reason);
+    return "nothing";
+  })
+  .then(value => console.log("Handler 2", value));
+// → Caught failure Error: Fail
+// → Handler 2 nothing
+ +

Much like an uncaught exception is handled by the environment, JavaScript environments can detect when a promise rejection isn’t handled and will report this as an error.

+ +

Networks are hard

+ +

Occasionally, there isn’t enough light for the crows’ mirror systems to transmit a signal or something is blocking the path of the signal. It is possible for a signal to be sent but never received.

+ +

As it is, that will just cause the callback given to send to never be called, which will probably cause the program to stop without even noticing there is a problem. It would be nice if, after a given period of not getting a response, a request would time out and report failure.

+ +

Often, transmission failures are random accidents, like a car’s headlight interfering with the light signals, and simply retrying the request may cause it to succeed. So while we’re at it, let’s make our request function automatically retry the sending of the request a few times before it gives up.

+ +

And, since we’ve established that promises are a good thing, we’ll also make our request function return a promise. In terms of what they can express, callbacks and promises are equivalent. Callback-based functions can be wrapped to expose a promise-based interface, and vice versa.

+ +

Even when a request and its response are successfully delivered, the response may indicate failure—for example, if the request tries to use a request type that hasn’t been defined or the handler throws an error. To support this, send and defineRequestType follow the convention mentioned before, where the first argument passed to callbacks is the failure reason, if any, and the second is the actual result.

+ +

These can be translated to promise resolution and rejection by our wrapper.

+ +
class Timeout extends Error {}
+
+function request(nest, target, type, content) {
+  return new Promise((resolve, reject) => {
+    let done = false;
+    function attempt(n) {
+      nest.send(target, type, content, (failed, value) => {
+        done = true;
+        if (failed) reject(failed);
+        else resolve(value);
+      });
+      setTimeout(() => {
+        if (done) return;
+        else if (n < 3) attempt(n + 1);
+        else reject(new Timeout("Timed out"));
+      }, 250);
+    }
+    attempt(1);
+  });
+}
+ +

Because promises can be resolved (or rejected) only once, this will work. The first time resolve or reject is called determines the outcome of the promise, and any further calls, such as the timeout arriving after the request finishes or a request coming back after another request finished, are ignored.

+ +

To build an asynchronous loop, for the retries, we need to use a recursive function—a regular loop doesn’t allow us to stop and wait for an asynchronous action. The attempt function makes a single attempt to send a request. It also sets a timeout that, if no response has come back after 250 milliseconds, either starts the next attempt or, if this was the fourth attempt, rejects the promise with an instance of Timeout as the reason.

+ +

Retrying every quarter-second and giving up when no response has come in after a second is definitely somewhat arbitrary. It is even possible, if the request did come through but the handler is just taking a bit longer, for requests to be delivered multiple times. We’ll write our handlers with that problem in mind—duplicate messages should be harmless.

+ +

In general, we will not be building a world-class, robust network today. But that’s okay—crows don’t have very high expectations yet when it comes to computing.

+ +

To isolate ourselves from callbacks altogether, we’ll go ahead and also define a wrapper for defineRequestType that allows the handler function to return a promise or plain value and wires that up to the callback for us.

+ +
function requestType(name, handler) {
+  defineRequestType(name, (nest, content, source,
+                           callback) => {
+    try {
+      Promise.resolve(handler(nest, content, source))
+        .then(response => callback(null, response),
+              failure => callback(failure));
+    } catch (exception) {
+      callback(exception);
+    }
+  });
+}
+ +

Promise.resolve is used to convert the value returned by handler to a promise if it isn’t already.

+ +

Note that the call to handler had to be wrapped in a try block to make sure any exception it raises directly is given to the callback. This nicely illustrates the difficulty of properly handling errors with raw callbacks—it is easy to forget to properly route exceptions like that, and if you don’t do it, failures won’t get reported to the right callback. Promises make this mostly automatic and thus less error-prone.

+ +

Collections of promises

+ +

Each nest computer keeps an array of other nests within transmission distance in its neighbors property. To check which of those are currently reachable, you could write a function that tries to send a "ping" request (a request that simply asks for a response) to each of them and see which ones come back.

+ +

When working with collections of promises running at the same time, the Promise.all function can be useful. It returns a promise that waits for all of the promises in the array to resolve and then resolves to an array of the values that these promises produced (in the same order as the original array). If any promise is rejected, the result of Promise.all is itself rejected.

+ +
requestType("ping", () => "pong");
+
+function availableNeighbors(nest) {
+  let requests = nest.neighbors.map(neighbor => {
+    return request(nest, neighbor, "ping")
+      .then(() => true, () => false);
+  });
+  return Promise.all(requests).then(result => {
+    return nest.neighbors.filter((_, i) => result[i]);
+  });
+}
+ +

When a neighbor isn’t available, we don’t want the entire combined promise to fail since then we still wouldn’t know anything. So the function that is mapped over the set of neighbors to turn them into request promises attaches handlers that make successful requests produce true and rejected ones produce false.

+ +

In the handler for the combined promise, filter is used to remove those elements from the neighbors array whose corresponding value is false. This makes use of the fact that filter passes the array index of the current element as a second argument to its filtering function (map, some, and similar higher-order array methods do the same).

+ +

Network flooding

+ +

The fact that nests can talk only to their neighbors greatly inhibits the usefulness of this network.

+ +

For broadcasting information to the whole network, one solution is to set up a type of request that is automatically forwarded to neighbors. These neighbors then in turn forward it to their neighbors, until the whole network has received the message.

+ +
import {everywhere} from "./crow-tech";
+
+everywhere(nest => {
+  nest.state.gossip = [];
+});
+
+function sendGossip(nest, message, exceptFor = null) {
+  nest.state.gossip.push(message);
+  for (let neighbor of nest.neighbors) {
+    if (neighbor == exceptFor) continue;
+    request(nest, neighbor, "gossip", message);
+  }
+}
+
+requestType("gossip", (nest, message, source) => {
+  if (nest.state.gossip.includes(message)) return;
+  console.log(`${nest.name} received gossip '${
+               message}' from ${source}`);
+  sendGossip(nest, message, source);
+});
+ +

To avoid sending the same message around the network forever, each nest keeps an array of gossip strings that it has already seen. To define this array, we use the everywhere function—which runs code on every nest—to add a property to the nest’s state object, which is where we’ll keep nest-local state.

+ +

When a nest receives a duplicate gossip message, which is very likely to happen with everybody blindly resending them, it ignores it. But when it receives a new message, it excitedly tells all its neighbors except for the one who sent it the message.

+ +

This will cause a new piece of gossip to spread through the network like an ink stain in water. Even when some connections aren’t currently working, if there is an alternative route to a given nest, the gossip will reach it through there.

+ +

This style of network communication is called flooding—it floods the network with a piece of information until all nodes have it.

+ +

We can call sendGossip to see a message flow through the village.

+ +
sendGossip(bigOak, "Kids with airgun in the park");
+ +

Message routing

+ +

If a given node wants to talk to a single other node, flooding is not a very efficient approach. Especially when the network is big, that would lead to a lot of useless data transfers.

+ +

An alternative approach is to set up a way for messages to hop from node to node until they reach their destination. The difficulty with that is it requires knowledge about the layout of the network. To send a request in the direction of a faraway nest, it is necessary to know which neighboring nest gets it closer to its destination. Sending it in the wrong direction will not do much good.

+ +

Since each nest knows only about its direct neighbors, it doesn’t have the information it needs to compute a route. We must somehow spread the information about these connections to all nests, preferably in a way that allows it to change over time, when nests are abandoned or new nests are built.

+ +

We can use flooding again, but instead of checking whether a given message has already been received, we now check whether the new set of neighbors for a given nest matches the current set we have for it.

+ +
requestType("connections", (nest, {name, neighbors},
+                            source) => {
+  let connections = nest.state.connections;
+  if (JSON.stringify(connections.get(name)) ==
+      JSON.stringify(neighbors)) return;
+  connections.set(name, neighbors);
+  broadcastConnections(nest, name, source);
+});
+
+function broadcastConnections(nest, name, exceptFor = null) {
+  for (let neighbor of nest.neighbors) {
+    if (neighbor == exceptFor) continue;
+    request(nest, neighbor, "connections", {
+      name,
+      neighbors: nest.state.connections.get(name)
+    });
+  }
+}
+
+everywhere(nest => {
+  nest.state.connections = new Map;
+  nest.state.connections.set(nest.name, nest.neighbors);
+  broadcastConnections(nest, nest.name);
+});
+ +

The comparison uses JSON.stringify because ==, on objects or arrays, will return true only when the two are the exact same value, which is not what we need here. Comparing the JSON strings is a crude but effective way to compare their content.

+ +

The nodes immediately start broadcasting their connections, which should, unless some nests are completely unreachable, quickly give every nest a map of the current network graph.

+ +

A thing you can do with graphs is find routes in them, as we saw in Chapter 7. If we have a route toward a message’s destination, we know which direction to send it in.

+ +

This findRoute function, which greatly resembles the findRoute from Chapter 7, searches for a way to reach a given node in the network. But instead of returning the whole route, it just returns the next step. That next nest will itself, using its current information about the network, decide where it sends the message.

+ +
function findRoute(from, to, connections) {
+  let work = [{at: from, via: null}];
+  for (let i = 0; i < work.length; i++) {
+    let {at, via} = work[i];
+    for (let next of connections.get(at) || []) {
+      if (next == to) return via;
+      if (!work.some(w => w.at == next)) {
+        work.push({at: next, via: via || next});
+      }
+    }
+  }
+  return null;
+}
+ +

Now we can build a function that can send long-distance messages. If the message is addressed to a direct neighbor, it is delivered as usual. If not, it is packaged in an object and sent to a neighbor that is closer to the target, using the "route" request type, which will cause that neighbor to repeat the same behavior.

+ +
function routeRequest(nest, target, type, content) {
+  if (nest.neighbors.includes(target)) {
+    return request(nest, target, type, content);
+  } else {
+    let via = findRoute(nest.name, target,
+                        nest.state.connections);
+    if (!via) throw new Error(`No route to ${target}`);
+    return request(nest, via, "route",
+                   {target, type, content});
+  }
+}
+
+requestType("route", (nest, {target, type, content}) => {
+  return routeRequest(nest, target, type, content);
+});
+ +

We can now send a message to the nest in the church tower, which is four network hops removed.

+ +
routeRequest(bigOak, "Church Tower", "note",
+             "Incoming jackdaws!");
+ +

We’ve constructed several layers of functionality on top of a primitive communication system to make it convenient to use. This is a nice (though simplified) model of how real computer networks work.

+ +

A distinguishing property of computer networks is that they aren’t reliable—abstractions built on top of them can help, but you can’t abstract away network failure. So network programming is typically very much about anticipating and dealing with failures.

+ +

Async functions

+ +

To store important information, crows are known to duplicate it across nests. That way, when a hawk destroys a nest, the information isn’t lost.

+ +

To retrieve a given piece of information that it doesn’t have in its own storage bulb, a nest computer might consult random other nests in the network until it finds one that has it.

+ +
requestType("storage", (nest, name) => storage(nest, name));
+
+function findInStorage(nest, name) {
+  return storage(nest, name).then(found => {
+    if (found != null) return found;
+    else return findInRemoteStorage(nest, name);
+  });
+}
+
+function network(nest) {
+  return Array.from(nest.state.connections.keys());
+}
+
+function findInRemoteStorage(nest, name) {
+  let sources = network(nest).filter(n => n != nest.name);
+  function next() {
+    if (sources.length == 0) {
+      return Promise.reject(new Error("Not found"));
+    } else {
+      let source = sources[Math.floor(Math.random() *
+                                      sources.length)];
+      sources = sources.filter(n => n != source);
+      return routeRequest(nest, source, "storage", name)
+        .then(value => value != null ? value : next(),
+              next);
+    }
+  }
+  return next();
+}
+ +

Because connections is a Map, Object.keys doesn’t work on it. It has a keys method, but that returns an iterator rather than an array. An iterator (or iterable value) can be converted to an array with the Array.from function.

+ +

Even with promises this is some rather awkward code. Multiple asynchronous actions are chained together in non-obvious ways. We again need a recursive function (next) to model looping through the nests.

+ +

And the thing the code actually does is completely linear—it always waits for the previous action to complete before starting the next one. In a synchronous programming model, it’d be simpler to express.

+ +

The good news is that JavaScript allows you to write pseudo-synchronous code to describe asynchronous computation. An async function is a function that implicitly returns a promise and that can, in its body, await other promises in a way that looks synchronous.

+ +

We can rewrite findInStorage like this:

+ +
async function findInStorage(nest, name) {
+  let local = await storage(nest, name);
+  if (local != null) return local;
+
+  let sources = network(nest).filter(n => n != nest.name);
+  while (sources.length > 0) {
+    let source = sources[Math.floor(Math.random() *
+                                    sources.length)];
+    sources = sources.filter(n => n != source);
+    try {
+      let found = await routeRequest(nest, source, "storage",
+                                     name);
+      if (found != null) return found;
+    } catch (_) {}
+  }
+  throw new Error("Not found");
+}
+ +

An async function is marked by the word async before the function keyword. Methods can also be made async by writing async before their name. When such a function or method is called, it returns a promise. As soon as the body returns something, that promise is resolved. If it throws an exception, the promise is rejected.

+ +
findInStorage(bigOak, "events on 2017-12-21")
+  .then(console.log);
+ +

Inside an async function, the word await can be put in front of an expression to wait for a promise to resolve and only then continue the execution of the function.

+ +

Such a function no longer, like a regular JavaScript function, runs from start to completion in one go. Instead, it can be frozen at any point that has an await, and can be resumed at a later time.

+ +

For non-trivial asynchronous code, this notation is usually more convenient than directly using promises. Even if you need to do something that doesn’t fit the synchronous model, such as perform multiple actions at the same time, it is easy to combine await with the direct use of promises.

+ +

Generators

+ +

This ability of functions to be paused and then resumed again is not exclusive to async functions. JavaScript also has a feature called generator functions. These are similar, but without the promises.

+ +

When you define a function with function* (placing an asterisk after the word function), it becomes a generator. When you call a generator, it returns an iterator, which we already saw in Chapter 6.

+ +
function* powers(n) {
+  for (let current = n;; current *= n) {
+    yield current;
+  }
+}
+
+for (let power of powers(3)) {
+  if (power > 50) break;
+  console.log(power);
+}
+// → 3
+// → 9
+// → 27
+ +

Initially, when you call powers, the function is frozen at its start. Every time you call next on the iterator, the function runs until it hits a yield expression, which pauses it and causes the yielded value to become the next value produced by the iterator. When the function returns (the one in the example never does), the iterator is done.

+ +

Writing iterators is often much easier when you use generator functions. The iterator for the Group class (from the exercise in Chapter 6) can be written with this generator:

+ +
Group.prototype[Symbol.iterator] = function*() {
+  for (let i = 0; i < this.members.length; i++) {
+    yield this.members[i];
+  }
+};
+ +

There’s no longer a need to create an object to hold the iteration state—generators automatically save their local state every time they yield.

+ +

Such yield expressions may occur only directly in the generator function itself and not in an inner function you define inside of it. The state a generator saves, when yielding, is only its local environment and the position where it yielded.

+ +

An async function is a special type of generator. It produces a promise when called, which is resolved when it returns (finishes) and rejected when it throws an exception. Whenever it yields (awaits) a promise, the result of that promise (value or thrown exception) is the result of the await expression.

+ +

The event loop

+ +

Asynchronous programs are executed piece by piece. Each piece may start some actions and schedule code to be executed when the action finishes or fails. In between these pieces, the program sits idle, waiting for the next action.

+ +

So callbacks are not directly called by the code that scheduled them. If I call setTimeout from within a function, that function will have returned by the time the callback function is called. And when the callback returns, control does not go back to the function that scheduled it.

+ +

Asynchronous behavior happens on its own empty function call +stack. This is one of the reasons that, without promises, managing exceptions across asynchronous code is hard. Since each callback starts with a mostly empty stack, your catch handlers won’t be on the stack when they throw an exception.

+ +
try {
+  setTimeout(() => {
+    throw new Error("Woosh");
+  }, 20);
+} catch (_) {
+  // This will not run
+  console.log("Caught!");
+}
+ +

No matter how closely together events—such as timeouts or incoming requests—happen, a JavaScript environment will run only one program at a time. You can think of this as it running a big loop around your program, called the event loop. When there’s nothing to be done, that loop is stopped. But as events come in, they are added to a queue, and their code is executed one after the other. Because no two things run at the same time, slow-running code might delay the handling of other events.

+ +

This example sets a timeout but then dallies until after the timeout’s intended point of time, causing the timeout to be late.

+ +
let start = Date.now();
+setTimeout(() => {
+  console.log("Timeout ran at", Date.now() - start);
+}, 20);
+while (Date.now() < start + 50) {}
+console.log("Wasted time until", Date.now() - start);
+// → Wasted time until 50
+// → Timeout ran at 55
+ +

Promises always resolve or reject as a new event. Even if a promise is already resolved, waiting for it will cause your callback to run after the current script finishes, rather than right away.

+ +
Promise.resolve("Done").then(console.log);
+console.log("Me first!");
+// → Me first!
+// → Done
+ +

In later chapters we’ll see various other types of events that run on the event loop.

+ +

Asynchronous bugs

+ +

When your program runs synchronously, in a single go, there are no state changes happening except those that the program itself makes. For asynchronous programs this is different—they may have gaps in their execution during which other code can run.

+ +

Let’s look at an example. One of the hobbies of our crows is to count the number of chicks that hatch throughout the village every year. Nests store this count in their storage bulbs. The following code tries to enumerate the counts from all the nests for a given year:

+ +
function anyStorage(nest, source, name) {
+  if (source == nest.name) return storage(nest, name);
+  else return routeRequest(nest, source, "storage", name);
+}
+
+async function chicks(nest, year) {
+  let list = "";
+  await Promise.all(network(nest).map(async name => {
+    list += `${name}: ${
+      await anyStorage(nest, name, `chicks in ${year}`)
+    }\n`;
+  }));
+  return list;
+}
+ +

The async name => part shows that arrow functions can also be made async by putting the word async in front of them.

+ +

The code doesn’t immediately look suspicious...it maps the async arrow function over the set of nests, creating an array of promises, and then uses Promise.all to wait for all of these before returning the list they build up.

+ +

But it is seriously broken. It’ll always return only a single line of output, listing the nest that was slowest to respond.

+ +
chicks(bigOak, 2017).then(console.log);
+ +

Can you work out why?

+ +

The problem lies in the += operator, which takes the current value of list at the time where the statement starts executing and then, when the await finishes, sets the list binding to be that value plus the added string.

+ +

But between the time where the statement starts executing and the time where it finishes there’s an asynchronous gap. The map expression runs before anything has been added to the list, so each of the += operators starts from an empty string and ends up, when its storage retrieval finishes, setting list to a single-line list—the result of adding its line to the empty string.

+ +

This could have easily been avoided by returning the lines from the mapped promises and calling join on the result of Promise.all, instead of building up the list by changing a binding. As usual, computing new values is less error-prone than changing existing values.

+ +
async function chicks(nest, year) {
+  let lines = network(nest).map(async name => {
+    return name + ": " +
+      await anyStorage(nest, name, `chicks in ${year}`);
+  });
+  return (await Promise.all(lines)).join("\n");
+}
+ +

Mistakes like this are easy to make, especially when using await, and you should be aware of where the gaps in your code occur. An advantage of JavaScript’s explicit asynchronicity (whether through callbacks, promises, or await) is that spotting these gaps is relatively easy.

+ +

Summary

+ +

Asynchronous programming makes it possible to express waiting for long-running actions without freezing the program during these actions. JavaScript environments typically implement this style of programming using callbacks, functions that are called when the actions complete. An event loop schedules such callbacks to be called when appropriate, one after the other, so that their execution does not overlap.

+ +

Programming asynchronously is made easier by promises, objects that represent actions that might complete in the future, and async functions, which allow you to write an asynchronous program as if it were synchronous.

+ +

Exercises

+ +

Tracking the scalpel

+ +

The village crows own an old scalpel that they occasionally use on special missions—say, to cut through screen doors or packaging. To be able to quickly track it down, every time the scalpel is moved to another nest, an entry is added to the storage of both the nest that had it and the nest that took it, under the name "scalpel", with its new location as the value.

+ +

This means that finding the scalpel is a matter of following the breadcrumb trail of storage entries, until you find a nest where that points at the nest itself.

+ +

Write an async function locateScalpel that does this, starting at the nest on which it runs. You can use the anyStorage function defined earlier to access storage in arbitrary nests. The scalpel has been going around long enough that you may assume that every nest has a "scalpel" entry in its data storage.

+ +

Next, write the same function again without using async and await.

+ +

Do request failures properly show up as rejections of the returned promise in both versions? How?

+ +
async function locateScalpel(nest) {
+  // Your code here.
+}
+
+function locateScalpel2(nest) {
+  // Your code here.
+}
+
+locateScalpel(bigOak).then(console.log);
+// → Butcher Shop
+ +
+ +

This can be done with a single loop that searches through the nests, moving forward to the next when it finds a value that doesn’t match the current nest’s name and returning the name when it finds a matching value. In the async function, a regular for or while loop can be used.

+ +

To do the same in a plain function, you will have to build your loop using a recursive function. The easiest way to do this is to have that function return a promise by calling then on the promise that retrieves the storage value. Depending on whether that value matches the name of the current nest, the handler returns that value or a further promise created by calling the loop function again.

+ +

Don’t forget to start the loop by calling the recursive function once from the main function.

+ +

In the async function, rejected promises are converted to exceptions by await. When an async function throws an exception, its promise is rejected. So that works.

+ +

If you implemented the non-async function as outlined earlier, the way then works also automatically causes a failure to end up in the returned promise. If a request fails, the handler passed to then isn’t called, and the promise it returns is rejected with the same reason.

+ +
+ +

Building Promise.all

+ +

Given an array of promises, Promise.all returns a promise that waits for all of the promises in the array to finish. It then succeeds, yielding an array of result values. If a promise in the array fails, the promise returned by all fails too, with the failure reason from the failing promise.

+ +

Implement something like this yourself as a regular function called Promise_all.

+ +

Remember that after a promise has succeeded or failed, it can’t succeed or fail again, and further calls to the functions that resolve it are ignored. This can simplify the way you handle failure of your promise.

+ +
function Promise_all(promises) {
+  return new Promise((resolve, reject) => {
+    // Your code here.
+  });
+}
+
+// Test code.
+Promise_all([]).then(array => {
+  console.log("This should be []:", array);
+});
+function soon(val) {
+  return new Promise(resolve => {
+    setTimeout(() => resolve(val), Math.random() * 500);
+  });
+}
+Promise_all([soon(1), soon(2), soon(3)]).then(array => {
+  console.log("This should be [1, 2, 3]:", array);
+});
+Promise_all([soon(1), Promise.reject("X"), soon(3)])
+  .then(array => {
+    console.log("We should not get here");
+  })
+  .catch(error => {
+    if (error != "X") {
+      console.log("Unexpected failure:", error);
+    }
+  });
+ +
+ +

The function passed to the Promise constructor will have to call then on each of the promises in the given array. When one of them succeeds, two things need to happen. The resulting value needs to be stored in the correct position of a result array, and we must check whether this was the last pending promise and finish our own promise if it was.

+ +

The latter can be done with a counter that is initialized to the length of the input array and from which we subtract 1 every time a promise succeeds. When it reaches 0, we are done. Make sure you take into account the situation where the input array is empty (and thus no promise will ever resolve).

+ +

Handling failure requires some thought but turns out to be extremely simple. Just pass the reject function of the wrapping promise to each of the promises in the array as a catch handler or as a second argument to then so that a failure in one of them triggers the rejection of the whole wrapper promise.

+ +
+
diff --git a/docs/12_language.html b/docs/12_language.html new file mode 100644 index 000000000..aa32b6168 --- /dev/null +++ b/docs/12_language.html @@ -0,0 +1,502 @@ + + + + + Project: A Programming Language :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 12Project: A Programming Language

+ +
+ +

The evaluator, which determines the meaning of expressions in a programming language, is just another program.

+ +
Hal Abelson and Gerald Sussman, Structure and Interpretation of Computer Programs
+ +
Picture of an egg with smaller eggs inside
+ +

Building your own programming language is surprisingly easy (as long as you do not aim too high) and very enlightening.

+ +

The main thing I want to show in this chapter is that there is no magic involved in building your own language. I’ve often felt that some human inventions were so immensely clever and complicated that I’d never be able to understand them. But with a little reading and experimenting, they often turn out to be quite mundane.

+ +

We will build a programming language called Egg. It will be a tiny, simple language—but one that is powerful enough to express any computation you can think of. It will allow simple abstraction based on functions.

+ +

Parsing

+ +

The most immediately visible part of a programming language is its syntax, or notation. A parser is a program that reads a piece of text and produces a data structure that reflects the structure of the program contained in that text. If the text does not form a valid program, the parser should point out the error.

+ +

Our language will have a simple and uniform syntax. Everything in Egg is an expression. An expression can be the name of a binding, a number, a string, or an application. Applications are used for function calls but also for constructs such as if or while.

+ +

To keep the parser simple, strings in Egg do not support anything like backslash escapes. A string is simply a sequence of characters that are not double quotes, wrapped in double quotes. A number is a sequence of digits. Binding names can consist of any character that is not whitespace and that does not have a special meaning in the syntax.

+ +

Applications are written the way they are in JavaScript, by putting parentheses after an expression and having any number of arguments between those parentheses, separated by commas.

+ +
do(define(x, 10),
+   if(>(x, 5),
+      print("large"),
+      print("small")))
+ +

The uniformity of the Egg language means that things that are operators in JavaScript (such as >) are normal bindings in this language, applied just like other functions. And since the syntax has no concept of a block, we need a do construct to represent doing multiple things in sequence.

+ +

The data structure that the parser will use to describe a program consists of expression objects, each of which has a type property indicating the kind of expression it is and other properties to describe its content.

+ +

Expressions of type "value" represent literal strings or numbers. Their value property contains the string or number value that they represent. Expressions of type "word" are used for identifiers (names). Such objects have a name property that holds the identifier’s name as a string. Finally, "apply" expressions represent applications. They have an operator property that refers to the expression that is being applied, as well as an args property that holds an array of argument expressions.

+ +

The >(x, 5) part of the previous program would be represented like this:

+ +
{
+  type: "apply",
+  operator: {type: "word", name: ">"},
+  args: [
+    {type: "word", name: "x"},
+    {type: "value", value: 5}
+  ]
+}
+ +

Such a data structure is called a syntax tree. If you imagine the objects as dots and the links between them as lines between those dots, it has a treelike shape. The fact that expressions contain other expressions, which in turn might contain more expressions, is similar to the way tree branches split and split again.

The structure of a syntax tree
+ +

Contrast this to the parser we wrote for the configuration file format in Chapter 9, which had a simple structure: it split the input into lines and handled those lines one at a time. There were only a few simple forms that a line was allowed to have.

+ +

Here we must find a different approach. Expressions are not separated into lines, and they have a recursive structure. Application expressions contain other expressions.

+ +

Fortunately, this problem can be solved very well by writing a parser function that is recursive in a way that reflects the recursive nature of the language.

+ +

We define a function parseExpression, which takes a string as input and returns an object containing the data structure for the expression at the start of the string, along with the part of the string left after parsing this expression. When parsing subexpressions (the argument to an application, for example), this function can be called again, yielding the argument expression as well as the text that remains. This text may in turn contain more arguments or may be the closing parenthesis that ends the list of arguments.

+ +

This is the first part of the parser:

+ +
function parseExpression(program) {
+  program = skipSpace(program);
+  let match, expr;
+  if (match = /^"([^"]*)"/.exec(program)) {
+    expr = {type: "value", value: match[1]};
+  } else if (match = /^\d+\b/.exec(program)) {
+    expr = {type: "value", value: Number(match[0])};
+  } else if (match = /^[^\s(),#"]+/.exec(program)) {
+    expr = {type: "word", name: match[0]};
+  } else {
+    throw new SyntaxError("Unexpected syntax: " + program);
+  }
+
+  return parseApply(expr, program.slice(match[0].length));
+}
+
+function skipSpace(string) {
+  let first = string.search(/\S/);
+  if (first == -1) return "";
+  return string.slice(first);
+}
+ +

Because Egg, like JavaScript, allows any amount of whitespace between its elements, we have to repeatedly cut the whitespace off the start of the program string. That is what the skipSpace function helps with.

+ +

After skipping any leading space, parseExpression uses three regular expressions to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which one matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use SyntaxError instead of Error as the exception constructor, which is another standard error type, because it is a little more specific—it is also the error type thrown when an attempt is made to run an invalid JavaScript program.

+ +

We then cut off the part that was matched from the program string and pass that, along with the object for the expression, to parseApply, which checks whether the expression is an application. If so, it parses a parenthesized list of arguments.

+ +
function parseApply(expr, program) {
+  program = skipSpace(program);
+  if (program[0] != "(") {
+    return {expr: expr, rest: program};
+  }
+
+  program = skipSpace(program.slice(1));
+  expr = {type: "apply", operator: expr, args: []};
+  while (program[0] != ")") {
+    let arg = parseExpression(program);
+    expr.args.push(arg.expr);
+    program = skipSpace(arg.rest);
+    if (program[0] == ",") {
+      program = skipSpace(program.slice(1));
+    } else if (program[0] != ")") {
+      throw new SyntaxError("Expected ',' or ')'");
+    }
+  }
+  return parseApply(expr, program.slice(1));
+}
+ +

If the next character in the program is not an opening parenthesis, this is not an application, and parseApply returns the expression it was given.

+ +

Otherwise, it skips the opening parenthesis and creates the syntax +tree object for this application expression. It then recursively calls parseExpression to parse each argument until a closing parenthesis is found. The recursion is indirect, through parseApply and parseExpression calling each other.

+ +

Because an application expression can itself be applied (such as in multiplier(2)(1)), parseApply must, after it has parsed an application, call itself again to check whether another pair of parentheses follows.

+ +

This is all we need to parse Egg. We wrap it in a convenient parse function that verifies that it has reached the end of the input string after parsing the expression (an Egg program is a single expression), and that gives us the program’s data structure.

+ +
function parse(program) {
+  let {expr, rest} = parseExpression(program);
+  if (skipSpace(rest).length > 0) {
+    throw new SyntaxError("Unexpected text after program");
+  }
+  return expr;
+}
+
+console.log(parse("+(a, 10)"));
+// → {type: "apply",
+//    operator: {type: "word", name: "+"},
+//    args: [{type: "word", name: "a"},
+//           {type: "value", value: 10}]}
+ +

It works! It doesn’t give us very helpful information when it fails and doesn’t store the line and column on which each expression starts, which might be helpful when reporting errors later, but it’s good enough for our purposes.

+ +

The evaluator

+ +

What can we do with the syntax tree for a program? Run it, of course! And that is what the evaluator does. You give it a syntax tree and a scope object that associates names with values, and it will evaluate the expression that the tree represents and return the value that this produces.

+ +
const specialForms = Object.create(null);
+
+function evaluate(expr, scope) {
+  if (expr.type == "value") {
+    return expr.value;
+  } else if (expr.type == "word") {
+    if (expr.name in scope) {
+      return scope[expr.name];
+    } else {
+      throw new ReferenceError(
+        `Undefined binding: ${expr.name}`);
+    }
+  } else if (expr.type == "apply") {
+    let {operator, args} = expr;
+    if (operator.type == "word" &&
+        operator.name in specialForms) {
+      return specialForms[operator.name](expr.args, scope);
+    } else {
+      let op = evaluate(operator, scope);
+      if (typeof op == "function") {
+        return op(...args.map(arg => evaluate(arg, scope)));
+      } else {
+        throw new TypeError("Applying a non-function.");
+      }
+    }
+  }
+}
+ +

The evaluator has code for each of the expression types. A literal value expression produces its value. (For example, the expression 100 just evaluates to the number 100.) For a binding, we must check whether it is actually defined in the scope and, if it is, fetch the binding’s value.

+ +

Applications are more involved. If they are a special form, like if, we do not evaluate anything and pass the argument expressions, along with the scope, to the function that handles this form. If it is a normal call, we evaluate the operator, verify that it is a function, and call it with the evaluated arguments.

+ +

We use plain JavaScript function values to represent Egg’s function values. We will come back to this later, when the special form called fun is defined.

+ +

The recursive structure of evaluate resembles the similar structure of the parser, and both mirror the structure of the language itself. It would also be possible to integrate the parser with the evaluator and evaluate during parsing, but splitting them up this way makes the program clearer.

+ +

This is really all that is needed to interpret Egg. It is that simple. But without defining a few special forms and adding some useful values to the environment, you can’t do much with this language yet.

+ +

Special forms

+ +

The specialForms object is used to define special syntax in Egg. It associates words with functions that evaluate such forms. It is currently empty. Let’s add if.

+ +
specialForms.if = (args, scope) => {
+  if (args.length != 3) {
+    throw new SyntaxError("Wrong number of args to if");
+  } else if (evaluate(args[0], scope) !== false) {
+    return evaluate(args[1], scope);
+  } else {
+    return evaluate(args[2], scope);
+  }
+};
+ +

Egg’s if construct expects exactly three arguments. It will evaluate the first, and if the result isn’t the value false, it will evaluate the second. Otherwise, the third gets evaluated. This if form is more similar to JavaScript’s ternary ?: operator than to JavaScript’s if. It is an expression, not a statement, and it produces a value, namely, the result of the second or third argument.

+ +

Egg also differs from JavaScript in how it handles the condition value to if. It will not treat things like zero or the empty string as false, only the precise value false.

+ +

The reason we need to represent if as a special form, rather than a regular function, is that all arguments to functions are evaluated before the function is called, whereas if should evaluate only either its second or its third argument, depending on the value of the first.

+ +

The while form is similar.

+ +
specialForms.while = (args, scope) => {
+  if (args.length != 2) {
+    throw new SyntaxError("Wrong number of args to while");
+  }
+  while (evaluate(args[0], scope) !== false) {
+    evaluate(args[1], scope);
+  }
+
+  // Since undefined does not exist in Egg, we return false,
+  // for lack of a meaningful result.
+  return false;
+};
+ +

Another basic building block is do, which executes all its arguments from top to bottom. Its value is the value produced by the last argument.

+ +
specialForms.do = (args, scope) => {
+  let value = false;
+  for (let arg of args) {
+    value = evaluate(arg, scope);
+  }
+  return value;
+};
+ +

To be able to create bindings and give them new values, we also create a form called define. It expects a word as its first argument and an expression producing the value to assign to that word as its second argument. Since define, like everything, is an expression, it must return a value. We’ll make it return the value that was assigned (just like JavaScript’s = operator).

+ +
specialForms.define = (args, scope) => {
+  if (args.length != 2 || args[0].type != "word") {
+    throw new SyntaxError("Incorrect use of define");
+  }
+  let value = evaluate(args[1], scope);
+  scope[args[0].name] = value;
+  return value;
+};
+ +

The environment

+ +

The scope accepted by evaluate is an object with properties whose names correspond to binding names and whose values correspond to the values those bindings are bound to. Let’s define an object to represent the global scope.

+ +

To be able to use the if construct we just defined, we must have access to Boolean values. Since there are only two Boolean values, we do not need special syntax for them. We simply bind two names to the values true and false and use them.

+ +
const topScope = Object.create(null);
+
+topScope.true = true;
+topScope.false = false;
+ +

We can now evaluate a simple expression that negates a Boolean value.

+ +
let prog = parse(`if(true, false, true)`);
+console.log(evaluate(prog, topScope));
+// → false
+ +

To supply basic arithmetic and comparison operators, we will also add some function values to the scope. In the interest of keeping the code short, we’ll use Function to synthesize a bunch of operator functions in a loop, instead of defining them individually.

+ +
for (let op of ["+", "-", "*", "/", "==", "<", ">"]) {
+  topScope[op] = Function("a, b", `return a ${op} b;`);
+}
+ +

A way to output values is also useful, so we’ll wrap console.log in a function and call it print.

+ +
topScope.print = value => {
+  console.log(value);
+  return value;
+};
+ +

That gives us enough elementary tools to write simple programs. The following function provides a convenient way to parse a program and run it in a fresh scope:

+ +
function run(program) {
+  return evaluate(parse(program), Object.create(topScope));
+}
+ +

We’ll use object prototype chains to represent nested scopes so that the program can add bindings to its local scope without changing the top-level scope.

+ +
run(`
+do(define(total, 0),
+   define(count, 1),
+   while(<(count, 11),
+         do(define(total, +(total, count)),
+            define(count, +(count, 1)))),
+   print(total))
+`);
+// → 55
+ +

This is the program we’ve seen several times before, which computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in less than 150 lines of code.

+ +

Functions

+ +

A programming language without functions is a poor programming language indeed.

+ +

Fortunately, it isn’t hard to add a fun construct, which treats its last argument as the function’s body and uses all arguments before that as the names of the function’s parameters.

+ +
specialForms.fun = (args, scope) => {
+  if (!args.length) {
+    throw new SyntaxError("Functions need a body");
+  }
+  let body = args[args.length - 1];
+  let params = args.slice(0, args.length - 1).map(expr => {
+    if (expr.type != "word") {
+      throw new SyntaxError("Parameter names must be words");
+    }
+    return expr.name;
+  });
+
+  return function() {
+    if (arguments.length != params.length) {
+      throw new TypeError("Wrong number of arguments");
+    }
+    let localScope = Object.create(scope);
+    for (let i = 0; i < arguments.length; i++) {
+      localScope[params[i]] = arguments[i];
+    }
+    return evaluate(body, localScope);
+  };
+};
+ +

Functions in Egg get their own local scope. The function produced by the fun form creates this local scope and adds the argument bindings to it. It then evaluates the function body in this scope and returns the result.

+ +
run(`
+do(define(plusOne, fun(a, +(a, 1))),
+   print(plusOne(10)))
+`);
+// → 11
+
+run(`
+do(define(pow, fun(base, exp,
+     if(==(exp, 0),
+        1,
+        *(base, pow(base, -(exp, 1)))))),
+   print(pow(2, 10)))
+`);
+// → 1024
+ +

Compilation

+ +

What we have built is an interpreter. During evaluation, it acts directly on the representation of the program produced by the parser.

+ +

Compilation is the process of adding another step between the parsing and the running of a program, which transforms the program into something that can be evaluated more efficiently by doing as much work as possible in advance. For example, in well-designed languages it is obvious, for each use of a binding, which binding is being referred to, without actually running the program. This can be used to avoid looking up the binding by name every time it is accessed, instead directly fetching it from some predetermined memory location.

+ +

Traditionally, compilation involves converting the program to machine code, the raw format that a computer’s processor can execute. But any process that converts a program to a different representation can be thought of as compilation.

+ +

It would be possible to write an alternative evaluation strategy for Egg, one that first converts the program to a JavaScript program, uses Function to invoke the JavaScript compiler on it, and then runs the result. When done right, this would make Egg run very fast while still being quite simple to implement.

+ +

If you are interested in this topic and willing to spend some time on it, I encourage you to try to implement such a compiler as an exercise.

+ +

Cheating

+ +

When we defined if and while, you probably noticed that they were more or less trivial wrappers around JavaScript’s own if and while. Similarly, the values in Egg are just regular old JavaScript values.

+ +

If you compare the implementation of Egg, built on top of JavaScript, with the amount of work and complexity required to build a programming language directly on the raw functionality provided by a machine, the difference is huge. Regardless, this example ideally gave you an impression of the way programming languages work.

+ +

And when it comes to getting something done, cheating is more effective than doing everything yourself. Though the toy language in this chapter doesn’t do anything that couldn’t be done better in JavaScript, there are situations where writing small languages helps get real work done.

+ +

Such a language does not have to resemble a typical programming language. If JavaScript didn’t come equipped with regular expressions, for example, you could write your own parser and evaluator for regular expressions.

+ +

Or imagine you are building a giant robotic dinosaur and need to program its behavior. JavaScript might not be the most effective way to do this. You might instead opt for a language that looks like this:

+ +
behavior walk
+  perform when
+    destination ahead
+  actions
+    move left-foot
+    move right-foot
+
+behavior attack
+  perform when
+    Godzilla in-view
+  actions
+    fire laser-eyes
+    launch arm-rockets
+ +

This is what is usually called a domain-specific language, a language tailored to express a narrow domain of knowledge. Such a language can be more expressive than a general-purpose language because it is designed to describe exactly the things that need to be described in its domain, and nothing else.

+ +

Exercises

+ +

Arrays

+ +

Add support for arrays to Egg by adding the following three functions to the top scope: array(...values) to construct an array containing the argument values, length(array) to get an array’s length, and element(array, n) to fetch the nth element from an array.

+ +
// Modify these definitions...
+
+topScope.array = "...";
+
+topScope.length = "...";
+
+topScope.element = "...";
+
+run(`
+do(define(sum, fun(array,
+     do(define(i, 0),
+        define(sum, 0),
+        while(<(i, length(array)),
+          do(define(sum, +(sum, element(array, i))),
+             define(i, +(i, 1)))),
+        sum))),
+   print(sum(array(1, 2, 3))))
+`);
+// → 6
+ +
+ +

The easiest way to do this is to represent Egg arrays with JavaScript arrays.

+ +

The values added to the top scope must be functions. By using a rest argument (with triple-dot notation), the definition of array can be very simple.

+ +
+ +

Closure

+ +

The way we have defined fun allows functions in Egg to reference the surrounding scope, allowing the function’s body to use local values that were visible at the time the function was defined, just like JavaScript functions do.

+ +

The following program illustrates this: function f returns a function that adds its argument to f’s argument, meaning that it needs access to the local scope inside f to be able to use binding a.

+ +
run(`
+do(define(f, fun(a, fun(b, +(a, b)))),
+   print(f(4)(5)))
+`);
+// → 9
+ +

Go back to the definition of the fun form and explain which mechanism causes this to work.

+ +
+ +

Again, we are riding along on a JavaScript mechanism to get the equivalent feature in Egg. Special forms are passed the local scope in which they are evaluated so that they can evaluate their subforms in that scope. The function returned by fun has access to the scope argument given to its enclosing function and uses that to create the function’s local scope when it is called.

+ +

This means that the prototype of the local scope will be the scope in which the function was created, which makes it possible to access bindings in that scope from the function. This is all there is to implementing closure (though to compile it in a way that is actually efficient, you’d need to do some more work).

+ +
+ +

Comments

+ +

It would be nice if we could write comments in Egg. For example, whenever we find a hash sign (#), we could treat the rest of the line as a comment and ignore it, similar to // in JavaScript.

+ +

We do not have to make any big changes to the parser to support this. We can simply change skipSpace to skip comments as if they are whitespace so that all the points where skipSpace is called will now also skip comments. Make this change.

+ +
// This is the old skipSpace. Modify it...
+function skipSpace(string) {
+  let first = string.search(/\S/);
+  if (first == -1) return "";
+  return string.slice(first);
+}
+
+console.log(parse("# hello\nx"));
+// → {type: "word", name: "x"}
+
+console.log(parse("a # one\n   # two\n()"));
+// → {type: "apply",
+//    operator: {type: "word", name: "a"},
+//    args: []}
+ +
+ +

Make sure your solution handles multiple comments in a row, with potentially whitespace between or after them.

+ +

A regular expression is probably the easiest way to solve this. Write something that matches “whitespace or a comment, zero or more times”. Use the exec or match method and look at the length of the first element in the returned array (the whole match) to find out how many characters to slice off.

+ +
+ +

Fixing scope

+ +

Currently, the only way to assign a binding a value is define. This construct acts as a way both to define new bindings and to give existing ones a new value.

+ +

This ambiguity causes a problem. When you try to give a nonlocal binding a new value, you will end up defining a local one with the same name instead. Some languages work like this by design, but I’ve always found it an awkward way to handle scope.

+ +

Add a special form set, similar to define, which gives a binding a new value, updating the binding in an outer scope if it doesn’t already exist in the inner scope. If the binding is not defined at all, throw a ReferenceError (another standard error type).

+ +

The technique of representing scopes as simple objects, which has made things convenient so far, will get in your way a little at this point. You might want to use the Object.getPrototypeOf function, which returns the prototype of an object. Also remember that scopes do not derive from Object.prototype, so if you want to call hasOwnProperty on them, you have to use this clumsy expression:

+ +
Object.prototype.hasOwnProperty.call(scope, name);
+ +
specialForms.set = (args, scope) => {
+  // Your code here.
+};
+
+run(`
+do(define(x, 4),
+   define(setx, fun(val, set(x, val))),
+   setx(50),
+   print(x))
+`);
+// → 50
+run(`set(quux, true)`);
+// → Some kind of ReferenceError
+ +
+ +

You will have to loop through one scope at a time, using Object.getPrototypeOf to go to the next outer scope. For each scope, use hasOwnProperty to find out whether the binding, indicated by the name property of the first argument to set, exists in that scope. If it does, set it to the result of evaluating the second argument to set and then return that value.

+ +

If the outermost scope is reached (Object.getPrototypeOf returns null) and we haven’t found the binding yet, it doesn’t exist, and an error should be thrown.

+ +
+
diff --git a/docs/13_browser.html b/docs/13_browser.html new file mode 100644 index 000000000..3649e531a --- /dev/null +++ b/docs/13_browser.html @@ -0,0 +1,176 @@ + + + + + JavaScript and the Browser :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 13JavaScript and the Browser

+ +
+ +

The dream behind the Web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished.

+ +
Tim Berners-Lee, The World Wide Web: A very short personal history
+ +
Picture of a telephone switchboard
+ +

The next chapters of this book will talk about web browsers. Without web browsers, there would be no JavaScript. Or even if there were, no one would ever have paid any attention to it.

+ +

Web technology has been decentralized from the start, not just technically but also in the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which then, sometimes, ended up being adopted by others—and finally set down as in standards.

+ +

This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose collaboration (or occasionally open hostility). On the other hand, the haphazard way in which the Web was developed means that the resulting system is not exactly a shining example of internal consistency. Some parts of it are downright confusing and poorly conceived.

+ +

Networks and the Internet

+ +

Computer networks have been around since the 1950s. If you put cables between two or more computers and allow them to send data back and forth through these cables, you can do all kinds of wonderful things.

+ +

And if connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the Internet. It has lived up to its promise.

+ +

A computer can use this network to shoot bits at another computer. For any effective communication to arise out of this bit-shooting, the computers on both ends must know what the bits are supposed to represent. The meaning of any given sequence of bits depends entirely on the kind of thing that it is trying to express and on the encoding mechanism used.

+ +

A network protocol describes a style of communication over a network. There are protocols for sending email, for fetching email, for sharing files, and even for controlling computers that happen to be infected by malicious software.

+ +

For example, the Hypertext Transfer Protocol (HTTP) is a protocol for retrieving named resources (chunks of information, such as web pages or pictures). It specifies that the side making the request should start with a line like this, naming the resource and the version of the protocol that it is trying to use:

+ +
GET /index.html HTTP/1.1
+ +

There are a lot more rules about the way the requester can include more information in the request and the way the other side, which returns the resource, packages up its content. We’ll look at HTTP in a little more detail in Chapter 18.

+ +

Most protocols are built on top of other protocols. HTTP treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. As we saw in Chapter 11, ensuring those things is already a rather difficult problem.

+ +

The Transmission Control Protocol (TCP) is a protocol that addresses this problem. All Internet-connected devices “speak” it, and most communication on the Internet is built on top of it.

+ +

A TCP connection works as follows: one computer must be waiting, or listening, for other computers to start talking to it. To be able to listen for different kinds of communication at the same time on a single machine, each listener has a number (called a port) associated with it. Most protocols specify which port should be used by default. For example, when we want to send an email using the SMTP protocol, the machine through which we send it is expected to be listening on port 25.

+ +

Another computer can then establish a connection by connecting to the target machine using the correct port number. If the target machine can be reached and is listening on that port, the connection is successfully created. The listening computer is called the server, and the connecting computer is called the client.

+ +

Such a connection acts as a two-way pipe through which bits can flow—the machines on both ends can put data into it. Once the bits are successfully transmitted, they can be read out again by the machine on the other side. This is a convenient model. You could say that TCP provides an abstraction of the network.

+ +

The Web

+ +

The World Wide Web (not to be confused with the Internet as a whole) is a set of protocols and formats that allow us to visit web pages in a browser. The “Web” part in the name refers to the fact that such pages can easily link to each other, thus connecting into a huge mesh that users can move through.

+ +

To become part of the Web, all you need to do is connect a machine to the Internet and have it listen on port 80 with the HTTP protocol so that other computers can ask it for documents.

+ +

Each document on the Web is named by a Uniform Resource Locator (URL), which looks something like this:

+ +
  http://eloquentjavascript.net/13_browser.html
+ |      |                      |               |
+ protocol       server               path
+ +

The first part tells us that this URL uses the HTTP protocol (as opposed to, for example, encrypted HTTP, which would be https://). Then comes the part that identifies which server we are requesting the document from. Last is a path string that identifies the specific document (or resource) we are interested in.

+ +

Machines connected to the Internet get an IP address, which is a number that can be used to send messages to that machine, and looks something like 149.210.142.219 or 2001:4860:4860::8888. But lists of more or less random numbers are hard to remember and awkward to type, so you can instead register a domain name for a specific address or set of addresses. I registered eloquentjavascript.net to point at the IP address of a machine I control and can thus use that domain name to serve web pages.

+ +

If you type this URL into your browser’s address bar, the browser will try to retrieve and display the document at that URL. First, your browser has to find out what address eloquentjavascript.net refers to. Then, using the HTTP protocol, it will make a connection to the server at that address and ask for the resource /13_browser.html. If all goes well, the server sends back a document, which your browser then displays on your screen.

+ +

HTML

+ +

HTML, which stands for Hypertext Markup Language, is the document format used for web pages. An HTML document contains text, as well as tags that give structure to the text, describing things such as links, paragraphs, and headings.

+ +

A short HTML document might look like this:

+ +
<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>My home page</title>
+  </head>
+  <body>
+    <h1>My home page</h1>
+    <p>Hello, I am Marijn and this is my home page.</p>
+    <p>I also wrote a book! Read it
+      <a href="http://eloquentjavascript.net">here</a>.</p>
+  </body>
+</html>
+ +

The tags, wrapped in angle brackets (< and >, the symbols for less than and greater than), provide information about the structure of the document. The other text is just plain text.

+ +

The document starts with <!doctype html>, which tells the browser to interpret the page as modern HTML, as opposed to various dialects that were in use in the past.

+ +

HTML documents have a head and a body. The head contains information about the document, and the body contains the document itself. In this case, the head declares that the title of this document is “My home page” and that it uses the UTF-8 encoding, which is a way to encode Unicode text as binary data. The document’s body contains a heading (<h1>, meaning “heading 1”—<h2> to <h6> produce subheadings) and two paragraphs (<p>).

+ +

Tags come in several forms. An element, such as the body, a paragraph, or a link, is started by an opening tag like <p> and ended by a closing tag like </p>. Some opening tags, such as the one for the link (<a>), contain extra information in the form of name="value" pairs. These are called attributes. In this case, the destination of the link is indicated with href="http://eloquentjavascript.net", where href stands for “hypertext reference”.

+ +

Some kinds of tags do not enclose anything and thus do not need to be closed. The metadata tag <meta charset="utf-8"> is an example of this.

+ +

To be able to include angle brackets in the text of a document, even though they have a special meaning in HTML, yet another form of special notation has to be introduced. A plain opening angle bracket is written as &lt; (“less than”), and a closing bracket is written as &gt; (“greater than”). In HTML, an ampersand (&) character followed by a name or character code and a semicolon (;) is called an entity and will be replaced by the character it encodes.

+ +

This is analogous to the way backslashes are used in JavaScript strings. Since this mechanism gives ampersand characters a special meaning, too, they need to be escaped as &amp;. Inside attribute values, which are wrapped in double quotes, &quot; can be used to insert an actual quote character.

+ +

HTML is parsed in a remarkably error-tolerant way. When tags that should be there are missing, the browser reconstructs them. The way in which this is done has been standardized, and you can rely on all modern browsers to do it in the same way.

+ +

The following document will be treated just like the one shown previously:

+ +
<!doctype html>
+
+<meta charset=utf-8>
+<title>My home page</title>
+
+<h1>My home page</h1>
+<p>Hello, I am Marijn and this is my home page.
+<p>I also wrote a book! Read it
+  <a href=http://eloquentjavascript.net>here</a>.
+ +

The <html>, <head>, and <body> tags are gone completely. The browser knows that <meta> and <title> belong in the head and that <h1> means the body has started. Furthermore, I am no longer explicitly closing the paragraphs since opening a new paragraph or ending the document will close them implicitly. The quotes around the attribute values are also gone.

+ +

This book will usually omit the <html>, <head>, and <body> tags from examples to keep them short and free of clutter. But I will close tags and include quotes around attributes.

+ +

I will also usually omit the doctype and charset declaration. This is not to be taken as an encouragement to drop these from HTML documents. Browsers will often do ridiculous things when you forget them. You should consider the doctype and the charset metadata to be implicitly present in examples, even when they are not actually shown in the text.

+ +

HTML and JavaScript

+ +

In the context of this book, the most important HTML tag is <script>. This tag allows us to include a piece of JavaScript in a document.

+ +
<h1>Testing alert</h1>
+<script>alert("hello!");</script>
+ +

Such a script will run as soon as its <script> tag is encountered while the browser reads the HTML. This page will pop up a dialog when opened—the alert function resembles prompt, in that it pops up a little window, but only shows a message without asking for input.

+ +

Including large programs directly in HTML documents is often impractical. The <script> tag can be given an src attribute to fetch a script file (a text file containing a JavaScript program) from a URL.

+ +
<h1>Testing alert</h1>
+<script src="code/hello.js"></script>
+ +

The code/hello.js file included here contains the same program—alert("hello!"). When an HTML page references other URLs as part of itself—for example, an image file or a script—web browsers will retrieve them immediately and include them in the page.

+ +

A script tag must always be closed with </script>, even if it refers to a script file and doesn’t contain any code. If you forget this, the rest of the page will be interpreted as part of the script.

+ +

You can load ES modules (see Chapter 10) in the browser by giving your script tag a type="module" attribute. Such modules can depend on other modules by using URLs relative to themselves as module names in import declarations.

+ +

Some attributes can also contain a JavaScript program. The <button> tag shown next (which shows up as a button) has an onclick attribute. The attribute’s value will be run whenever the button is clicked.

+ +
<button onclick="alert('Boom!');">DO NOT PRESS</button>
+ +

Note that I had to use single quotes for the string in the onclick attribute because double quotes are already used to quote the whole attribute. I could also have used &quot;.

+ +

In the sandbox

+ +

Running programs downloaded from the Internet is potentially dangerous. You do not know much about the people behind most sites you visit, and they do not necessarily mean well. Running programs by people who do not mean well is how you get your computer infected by viruses, your data stolen, and your accounts hacked.

+ +

Yet the attraction of the Web is that you can browse it without necessarily trusting all the pages you visit. This is why browsers severely limit the things a JavaScript program may do: it can’t look at the files on your computer or modify anything not related to the web page it was embedded in.

+ +

Isolating a programming environment in this way is called sandboxing, the idea being that the program is harmlessly playing in a sandbox. But you should imagine this particular kind of sandbox as having a cage of thick steel bars over it so that the programs playing in it can’t actually get out.

+ +

The hard part of sandboxing is allowing the programs enough room to be useful yet at the same time restricting them from doing anything dangerous. Lots of useful functionality, such as communicating with other servers or reading the content of the copy-paste clipboard, can also be used to do problematic, privacy-invading things.

+ +

Every now and then, someone comes up with a new way to circumvent the limitations of a browser and do something harmful, ranging from leaking minor private information to taking over the whole machine that the browser runs on. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized, rather than secretly exploited by some government agency or mafia.

+ +

Compatibility and the browser wars

+ +

In the early stages of the Web, a browser called Mosaic dominated the market. After a few years, the balance shifted to Netscape, which was then, in turn, largely supplanted by Microsoft’s Internet Explorer. At any point where a single browser was dominant, that browser’s vendor would feel entitled to unilaterally invent new features for the Web. Since most users used the most popular browser, websites would simply start using those features—never mind the other browsers.

+ +

This was the dark age of compatibility, often called the browser wars. Web developers were left with not one unified Web but two or three incompatible platforms. To make things worse, the browsers in use around 2003 were all full of bugs, and of course the bugs were different for each browser. Life was hard for people writing web pages.

+ +

Mozilla Firefox, a not-for-profit offshoot of Netscape, challenged Internet Explorer’s position in the late 2000s. Because Microsoft was not particularly interested in staying competitive at the time, Firefox took a lot of market share away from it. Around the same time, Google introduced its Chrome browser, and Apple’s Safari browser gained popularity, leading to a situation where there were four major players, rather than one.

+ +

The new players had a more serious attitude toward standards and better engineering practices, giving us less incompatibility and fewer bugs. Microsoft, seeing its market share crumble, came around and adopted these attitudes in its Edge browser, which replaces Internet Explorer. If you are starting to learn web development today, consider yourself lucky. The latest versions of the major browsers behave quite uniformly and have relatively few bugs.

+
diff --git a/docs/14_dom.html b/docs/14_dom.html new file mode 100644 index 000000000..f816c432a --- /dev/null +++ b/docs/14_dom.html @@ -0,0 +1,572 @@ + + + + + The Document Object Model :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 14The Document Object Model

+ +
+ +

Too bad! Same old story! Once you’ve finished building your house you notice you’ve accidentally learned something that you really should have known—before you started.

+ +
Friedrich Nietzsche, Beyond Good and Evil
+ +
Picture of a tree with letters and scripts hanging from its branches
+ +

When you open a web page in your browser, the browser retrieves the page’s HTML text and parses it, much like the way our parser from Chapter 12 parsed programs. The browser builds up a model of the document’s structure and uses this model to draw the page on the screen.

+ +

This representation of the document is one of the toys that a JavaScript program has available in its sandbox. It is a data +structure that you can read or modify. It acts as a live data structure: when it’s modified, the page on the screen is updated to reflect the changes.

+ +

Document structure

+ +

You can imagine an HTML document as a nested set of boxes. Tags such as <body> and </body> enclose other tags, which in turn contain other tags or text. Here’s the example document from the previous chapter:

+ +
<!doctype html>
+<html>
+  <head>
+    <title>My home page</title>
+  </head>
+  <body>
+    <h1>My home page</h1>
+    <p>Hello, I am Marijn and this is my home page.</p>
+    <p>I also wrote a book! Read it
+      <a href="http://eloquentjavascript.net">here</a>.</p>
+  </body>
+</html>
+ +

This page has the following structure:

HTML document as nested boxes
+ +

The data structure the browser uses to represent the document follows this shape. For each box, there is an object, which we can interact with to find out things such as what HTML tag it represents and which boxes and text it contains. This representation is called the Document Object Model, or DOM for short.

+ +

The global binding document gives us access to these objects. Its documentElement property refers to the object representing the <html> tag. Since every HTML document has a head and a body, it also has head and body properties, pointing at those elements.

+ +

Trees

+ +

Think back to the syntax trees from Chapter 12 for a moment. Their structures are strikingly similar to the structure of a browser’s document. Each node may refer to other nodes, children, which in turn may have their own children. This shape is typical of nested structures where elements can contain subelements that are similar to themselves.

+ +

We call a data structure a tree when it has a branching structure, has no cycles (a node may not contain itself, directly or indirectly), and has a single, well-defined root. In the case of the DOM, document.documentElement serves as the root.

+ +

Trees come up a lot in computer science. In addition to representing recursive structures such as HTML documents or programs, they are often used to maintain sorted sets of data because elements can usually be found or inserted more efficiently in a tree than in a flat array.

+ +

A typical tree has different kinds of nodes. The syntax tree for the Egg language had identifiers, values, and application nodes. Application nodes may have children, whereas identifiers and values are leaves, or nodes without children.

+ +

The same goes for the DOM. Nodes for elements, which represent HTML tags, determine the structure of the document. These can have child nodes. An example of such a node is document.body. Some of these children can be leaf nodes, such as pieces of text or comment nodes.

+ +

Each DOM node object has a nodeType property, which contains a code (number) that identifies the type of node. Elements have code 1, which is also defined as the constant property Node.ELEMENT_NODE. Text nodes, representing a section of text in the document, get code 3 (Node.TEXT_NODE). Comments have code 8 (Node.COMMENT_NODE).

+ +

Another way to visualize our document tree is as follows:

HTML document as a tree
+ +

The leaves are text nodes, and the arrows indicate parent-child relationships between nodes.

+ +

The standard

+ +

Using cryptic numeric codes to represent node types is not a very JavaScript-like thing to do. Later in this chapter, we’ll see that other parts of the DOM interface also feel cumbersome and alien. The reason for this is that the DOM wasn’t designed for just JavaScript. Rather, it tries to be a language-neutral interface that can be used in other systems as well—not just for HTML but also for XML, which is a generic data format with an HTML-like syntax.

+ +

This is unfortunate. Standards are often useful. But in this case, the advantage (cross-language consistency) isn’t all that compelling. Having an interface that is properly integrated with the language you are using will save you more time than having a familiar interface across languages.

+ +

As an example of this poor integration, consider the childNodes property that element nodes in the DOM have. This property holds an array-like object, with a length property and properties labeled by numbers to access the child nodes. But it is an instance of the NodeList type, not a real array, so it does not have methods such as slice and map.

+ +

Then there are issues that are simply poor design. For example, there is no way to create a new node and immediately add children or attributes to it. Instead, you have to first create it and then add the children and attributes one by one, using side effects. Code that interacts heavily with the DOM tends to get long, repetitive, and ugly.

+ +

But these flaws aren’t fatal. Since JavaScript allows us to create our own abstractions, it is possible to design improved ways to express the operations you are performing. Many libraries intended for browser programming come with such tools.

+ +

Moving through the tree

+ +

DOM nodes contain a wealth of links to other nearby nodes. The following diagram illustrates these:

Links between DOM nodes
+ +

Although the diagram shows only one link of each type, every node has a parentNode property that points to the node it is part of, if any. Likewise, every element node (node type 1) has a childNodes property that points to an array-like object holding its children.

+ +

In theory, you could move anywhere in the tree using just these parent and child links. But JavaScript also gives you access to a number of additional convenience links. The firstChild and lastChild properties point to the first and last child elements or have the value null for nodes without children. Similarly, previousSibling and nextSibling point to adjacent nodes, which are nodes with the same parent that appear immediately before or after the node itself. For a first child, previousSibling will be null, and for a last child, nextSibling will be null.

+ +

There’s also the children property, which is like childNodes but contains only element (type 1) children, not other types of child nodes. This can be useful when you aren’t interested in text nodes.

+ +

When dealing with a nested data structure like this one, recursive functions are often useful. The following function scans a document for text nodes containing a given string and returns true when it has found one:

+ +
function talksAbout(node, string) {
+  if (node.nodeType == Node.ELEMENT_NODE) {
+    for (let i = 0; i < node.childNodes.length; i++) {
+      if (talksAbout(node.childNodes[i], string)) {
+        return true;
+      }
+    }
+    return false;
+  } else if (node.nodeType == Node.TEXT_NODE) {
+    return node.nodeValue.indexOf(string) > -1;
+  }
+}
+
+console.log(talksAbout(document.body, "book"));
+// → true
+ +

Because childNodes is not a real array, we cannot loop over it with for/of and have to run over the index range using a regular for loop or use Array.from.

+ +

The nodeValue property of a text node holds the string of text that it represents.

+ +

Finding elements

+ +

Navigating these links among parents, children, and siblings is often useful. But if we want to find a specific node in the document, reaching it by starting at document.body and following a fixed path of properties is a bad idea. Doing so bakes assumptions into our program about the precise structure of the document—a structure you might want to change later. Another complicating factor is that text nodes are created even for the whitespace between nodes. The example document’s <body> tag does not have just three children (<h1> and two <p> elements) but actually has seven: those three, plus the spaces before, after, and between them.

+ +

So if we want to get the href attribute of the link in that document, we don’t want to say something like “Get the second child of the sixth child of the document body”. It’d be better if we could say “Get the first link in the document”. And we can.

+ +
let link = document.body.getElementsByTagName("a")[0];
+console.log(link.href);
+ +

All element nodes have a getElementsByTagName method, which collects all elements with the given tag name that are descendants (direct or indirect children) of that node and returns them as an array-like +object.

+ +

To find a specific single node, you can give it an id attribute and use document.getElementById instead.

+ +
<p>My ostrich Gertrude:</p>
+<p><img id="gertrude" src="img/ostrich.png"></p>
+
+<script>
+  let ostrich = document.getElementById("gertrude");
+  console.log(ostrich.src);
+</script>
+ +

A third, similar method is getElementsByClassName, which, like getElementsByTagName, searches through the contents of an element node and retrieves all elements that have the given string in their class attribute.

+ +

Changing the document

+ +

Almost everything about the DOM data structure can be changed. The shape of the document tree can be modified by changing parent-child relationships. Nodes have a remove method to remove them from their current parent node. To add a child node to an element node, we can use appendChild, which puts it at the end of the list of children, or insertBefore, which inserts the node given as the first argument before the node given as the second argument.

+ +
<p>One</p>
+<p>Two</p>
+<p>Three</p>
+
+<script>
+  let paragraphs = document.body.getElementsByTagName("p");
+  document.body.insertBefore(paragraphs[2], paragraphs[0]);
+</script>
+ +

A node can exist in the document in only one place. Thus, inserting paragraph Three in front of paragraph One will first remove it from the end of the document and then insert it at the front, resulting in Three/One/Two. All operations that insert a node somewhere will, as a side effect, cause it to be removed from its current position (if it has one).

+ +

The replaceChild method is used to replace a child node with another one. It takes as arguments two nodes: a new node and the node to be replaced. The replaced node must be a child of the element the method is called on. Note that both replaceChild and insertBefore expect the new node as their first argument.

+ +

Creating nodes

+ +

Say we want to write a script that replaces all images (<img> tags) in the document with the text held in their alt attributes, which specifies an alternative textual representation of the image.

+ +

This involves not only removing the images but adding a new text node to replace them. Text nodes are created with the document.createTextNode method.

+ +
<p>The <img src="img/cat.png" alt="Cat"> in the
+  <img src="img/hat.png" alt="Hat">.</p>
+
+<p><button onclick="replaceImages()">Replace</button></p>
+
+<script>
+  function replaceImages() {
+    let images = document.body.getElementsByTagName("img");
+    for (let i = images.length - 1; i >= 0; i--) {
+      let image = images[i];
+      if (image.alt) {
+        let text = document.createTextNode(image.alt);
+        image.parentNode.replaceChild(text, image);
+      }
+    }
+  }
+</script>
+ +

Given a string, createTextNode gives us a text node that we can insert into the document to make it show up on the screen.

+ +

The loop that goes over the images starts at the end of the list. This is necessary because the node list returned by a method like getElementsByTagName (or a property like childNodes) is live. That is, it is updated as the document changes. If we started from the front, removing the first image would cause the list to lose its first element so that the second time the loop repeats, where i is 1, it would stop because the length of the collection is now also 1.

+ +

If you want a solid collection of nodes, as opposed to a live one, you can convert the collection to a real array by calling Array.from.

+ +
let arrayish = {0: "one", 1: "two", length: 2};
+let array = Array.from(arrayish);
+console.log(array.map(s => s.toUpperCase()));
+// → ["ONE", "TWO"]
+ +

To create element nodes, you can use the document.createElement method. This method takes a tag name and returns a new empty node of the given type.

+ +

The following example defines a utility elt, which creates an element node and treats the rest of its arguments as children to that node. This function is then used to add an attribution to a quote.

+ +
<blockquote id="quote">
+  No book can ever be finished. While working on it we learn
+  just enough to find it immature the moment we turn away
+  from it.
+</blockquote>
+
+<script>
+  function elt(type, ...children) {
+    let node = document.createElement(type);
+    for (let child of children) {
+      if (typeof child != "string") node.appendChild(child);
+      else node.appendChild(document.createTextNode(child));
+    }
+    return node;
+  }
+
+  document.getElementById("quote").appendChild(
+    elt("footer", "—",
+        elt("strong", "Karl Popper"),
+        ", preface to the second edition of ",
+        elt("em", "The Open Society and Its Enemies"),
+        ", 1950"));
+</script>
+ +

Attributes

+ +

Some element attributes, such as href for links, can be accessed through a property of the same name on the element’s DOM object. This is the case for most commonly used standard attributes.

+ +

But HTML allows you to set any attribute you want on nodes. This can be useful because it allows you to store extra information in a document. If you make up your own attribute names, though, such attributes will not be present as properties on the element’s node. Instead, you have to use the getAttribute and setAttribute methods to work with them.

+ +
<p data-classified="secret">The launch code is 00000000.</p>
+<p data-classified="unclassified">I have two feet.</p>
+
+<script>
+  let paras = document.body.getElementsByTagName("p");
+  for (let para of Array.from(paras)) {
+    if (para.getAttribute("data-classified") == "secret") {
+      para.remove();
+    }
+  }
+</script>
+ +

It is recommended to prefix the names of such made-up attributes with data- to ensure they do not conflict with any other attributes.

+ +

There is a commonly used attribute, class, which is a keyword in the JavaScript language. For historical reasons—some old JavaScript implementations could not handle property names that matched keywords—the property used to access this attribute is called className. You can also access it under its real name, "class", by using the getAttribute and setAttribute methods.

+ +

Layout

+ +

You may have noticed that different types of elements are laid out differently. Some, such as paragraphs (<p>) or headings (<h1>), take up the whole width of the document and are rendered on separate lines. These are called block elements. Others, such as links (<a>) or the <strong> element, are rendered on the same line with their surrounding text. Such elements are called inline elements.

+ +

For any given document, browsers are able to compute a layout, which gives each element a size and position based on its type and content. This layout is then used to actually draw the document.

+ +

The size and position of an element can be accessed from JavaScript. The offsetWidth and offsetHeight properties give you the space the element takes up in pixels. A pixel is the basic unit of measurement in the browser. It traditionally corresponds to the smallest dot that the screen can draw, but on modern displays, which can draw very small dots, that may no longer be the case, and a browser pixel may span multiple display dots.

+ +

Similarly, clientWidth and clientHeight give you the size of the space inside the element, ignoring border width.

+ +
<p style="border: 3px solid red">
+  I'm boxed in
+</p>
+
+<script>
+  let para = document.body.getElementsByTagName("p")[0];
+  console.log("clientHeight:", para.clientHeight);
+  console.log("offsetHeight:", para.offsetHeight);
+</script>
+ +

The most effective way to find the precise position of an element on the screen is the getBoundingClientRect method. It returns an object with top, bottom, left, and right properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. If you want them relative to the whole document, you must add the current scroll position, which you can find in the pageXOffset and pageYOffset bindings.

+ +

Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time you change it but wait as long as they can. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout to draw the changed document to the screen. When a program asks for the position or size of something by reading properties such as offsetHeight or calling getBoundingClientRect, providing correct information also requires computing a layout.

+ +

A program that repeatedly alternates between reading DOM layout information and changing the DOM forces a lot of layout computations to happen and will consequently run very slowly. The following code is an example of this. It contains two different programs that build up a line of X characters 2,000 pixels wide and measures the time each one takes.

+ +
<p><span id="one"></span></p>
+<p><span id="two"></span></p>
+
+<script>
+  function time(name, action) {
+    let start = Date.now(); // Current time in milliseconds
+    action();
+    console.log(name, "took", Date.now() - start, "ms");
+  }
+
+  time("naive", () => {
+    let target = document.getElementById("one");
+    while (target.offsetWidth < 2000) {
+      target.appendChild(document.createTextNode("X"));
+    }
+  });
+  // → naive took 32 ms
+
+  time("clever", function() {
+    let target = document.getElementById("two");
+    target.appendChild(document.createTextNode("XXXXX"));
+    let total = Math.ceil(2000 / (target.offsetWidth / 5));
+    target.firstChild.nodeValue = "X".repeat(total);
+  });
+  // → clever took 1 ms
+</script>
+ +

Styling

+ +

We have seen that different HTML elements are drawn differently. Some are displayed as blocks, others inline. Some add styling—<strong> makes its content bold, and <a> makes it blue and underlines it.

+ +

The way an <img> tag shows an image or an <a> tag causes a link to be followed when it is clicked is strongly tied to the element type. But we can change the styling associated with an element, such as the text color or underline. Here is an example that uses the style property:

+ +
<p><a href=".">Normal link</a></p>
+<p><a href="." style="color: green">Green link</a></p>
+ +

A style attribute may contain one or more declarations, which are a property (such as color) followed by a colon and a value (such as green). When there is more than one declaration, they must be separated by semicolons, as in "color: red; border: none".

+ +

A lot of aspects of the document can be influenced by styling. For example, the display property controls whether an element is displayed as a block or an inline element.

+ +
This text is displayed <strong>inline</strong>,
+<strong style="display: block">as a block</strong>, and
+<strong style="display: none">not at all</strong>.
+ +

The block tag will end up on its own line since block elements are not displayed inline with the text around them. The last tag is not displayed at all—display: none prevents an element from showing up on the screen. This is a way to hide elements. It is often preferable to removing them from the document entirely because it makes it easy to reveal them again later.

+ +

JavaScript code can directly manipulate the style of an element through the element’s style property. This property holds an object that has properties for all possible style properties. The values of these properties are strings, which we can write to in order to change a particular aspect of the element’s style.

+ +
<p id="para" style="color: purple">
+  Nice text
+</p>
+
+<script>
+  let para = document.getElementById("para");
+  console.log(para.style.color);
+  para.style.color = "magenta";
+</script>
+ +

Some style property names contain hyphens, such as font-family. Because such property names are awkward to work with in JavaScript (you’d have to say style["font-family"]), the property names in the style object for such properties have their hyphens removed and the letters after them capitalized (style.fontFamily).

+ +

Cascading styles

+ +

The styling system for HTML is called CSS, for Cascading Style Sheets. A style sheet is a set of rules for how to style elements in a document. It can be given inside a <style> tag.

+ +
<style>
+  strong {
+    font-style: italic;
+    color: gray;
+  }
+</style>
+<p>Now <strong>strong text</strong> is italic and gray.</p>
+ +

The cascading in the name refers to the fact that multiple such rules are combined to produce the final style for an element. In the example, the default styling for <strong> tags, which gives them font-weight: bold, is overlaid by the rule in the <style> tag, which adds font-style and color.

+ +

When multiple rules define a value for the same property, the most recently read rule gets a higher precedence and wins. So if the rule in the <style> tag included font-weight: normal, contradicting the default font-weight rule, the text would be normal, not bold. Styles in a style attribute applied directly to the node have the highest precedence and always win.

+ +

It is possible to target things other than tag names in CSS rules. A rule for .abc applies to all elements with "abc" in their class attribute. A rule for #xyz applies to the element with an id attribute of "xyz" (which should be unique within the document).

+ +
.subtle {
+  color: gray;
+  font-size: 80%;
+}
+#header {
+  background: blue;
+  color: white;
+}
+/* p elements with id main and with classes a and b */
+p#main.a.b {
+  margin-bottom: 20px;
+}
+ +

The precedence rule favoring the most recently defined rule applies only when the rules have the same specificity. A rule’s specificity is a measure of how precisely it describes matching elements, determined by the number and kind (tag, class, or ID) of element aspects it requires. For example, a rule that targets p.a is more specific than rules that target p or just .a and would thus take precedence over them.

+ +

The notation p > a {…} applies the given styles to all <a> tags that are direct children of <p> tags. Similarly, p a {…} applies to all <a> tags inside <p> tags, whether they are direct or indirect children.

+ +

Query selectors

+ +

We won’t be using style sheets all that much in this book. Understanding them is helpful when programming in the browser, but they are complicated enough to warrant a separate book.

+ +

The main reason I introduced selector syntax—the notation used in style sheets to determine which elements a set of styles apply to—is that we can use this same mini-language as an effective way to find DOM elements.

+ +

The querySelectorAll method, which is defined both on the document object and on element nodes, takes a selector string and returns a NodeList containing all the elements that it matches.

+ +
<p>And if you go chasing
+  <span class="animal">rabbits</span></p>
+<p>And you know you're going to fall</p>
+<p>Tell 'em a <span class="character">hookah smoking
+  <span class="animal">caterpillar</span></span></p>
+<p>Has given you the call</p>
+
+<script>
+  function count(selector) {
+    return document.querySelectorAll(selector).length;
+  }
+  console.log(count("p"));           // All <p> elements
+  // → 4
+  console.log(count(".animal"));     // Class animal
+  // → 2
+  console.log(count("p .animal"));   // Animal inside of <p>
+  // → 2
+  console.log(count("p > .animal")); // Direct child of <p>
+  // → 1
+</script>
+ +

Unlike methods such as getElementsByTagName, the object returned by querySelectorAll is not live. It won’t change when you change the document. It is still not a real array, though, so you still need to call Array.from if you want to treat it like one.

+ +

The querySelector method (without the All part) works in a similar way. This one is useful if you want a specific, single element. It will return only the first matching element or null when no element matches.

+ +

Positioning and animating

+ +

The position style property influences layout in a powerful way. By default it has a value of static, meaning the element sits in its normal place in the document. When it is set to relative, the element still takes up space in the document, but now the top and left style properties can be used to move it relative to that normal place. When position is set to absolute, the element is removed from the normal document flow—that is, it no longer takes up space and may overlap with other elements. Also, its top and left properties can be used to absolutely position it relative to the top-left corner of the nearest enclosing element whose position property isn’t static, or relative to the document if no such enclosing element exists.

+ +

We can use this to create an animation. The following document displays a picture of a cat that moves around in an ellipse:

+ +
<p style="text-align: center">
+  <img src="img/cat.png" style="position: relative">
+</p>
+<script>
+  let cat = document.querySelector("img");
+  let angle = Math.PI / 2;
+  function animate(time, lastTime) {
+    if (lastTime != null) {
+      angle += (time - lastTime) * 0.001;
+    }
+    cat.style.top = (Math.sin(angle) * 20) + "px";
+    cat.style.left = (Math.cos(angle) * 200) + "px";
+    requestAnimationFrame(newTime => animate(newTime, time));
+  }
+  requestAnimationFrame(animate);
+</script>
+ +

Our picture is centered on the page and given a position of relative. We’ll repeatedly update that picture’s top and left styles to move it.

+ +

The script uses requestAnimationFrame to schedule the animate function to run whenever the browser is ready to repaint the screen. The animate function itself again calls requestAnimationFrame to schedule the next update. When the browser window (or tab) is active, this will cause updates to happen at a rate of about 60 per second, which tends to produce a good-looking animation.

+ +

If we just updated the DOM in a loop, the page would freeze, and nothing would show up on the screen. Browsers do not update their display while a JavaScript program is running, nor do they allow any interaction with the page. This is why we need requestAnimationFrame—it lets the browser know that we are done for now, and it can go ahead and do the things that browsers do, such as updating the screen and responding to user actions.

+ +

The animation function is passed the current time as an argument. To ensure that the motion of the cat per millisecond is stable, it bases the speed at which the angle changes on the difference between the current time and the last time the function ran. If it just moved the angle by a fixed amount per step, the motion would stutter if, for example, another heavy task running on the same computer were to prevent the function from running for a fraction of a second.

+ +

Moving in circles is done using the trigonometry functions Math.cos and Math.sin. For those who aren’t familiar with these, I’ll briefly introduce them since we will occasionally use them in this book.

+ +

Math.cos and Math.sin are useful for finding points that lie on a circle around point (0,0) with a radius of one. Both functions interpret their argument as the position on this circle, with zero denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. Math.cos tells you the x-coordinate of the point that corresponds to the given position, and Math.sin yields the y-coordinate. Positions (or angles) greater than 2π or less than 0 are valid—the rotation repeats so that a+2π refers to the same angle as a.

+ +

This unit for measuring angles is called radians—a full circle is 2π radians, similar to how it is 360 degrees when measuring in degrees. The constant π is available as Math.PI in JavaScript.

Using cosine and sine to compute coordinates
+ +

The cat animation code keeps a counter, angle, for the current angle of the animation and increments it every time the animate function is called. It can then use this angle to compute the current position of the image element. The top style is computed with Math.sin and multiplied by 20, which is the vertical radius of our ellipse. The left style is based on Math.cos and multiplied by 200 so that the ellipse is much wider than it is high.

+ +

Note that styles usually need units. In this case, we have to append "px" to the number to tell the browser that we are counting in pixels (as opposed to centimeters, “ems”, or other units). This is easy to forget. Using numbers without units will result in your style being ignored—unless the number is 0, which always means the same thing, regardless of its unit.

+ +

Summary

+ +

JavaScript programs may inspect and interfere with the document that the browser is displaying through a data structure called the DOM. This data structure represents the browser’s model of the document, and a JavaScript program can modify it to change the visible document.

+ +

The DOM is organized like a tree, in which elements are arranged hierarchically according to the structure of the document. The objects representing elements have properties such as parentNode and childNodes, which can be used to navigate through this tree.

+ +

The way a document is displayed can be influenced by styling, both by attaching styles to nodes directly and by defining rules that match certain nodes. There are many different style properties, such as color or display. JavaScript code can manipulate an element’s style directly through its style property.

+ +

Exercises

+ +

Build a table

+ +

An HTML table is built with the following tag structure:

+ +
<table>
+  <tr>
+    <th>name</th>
+    <th>height</th>
+    <th>place</th>
+  </tr>
+  <tr>
+    <td>Kilimanjaro</td>
+    <td>5895</td>
+    <td>Tanzania</td>
+  </tr>
+</table>
+ +

For each row, the <table> tag contains a <tr> tag. Inside of these <tr> tags, we can put cell elements: either heading cells (<th>) or regular cells (<td>).

+ +

Given a data set of mountains, an array of objects with name, height, and place properties, generate the DOM structure for a table that enumerates the objects. It should have one column per key and one row per object, plus a header row with <th> elements at the top, listing the column names.

+ +

Write this so that the columns are automatically derived from the objects, by taking the property names of the first object in the data.

+ +

Add the resulting table to the element with an id attribute of "mountains" so that it becomes visible in the document.

+ +

Once you have this working, right-align cells that contain number values by setting their style.textAlign property to "right".

+ +
<h1>Mountains</h1>
+
+<div id="mountains"></div>
+
+<script>
+  const MOUNTAINS = [
+    {name: "Kilimanjaro", height: 5895, place: "Tanzania"},
+    {name: "Everest", height: 8848, place: "Nepal"},
+    {name: "Mount Fuji", height: 3776, place: "Japan"},
+    {name: "Vaalserberg", height: 323, place: "Netherlands"},
+    {name: "Denali", height: 6168, place: "United States"},
+    {name: "Popocatepetl", height: 5465, place: "Mexico"},
+    {name: "Mont Blanc", height: 4808, place: "Italy/France"}
+  ];
+
+  // Your code here
+</script>
+ +
+ +

You can use document.createElement to create new element nodes, document.createTextNode to create text nodes, and the appendChild method to put nodes into other nodes.

+ +

You’ll want to loop over the key names once to fill in the top row and then again for each object in the array to construct the data rows. To get an array of key names from the first object, Object.keys will be useful.

+ +

To add the table to the correct parent node, you can use document.getElementById or document.querySelector to find the node with the proper id attribute.

+ +
+ +

Elements by tag name

+ +

The document.getElementsByTagName method returns all child elements with a given tag name. Implement your own version of this as a function that takes a node and a string (the tag name) as arguments and returns an array containing all descendant element nodes with the given tag name.

+ +

To find the tag name of an element, use its nodeName property. But note that this will return the tag name in all uppercase. Use the toLowerCase or toUpperCase string methods to compensate for this.

+ +
<h1>Heading with a <span>span</span> element.</h1>
+<p>A paragraph with <span>one</span>, <span>two</span>
+  spans.</p>
+
+<script>
+  function byTagName(node, tagName) {
+    // Your code here.
+  }
+
+  console.log(byTagName(document.body, "h1").length);
+  // → 1
+  console.log(byTagName(document.body, "span").length);
+  // → 3
+  let para = document.querySelector("p");
+  console.log(byTagName(para, "span").length);
+  // → 2
+</script>
+ +
+ +

The solution is most easily expressed with a recursive function, similar to the talksAbout function defined earlier in this chapter.

+ +

You could call byTagname itself recursively, concatenating the resulting arrays to produce the output. Or you could create an inner function that calls itself recursively and that has access to an array binding defined in the outer function, to which it can add the matching elements it finds. Don’t forget to call the inner +function once from the outer function to start the process.

+ +

The recursive function must check the node type. Here we are interested only in node type 1 (Node.ELEMENT_NODE). For such nodes, we must loop over their children and, for each child, see whether the child matches the query while also doing a recursive call on it to inspect its own children.

+ +
+ +

The cat’s hat

+ +

Extend the cat animation defined earlier so that both the cat and his hat (<img src="img/hat.png">) orbit at opposite sides of the ellipse.

+ +

Or make the hat circle around the cat. Or alter the animation in some other interesting way.

+ +

To make positioning multiple objects easier, it is probably a good idea to switch to absolute positioning. This means that top and left are counted relative to the top left of the document. To avoid using negative coordinates, which would cause the image to move outside of the visible page, you can add a fixed number of pixels to the position values.

+ +
<style>body { min-height: 200px }</style>
+<img src="img/cat.png" id="cat" style="position: absolute">
+<img src="img/hat.png" id="hat" style="position: absolute">
+
+<script>
+  let cat = document.querySelector("#cat");
+  let hat = document.querySelector("#hat");
+
+  let angle = 0;
+  let lastTime = null;
+  function animate(time) {
+    if (lastTime != null) angle += (time - lastTime) * 0.001;
+    lastTime = time;
+    cat.style.top = (Math.sin(angle) * 40 + 40) + "px";
+    cat.style.left = (Math.cos(angle) * 200 + 230) + "px";
+
+    // Your extensions here.
+
+    requestAnimationFrame(animate);
+  }
+  requestAnimationFrame(animate);
+</script>
+ +
+ +

Math.cos and Math.sin measure angles in radians, where a full circle is 2π. For a given angle, you can get the opposite angle by adding half of this, which is Math.PI. This can be useful for putting the hat on the opposite side of the orbit.

+ +
+
diff --git a/docs/15_event.html b/docs/15_event.html new file mode 100644 index 000000000..9c829e6cc --- /dev/null +++ b/docs/15_event.html @@ -0,0 +1,582 @@ + + + + + Handling Events :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 15Handling Events

+ +
+ +

You have power over your mind—not outside events. Realize this, and you will find strength.

+ +
Marcus Aurelius, Meditations
+ +
Picture a Rube Goldberg machine
+ +

Some programs work with direct user input, such as mouse and keyboard actions. That kind of input isn’t available as a well-organized data structure—it comes in piece by piece, in real time, and the program is expected to respond to it as it happens.

+ +

Event handlers

+ +

Imagine an interface where the only way to find out whether a key on the keyboard is being pressed is to read the current state of that key. To be able to react to keypresses, you would have to constantly read the key’s state so that you’d catch it before it’s released again. It would be dangerous to perform other time-intensive computations since you might miss a keypress.

+ +

Some primitive machines do handle input like that. A step up from this would be for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there.

+ +

Of course, it has to remember to look at the queue, and to do it often, because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called polling. Most programmers prefer to avoid it.

+ +

A better mechanism is for the system to actively notify our code when an event occurs. Browsers do this by allowing us to register functions as handlers for specific events.

+ +
<p>Click this document to activate the handler.</p>
+<script>
+  window.addEventListener("click", () => {
+    console.log("You knocked?");
+  });
+</script>
+ +

The window binding refers to a built-in object provided by the browser. It represents the browser window that contains the document. Calling its addEventListener method registers the second argument to be called whenever the event described by its first argument occurs.

+ +

Events and DOM nodes

+ +

Each browser event handler is registered in a context. In the previous example we called addEventListener on the window object to register a handler for the whole window. Such a method can also be found on DOM elements and some other types of objects. Event listeners are called only when the event happens in the context of the object they are registered on.

+ +
<button>Click me</button>
+<p>No handler here.</p>
+<script>
+  let button = document.querySelector("button");
+  button.addEventListener("click", () => {
+    console.log("Button clicked.");
+  });
+</script>
+ +

That example attaches a handler to the button node. Clicks on the button cause that handler to run, but clicks on the rest of the document do not.

+ +

Giving a node an onclick attribute has a similar effect. This works for most types of events—you can attach a handler through the attribute whose name is the event name with on in front of it.

+ +

But a node can have only one onclick attribute, so you can register only one handler per node that way. The addEventListener method allows you to add any number of handlers so that it is safe to add handlers even if there is already another handler on the element.

+ +

The removeEventListener method, called with arguments similar to addEventListener, removes a handler.

+ +
<button>Act-once button</button>
+<script>
+  let button = document.querySelector("button");
+  function once() {
+    console.log("Done.");
+    button.removeEventListener("click", once);
+  }
+  button.addEventListener("click", once);
+</script>
+ +

The function given to removeEventListener has to be the same function value that was given to addEventListener. So, to unregister a handler, you’ll want to give the function a name (once, in the example) to be able to pass the same function value to both methods.

+ +

Event objects

+ +

Though we have ignored it so far, event handler functions are passed an argument: the event object. This object holds additional information about the event. For example, if we want to know which mouse button was pressed, we can look at the event object’s button property.

+ +
<button>Click me any way you want</button>
+<script>
+  let button = document.querySelector("button");
+  button.addEventListener("mousedown", event => {
+    if (event.button == 0) {
+      console.log("Left button");
+    } else if (event.button == 1) {
+      console.log("Middle button");
+    } else if (event.button == 2) {
+      console.log("Right button");
+    }
+  });
+</script>
+ +

The information stored in an event object differs per type of event. We’ll discuss different types later in the chapter. The object’s type property always holds a string identifying the event (such as "click" or "mousedown").

+ +

Propagation

+ +

For most event types, handlers registered on nodes with children will also receive events that happen in the children. If a button inside a paragraph is clicked, event handlers on the paragraph will also see the click event.

+ +

But if both the paragraph and the button have a handler, the more specific handler—the one on the button—gets to go first. The event is said to propagate outward, from the node where it happened to that node’s parent node and on to the root of the document. Finally, after all handlers registered on a specific node have had their turn, handlers registered on the whole window get a chance to respond to the event.

+ +

At any point, an event handler can call the stopPropagation method on the event object to prevent handlers further up from receiving the event. This can be useful when, for example, you have a button inside another clickable element and you don’t want clicks on the button to activate the outer element’s click behavior.

+ +

The following example registers "mousedown" handlers on both a button and the paragraph around it. When clicked with the right mouse button, the handler for the button calls stopPropagation, which will prevent the handler on the paragraph from running. When the button is clicked with another mouse button, both handlers will run.

+ +
<p>A paragraph with a <button>button</button>.</p>
+<script>
+  let para = document.querySelector("p");
+  let button = document.querySelector("button");
+  para.addEventListener("mousedown", () => {
+    console.log("Handler for paragraph.");
+  });
+  button.addEventListener("mousedown", event => {
+    console.log("Handler for button.");
+    if (event.button == 2) event.stopPropagation();
+  });
+</script>
+ +

Most event objects have a target property that refers to the node where they originated. You can use this property to ensure that you’re not accidentally handling something that propagated up from a node you do not want to handle.

+ +

It is also possible to use the target property to cast a wide net for a specific type of event. For example, if you have a node containing a long list of buttons, it may be more convenient to register a single click handler on the outer node and have it use the target property to figure out whether a button was clicked, rather than register individual handlers on all of the buttons.

+ +
<button>A</button>
+<button>B</button>
+<button>C</button>
+<script>
+  document.body.addEventListener("click", event => {
+    if (event.target.nodeName == "BUTTON") {
+      console.log("Clicked", event.target.textContent);
+    }
+  });
+</script>
+ +

Default actions

+ +

Many events have a default action associated with them. If you click a link, you will be taken to the link’s target. If you press the down arrow, the browser will scroll the page down. If you right-click, you’ll get a context menu. And so on.

+ +

For most types of events, the JavaScript event handlers are called before the default behavior takes place. If the handler doesn’t want this normal behavior to happen, typically because it has already taken care of handling the event, it can call the preventDefault method on the event object.

+ +

This can be used to implement your own keyboard shortcuts or context menu. It can also be used to obnoxiously interfere with the behavior that users expect. For example, here is a link that cannot be followed:

+ +
<a href="https://developer.mozilla.org/">MDN</a>
+<script>
+  let link = document.querySelector("a");
+  link.addEventListener("click", event => {
+    console.log("Nope.");
+    event.preventDefault();
+  });
+</script>
+ +

Try not to do such things unless you have a really good reason to. It’ll be unpleasant for people who use your page when expected behavior is broken.

+ +

Depending on the browser, some events can’t be intercepted at all. On Chrome, for example, the keyboard shortcut to close the current tab (control-W or command-W) cannot be handled by JavaScript.

+ +

Key events

+ +

When a key on the keyboard is pressed, your browser fires a "keydown" event. When it is released, you get a "keyup" event.

+ +
<p>This page turns violet when you hold the V key.</p>
+<script>
+  window.addEventListener("keydown", event => {
+    if (event.key == "v") {
+      document.body.style.background = "violet";
+    }
+  });
+  window.addEventListener("keyup", event => {
+    if (event.key == "v") {
+      document.body.style.background = "";
+    }
+  });
+</script>
+ +

Despite its name, "keydown" fires not only when the key is physically pushed down. When a key is pressed and held, the event fires again every time the key repeats. Sometimes you have to be careful about this. For example, if you add a button to the DOM when a key is pressed and remove it again when the key is released, you might accidentally add hundreds of buttons when the key is held down longer.

+ +

The example looked at the key property of the event object to see which key the event is about. This property holds a string that, for most keys, corresponds to the thing that pressing that key would type. For special keys such as enter, it holds a string that names the key ("Enter", in this case). If you hold shift while pressing a key, that might also influence the name of the key—"v" becomes "V", and "1" may become "!", if that is what pressing shift-1 produces on your keyboard.

+ +

Modifier keys such as shift, control, alt, and meta (command on Mac) generate key events just like normal keys. But when looking for key combinations, you can also find out whether these keys are held down by looking at the shiftKey, ctrlKey, altKey, and metaKey properties of keyboard and mouse events.

+ +
<p>Press Control-Space to continue.</p>
+<script>
+  window.addEventListener("keydown", event => {
+    if (event.key == " " && event.ctrlKey) {
+      console.log("Continuing!");
+    }
+  });
+</script>
+ +

The DOM node where a key event originates depends on the element that has focus when the key is pressed. Most nodes cannot have focus unless you give them a tabindex attribute, but things like links, buttons, and form fields can. We’ll come back to form fields in Chapter 18. When nothing in particular has focus, document.body acts as the target node of key events.

+ +

When the user is typing text, using key events to figure out what is being typed is problematic. Some platforms, most notably the virtual +keyboard on Android phones, don’t fire key events. But even when you have an old-fashioned keyboard, some types of text input don’t match key presses in a straightforward way, such as input method editor (IME) software used by people whose scripts don’t fit on a keyboard, where multiple key strokes are combined to create characters.

+ +

To notice when something was typed, elements that you can type into, such as the <input> and <textarea> tags, fire "input" events whenever the user changes their content. To get the actual content that was typed, it is best to directly read it from the focused field. Chapter 18 will show how.

+ +

Pointer events

+ +

There are currently two widely used ways to point at things on a screen: mice (including devices that act like mice, such as touchpads and trackballs) and touchscreens. These produce different kinds of events.

+ +

Mouse clicks

+ +

Pressing a mouse button causes a number of events to fire. The "mousedown" and "mouseup" events are similar to "keydown" and "keyup" and fire when the button is pressed and released. These happen on the DOM nodes that are immediately below the mouse pointer when the event occurs.

+ +

After the "mouseup" event, a "click" event fires on the most specific node that contained both the press and the release of the button. For example, if I press down the mouse button on one paragraph and then move the pointer to another paragraph and release the button, the "click" event will happen on the element that contains both those paragraphs.

+ +

If two clicks happen close together, a "dblclick" (double-click) event also fires, after the second click event.

+ +

To get precise information about the place where a mouse event happened, you can look at its clientX and clientY properties, which contain the event’s coordinates (in pixels) relative to the top-left corner of the window, or pageX and pageY, which are relative to the top-left corner of the whole document (which may be different when the window has been scrolled).

+ +

The following implements a primitive drawing program. Every time you click the document, it adds a dot under your mouse pointer. See Chapter 19 for a less primitive drawing program.

+ +
<style>
+  body {
+    height: 200px;
+    background: beige;
+  }
+  .dot {
+    height: 8px; width: 8px;
+    border-radius: 4px; /* rounds corners */
+    background: blue;
+    position: absolute;
+  }
+</style>
+<script>
+  window.addEventListener("click", event => {
+    let dot = document.createElement("div");
+    dot.className = "dot";
+    dot.style.left = (event.pageX - 4) + "px";
+    dot.style.top = (event.pageY - 4) + "px";
+    document.body.appendChild(dot);
+  });
+</script>
+ +

Mouse motion

+ +

Every time the mouse pointer moves, a "mousemove" event is fired. This event can be used to track the position of the mouse. A common situation in which this is useful is when implementing some form of mouse-dragging functionality.

+ +

As an example, the following program displays a bar and sets up event handlers so that dragging to the left or right on this bar makes it narrower or wider:

+ +
<p>Drag the bar to change its width:</p>
+<div style="background: orange; width: 60px; height: 20px">
+</div>
+<script>
+  let lastX; // Tracks the last observed mouse X position
+  let bar = document.querySelector("div");
+  bar.addEventListener("mousedown", event => {
+    if (event.button == 0) {
+      lastX = event.clientX;
+      window.addEventListener("mousemove", moved);
+      event.preventDefault(); // Prevent selection
+    }
+  });
+
+  function moved(event) {
+    if (event.buttons == 0) {
+      window.removeEventListener("mousemove", moved);
+    } else {
+      let dist = event.clientX - lastX;
+      let newWidth = Math.max(10, bar.offsetWidth + dist);
+      bar.style.width = newWidth + "px";
+      lastX = event.clientX;
+    }
+  }
+</script>
+ +

Note that the "mousemove" handler is registered on the whole window. Even if the mouse goes outside of the bar during resizing, as long as the button is held we still want to update its size.

+ +

We must stop resizing the bar when the mouse button is released. For that, we can use the buttons property (note the plural), which tells us about the buttons that are currently held down. When this is zero, no buttons are down. When buttons are held, its value is the sum of the codes for those buttons—the left button has code 1, the right button 2, and the middle one 4. That way, you can check whether a given button is pressed by taking the remainder of the value of buttons and its code.

+ +

Note that the order of these codes is different from the one used by button, where the middle button came before the right one. As mentioned, consistency isn’t really a strong point of the browser’s programming interface.

+ +

Touch events

+ +

The style of graphical browser that we use was designed with mouse interfaces in mind, at a time where touchscreens were rare. To make the Web “work” on early touchscreen phones, browsers for those devices pretended, to a certain extent, that touch events were mouse events. If you tap your screen, you’ll get "mousedown", "mouseup", and "click" events.

+ +

But this illusion isn’t very robust. A touchscreen works differently from a mouse: it doesn’t have multiple buttons, you can’t track the finger when it isn’t on the screen (to simulate "mousemove"), and it allows multiple fingers to be on the screen at the same time.

+ +

Mouse events cover touch interaction only in straightforward cases—if you add a "click" handler to a button, touch users will still be able to use it. But something like the resizeable bar in the previous example does not work on a touchscreen.

+ +

There are specific event types fired by touch interaction. When a finger starts touching the screen, you get a "touchstart" event. When it is moved while touching, "touchmove" events fire. Finally, when it stops touching the screen, you’ll see a "touchend" event.

+ +

Because many touchscreens can detect multiple fingers at the same time, these events don’t have a single set of coordinates associated with them. Rather, their event objects have a touches property, which holds an array-like object of points, each of which has its own clientX, clientY, pageX, and pageY properties.

+ +

You could do something like this to show red circles around every touching finger:

+ +
<style>
+  dot { position: absolute; display: block;
+        border: 2px solid red; border-radius: 50px;
+        height: 100px; width: 100px; }
+</style>
+<p>Touch this page</p>
+<script>
+  function update(event) {
+    for (let dot; dot = document.querySelector("dot");) {
+      dot.remove();
+    }
+    for (let i = 0; i < event.touches.length; i++) {
+      let {pageX, pageY} = event.touches[i];
+      let dot = document.createElement("dot");
+      dot.style.left = (pageX - 50) + "px";
+      dot.style.top = (pageY - 50) + "px";
+      document.body.appendChild(dot);
+    }
+  }
+  window.addEventListener("touchstart", update);
+  window.addEventListener("touchmove", update);
+  window.addEventListener("touchend", update);
+</script>
+ +

You’ll often want to call preventDefault in touch event handlers to override the browser’s default behavior (which may include scrolling the page on swiping) and to prevent the mouse events from being fired, for which you may also have a handler.

+ +

Scroll events

+ +

Whenever an element is scrolled, a "scroll" event is fired on it. This has various uses, such as knowing what the user is currently looking at (for disabling off-screen animations or sending spy reports to your evil headquarters) or showing some indication of progress (by highlighting part of a table of contents or showing a page number).

+ +

The following example draws a progress bar above the document and updates it to fill up as you scroll down:

+ +
<style>
+  #progress {
+    border-bottom: 2px solid blue;
+    width: 0;
+    position: fixed;
+    top: 0; left: 0;
+  }
+</style>
+<div id="progress"></div>
+<script>
+  // Create some content
+  document.body.appendChild(document.createTextNode(
+    "supercalifragilisticexpialidocious ".repeat(1000)));
+
+  let bar = document.querySelector("#progress");
+  window.addEventListener("scroll", () => {
+    let max = document.body.scrollHeight - innerHeight;
+    bar.style.width = `${(pageYOffset / max) * 100}%`;
+  });
+</script>
+ +

Giving an element a position of fixed acts much like an absolute position but also prevents it from scrolling along with the rest of the document. The effect is to make our progress bar stay at the top. Its width is changed to indicate the current progress. We use %, rather than px, as a unit when setting the width so that the element is sized relative to the page width.

+ +

The global innerHeight binding gives us the height of the window, which we have to subtract from the total scrollable height—you can’t keep scrolling when you hit the bottom of the document. There’s also an innerWidth for the window width. By dividing pageYOffset, the current scroll position, by the maximum scroll position and multiplying by 100, we get the percentage for the progress bar.

+ +

Calling preventDefault on a scroll event does not prevent the scrolling from happening. In fact, the event handler is called only after the scrolling takes place.

+ +

Focus events

+ +

When an element gains focus, the browser fires a "focus" event on it. When it loses focus, the element gets a "blur" event.

+ +

Unlike the events discussed earlier, these two events do not propagate. A handler on a parent element is not notified when a child element gains or loses focus.

+ +

The following example displays help text for the text field that currently has focus:

+ +
<p>Name: <input type="text" data-help="Your full name"></p>
+<p>Age: <input type="text" data-help="Your age in years"></p>
+<p id="help"></p>
+
+<script>
+  let help = document.querySelector("#help");
+  let fields = document.querySelectorAll("input");
+  for (let field of Array.from(fields)) {
+    field.addEventListener("focus", event => {
+      let text = event.target.getAttribute("data-help");
+      help.textContent = text;
+    });
+    field.addEventListener("blur", event => {
+      help.textContent = "";
+    });
+  }
+</script>
+ +

The window object will receive "focus" and "blur" events when the user moves from or to the browser tab or window in which the document is shown.

+ +

Load event

+ +

When a page finishes loading, the "load" event fires on the window and the document body objects. This is often used to schedule initialization actions that require the whole document to have been built. Remember that the content of <script> tags is run immediately when the tag is encountered. This may be too soon, for example when the script needs to do something with parts of the document that appear after the <script> tag.

+ +

Elements such as images and script tags that load an external file also have a "load" event that indicates the files they reference were loaded. Like the focus-related events, loading events do not propagate.

+ +

When a page is closed or navigated away from (for example, by following a link), a "beforeunload" event fires. The main use of this event is to prevent the user from accidentally losing work by closing a document. If you prevent the default behavior on this event and set the returnValue property on the event object to a string, the browser will show the user a dialog asking if they really want to leave the page. That dialog might include your string, but because some malicious sites try to use these dialogs to confuse people into staying on their page to look at dodgy weight loss ads, most browsers no longer display them.

+ +

Events and the event loop

+ +

In the context of the event loop, as discussed in Chapter 11, browser event handlers behave like other asynchronous notifications. They are scheduled when the event occurs but must wait for other scripts that are running to finish before they get a chance to run.

+ +

The fact that events can be processed only when nothing else is running means that, if the event loop is tied up with other work, any interaction with the page (which happens through events) will be delayed until there’s time to process it. So if you schedule too much work, either with long-running event handlers or with lots of short-running ones, the page will become slow and cumbersome to use.

+ +

For cases where you really do want to do some time-consuming thing in the background without freezing the page, browsers provide something called web workers. A worker is a JavaScript process that runs alongside the main script, on its own timeline.

+ +

Imagine that squaring a number is a heavy, long-running computation that we want to perform in a separate thread. We could write a file called code/squareworker.js that responds to messages by computing a square and sending a message back.

+ +
addEventListener("message", event => {
+  postMessage(event.data * event.data);
+});
+ +

To avoid the problems of having multiple threads touching the same data, workers do not share their global scope or any other data with the main script’s environment. Instead, you have to communicate with them by sending messages back and forth.

+ +

This code spawns a worker running that script, sends it a few messages, and outputs the responses.

+ +
let squareWorker = new Worker("code/squareworker.js");
+squareWorker.addEventListener("message", event => {
+  console.log("The worker responded:", event.data);
+});
+squareWorker.postMessage(10);
+squareWorker.postMessage(24);
+ +

The postMessage function sends a message, which will cause a "message" event to fire in the receiver. The script that created the worker sends and receives messages through the Worker object, whereas the worker talks to the script that created it by sending and listening directly on its global scope. Only values that can be represented as JSON can be sent as messages—the other side will receive a copy of them, rather than the value itself.

+ +

Timers

+ +

We saw the setTimeout function in Chapter 11. It schedules another function to be called later, after a given number of milliseconds.

+ +

Sometimes you need to cancel a function you have scheduled. This is done by storing the value returned by setTimeout and calling clearTimeout on it.

+ +
let bombTimer = setTimeout(() => {
+  console.log("BOOM!");
+}, 500);
+
+if (Math.random() < 0.5) { // 50% chance
+  console.log("Defused.");
+  clearTimeout(bombTimer);
+}
+ +

The cancelAnimationFrame function works in the same way as clearTimeout—calling it on a value returned by requestAnimationFrame will cancel that frame (assuming it hasn’t already been called).

+ +

A similar set of functions, setInterval and clearInterval, are used to set timers that should repeat every X milliseconds.

+ +
let ticks = 0;
+let clock = setInterval(() => {
+  console.log("tick", ticks++);
+  if (ticks == 10) {
+    clearInterval(clock);
+    console.log("stop.");
+  }
+}, 200);
+ +

Debouncing

+ +

Some types of events have the potential to fire rapidly, many times in a row (the "mousemove" and "scroll" events, for example). When handling such events, you must be careful not to do anything too time-consuming or your handler will take up so much time that interaction with the document starts to feel slow.

+ +

If you do need to do something nontrivial in such a handler, you can use setTimeout to make sure you are not doing it too often. This is usually called debouncing the event. There are several slightly different approaches to this.

+ +

In the first example, we want to react when the user has typed something, but we don’t want to do it immediately for every input event. When they are typing quickly, we just want to wait until a pause occurs. Instead of immediately performing an action in the event handler, we set a timeout. We also clear the previous timeout (if any) so that when events occur close together (closer than our timeout delay), the timeout from the previous event will be canceled.

+ +
<textarea>Type something here...</textarea>
+<script>
+  let textarea = document.querySelector("textarea");
+  let timeout;
+  textarea.addEventListener("input", () => {
+    clearTimeout(timeout);
+    timeout = setTimeout(() => console.log("Typed!"), 500);
+  });
+</script>
+ +

Giving an undefined value to clearTimeout or calling it on a timeout that has already fired has no effect. Thus, we don’t have to be careful about when to call it, and we simply do so for every event.

+ +

We can use a slightly different pattern if we want to space responses so that they’re separated by at least a certain length of time but want to fire them during a series of events, not just afterward. For example, we might want to respond to "mousemove" events by showing the current coordinates of the mouse but only every 250 milliseconds.

+ +
<script>
+  let scheduled = null;
+  window.addEventListener("mousemove", event => {
+    if (!scheduled) {
+      setTimeout(() => {
+        document.body.textContent =
+          `Mouse at ${scheduled.pageX}, ${scheduled.pageY}`;
+        scheduled = null;
+      }, 250);
+    }
+    scheduled = event;
+  });
+</script>
+ +

Summary

+ +

Event handlers make it possible to detect and react to events happening in our web page. The addEventListener method is used to register such a handler.

+ +

Each event has a type ("keydown", "focus", and so on) that identifies it. Most events are called on a specific DOM element and then propagate to that element’s ancestors, allowing handlers associated with those elements to handle them.

+ +

When an event handler is called, it is passed an event object with additional information about the event. This object also has methods that allow us to stop further propagation (stopPropagation) and prevent the browser’s default handling of the event (preventDefault).

+ +

Pressing a key fires "keydown" and "keyup" events. Pressing a mouse button fires "mousedown", "mouseup", and "click" events. Moving the mouse fires "mousemove" events. Touchscreen interaction will result in "touchstart", "touchmove", and "touchend" events.

+ +

Scrolling can be detected with the "scroll" event, and focus changes can be detected with the "focus" and "blur" events. When the document finishes loading, a "load" event fires on the window.

+ +

Exercises

+ +

Balloon

+ +

Write a page that displays a balloon (using the balloon emoji, 🎈). When you press the up arrow, it should inflate (grow) 10 percent, and when you press the down arrow, it should deflate (shrink) 10 percent.

+ +

You can control the size of text (emoji are text) by setting the font-size CSS property (style.fontSize) on its parent element. Remember to include a unit in the value—for example, pixels (10px).

+ +

The key names of the arrow keys are "ArrowUp" and "ArrowDown". Make sure the keys change only the balloon, without scrolling the page.

+ +

When that works, add a feature where, if you blow up the balloon past a certain size, it explodes. In this case, exploding means that it is replaced with an 💥 emoji, and the event handler is removed (so that you can’t inflate or deflate the explosion).

+ +
<p>🎈</p>
+
+<script>
+  // Your code here
+</script>
+ +
+ +

You’ll want to register a handler for the "keydown" event and look at event.key to figure out whether the up or down arrow key was pressed.

+ +

The current size can be kept in a binding so that you can base the new size on it. It’ll be helpful to define a function that updates the size—both the binding and the style of the balloon in the DOM—so that you can call it from your event handler, and possibly also once when starting, to set the initial size.

+ +

You can change the balloon to an explosion by replacing the text node with another one (using replaceChild) or by setting the textContent property of its parent node to a new string.

+ +
+ +

Mouse trail

+ +

In JavaScript’s early days, which was the high time of gaudy home +pages with lots of animated images, people came up with some truly inspiring ways to use the language.

+ +

One of these was the mouse trail—a series of elements that would follow the mouse pointer as you moved it across the page.

+ +

In this exercise, I want you to implement a mouse trail. Use absolutely positioned <div> elements with a fixed size and background color (refer to the code in the “Mouse Clicks” section for an example). Create a bunch of such elements and, when the mouse moves, display them in the wake of the mouse pointer.

+ +

There are various possible approaches here. You can make your solution as simple or as complex as you want. A simple solution to start with is to keep a fixed number of trail elements and cycle through them, moving the next one to the mouse’s current position every time a "mousemove" event occurs.

+ +
<style>
+  .trail { /* className for the trail elements */
+    position: absolute;
+    height: 6px; width: 6px;
+    border-radius: 3px;
+    background: teal;
+  }
+  body {
+    height: 300px;
+  }
+</style>
+
+<script>
+  // Your code here.
+</script>
+ +
+ +

Creating the elements is best done with a loop. Append them to the document to make them show up. To be able to access them later to change their position, you’ll want to store the elements in an array.

+ +

Cycling through them can be done by keeping a counter variable and adding 1 to it every time the "mousemove" event fires. The remainder operator (% elements.length) can then be used to get a valid array index to pick the element you want to position during a given event.

+ +

Another interesting effect can be achieved by modeling a simple physics system. Use the "mousemove" event only to update a pair of bindings that track the mouse position. Then use requestAnimationFrame to simulate the trailing elements being attracted to the position of the mouse pointer. At every animation step, update their position based on their position relative to the pointer (and, optionally, a speed that is stored for each element). Figuring out a good way to do this is up to you.

+ +
+ +

Tabs

+ +

Tabbed panels are widely used in user interfaces. They allow you to select an interface panel by choosing from a number of tabs “sticking out” above an element.

+ +

In this exercise you must implement a simple tabbed interface. Write a function, asTabs, that takes a DOM node and creates a tabbed interface showing the child elements of that node. It should insert a list of <button> elements at the top of the node, one for each child element, containing text retrieved from the data-tabname attribute of the child. All but one of the original children should be hidden (given a display style of none). The currently visible node can be selected by clicking the buttons.

+ +

When that works, extend it to style the button for the currently selected tab differently so that it is obvious which tab is selected.

+ +
<tab-panel>
+  <div data-tabname="one">Tab one</div>
+  <div data-tabname="two">Tab two</div>
+  <div data-tabname="three">Tab three</div>
+</tab-panel>
+<script>
+  function asTabs(node) {
+    // Your code here.
+  }
+  asTabs(document.querySelector("tab-panel"));
+</script>
+ +
+ +

One pitfall you might run into is that you can’t directly use the node’s childNodes property as a collection of tab nodes. For one thing, when you add the buttons, they will also become child nodes and end up in this object because it is a live data structure. For another, the text nodes created for the whitespace between the nodes are also in childNodes but should not get their own tabs. You can use children instead of childNodes to ignore text nodes.

+ +

You could start by building up an array of tabs so that you have easy access to them. To implement the styling of the buttons, you could store objects that contain both the tab panel and its button.

+ +

I recommend writing a separate function for changing tabs. You can either store the previously selected tab and change only the styles needed to hide that and show the new one, or you can just update the style of all tabs every time a new tab is selected.

+ +

You might want to call this function immediately to make the interface start with the first tab visible.

+ +
+
diff --git a/docs/16_game.html b/docs/16_game.html new file mode 100644 index 000000000..697a1a327 --- /dev/null +++ b/docs/16_game.html @@ -0,0 +1,795 @@ + + + + + Project: A Platform Game :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 16Project: A Platform Game

+ +
+ +

All reality is a game.

+ +
Iain Banks, The Player of Games
+ +
Picture of a game character jumping over lava
+ +

Much of my initial fascination with computers, like that of many nerdy kids, had to do with computer games. I was drawn into the tiny simulated worlds that I could manipulate and in which stories (sort of) unfolded—more, I suppose, because of the way I projected my imagination into them than because of the possibilities they actually offered.

+ +

I don’t wish a career in game programming on anyone. Much like the music industry, the discrepancy between the number of eager young people wanting to work in it and the actual demand for such people creates a rather unhealthy environment. But writing games for fun is amusing.

+ +

This chapter will walk through the implementation of a small platform game. Platform games (or “jump and run” games) are games that expect the player to move a figure through a world, which is usually two-dimensional and viewed from the side, while jumping over and onto things.

+ +

The game

+ +

Our game will be roughly based on Dark Blue by Thomas Palef. I chose that game because it is both entertaining and minimalist and because it can be built without too much code. It looks like this:

The game Dark Blue
+ +

The dark box represents the player, whose task is to collect the yellow boxes (coins) while avoiding the red stuff (lava). A level is completed when all coins have been collected.

+ +

The player can walk around with the left and right arrow keys and can jump with the up arrow. Jumping is a specialty of this game character. It can reach several times its own height and can change direction in midair. This may not be entirely realistic, but it helps give the player the feeling of being in direct control of the on-screen avatar.

+ +

The game consists of a static background, laid out like a grid, with the moving elements overlaid on that background. Each field on the grid is either empty, solid, or lava. The moving elements are the player, coins, and certain pieces of lava. The positions of these elements are not constrained to the grid—their coordinates may be fractional, allowing smooth motion.

+ +

The technology

+ +

We will use the browser DOM to display the game, and we’ll read user input by handling key events.

+ +

The screen- and keyboard-related code is only a small part of the work we need to do to build this game. Since everything looks like colored boxes, drawing is uncomplicated: we create DOM elements and use styling to give them a background color, size, and position.

+ +

We can represent the background as a table since it is an unchanging grid of squares. The free-moving elements can be overlaid using absolutely positioned elements.

+ +

In games and other programs that should animate graphics and respond to user input without noticeable delay, efficiency is important. Although the DOM was not originally designed for high-performance graphics, it is actually better at this than you would expect. You saw some animations in Chapter 14. On a modern machine, a simple game like this performs well, even if we don’t worry about optimization very much.

+ +

In the next chapter, we will explore another browser technology, the <canvas> tag, which provides a more traditional way to draw graphics, working in terms of shapes and pixels rather than DOM elements.

+ +

Levels

+ +

We’ll want a human-readable, human-editable way to specify levels. Since it is okay for everything to start out on a grid, we could use big strings in which each character represents an element—either a part of the background grid or a moving element.

+ +

The plan for a small level might look like this:

+ +
let simpleLevelPlan = `
+......................
+..#................#..
+..#..............=.#..
+..#.........o.o....#..
+..#.@......#####...#..
+..#####............#..
+......#++++++++++++#..
+......##############..
+......................`;
+ +

Periods are empty space, hash (#) characters are walls, and plus signs are lava. The player’s starting position is the at sign (@). Every O character is a coin, and the equal sign (=) at the top is a block of lava that moves back and forth horizontally.

+ +

We’ll support two additional kinds of moving lava: the pipe character (|) creates vertically moving blobs, and v indicates dripping lava—vertically moving lava that doesn’t bounce back and forth but only moves down, jumping back to its start position when it hits the floor.

+ +

A whole game consists of multiple levels that the player must complete. A level is completed when all coins have been collected. If the player touches lava, the current level is restored to its starting position, and the player may try again.

+ +

Reading a level

+ +

The following class stores a level object. Its argument should be the string that defines the level.

+ +
class Level {
+  constructor(plan) {
+    let rows = plan.trim().split("\n").map(l => [...l]);
+    this.height = rows.length;
+    this.width = rows[0].length;
+    this.startActors = [];
+
+    this.rows = rows.map((row, y) => {
+      return row.map((ch, x) => {
+        let type = levelChars[ch];
+        if (typeof type == "string") return type;
+        this.startActors.push(
+          type.create(new Vec(x, y), ch));
+        return "empty";
+      });
+    });
+  }
+}
+ +

The trim method is used to remove whitespace at the start and end of the plan string. This allows our example plan to start with a newline so that all the lines are directly below each other. The remaining string is split on newline characters, and each line is spread into an array, producing arrays of characters.

+ +

So rows holds an array of arrays of characters, the rows of the plan. We can derive the level’s width and height from these. But we must still separate the moving elements from the background grid. We’ll call moving elements actors. They’ll be stored in an array of objects. The background will be an array of arrays of strings, holding field types such as "empty", "wall", or "lava".

+ +

To create these arrays, we map over the rows and then over their content. Remember that map passes the array index as a second argument to the mapping function, which tells us the x- and y-coordinates of a given character. Positions in the game will be stored as pairs of coordinates, with the top left being 0,0 and each background square being 1 unit high and wide.

+ +

To interpret the characters in the plan, the Level constructor uses the levelChars object, which maps background elements to strings and actor characters to classes. When type is an actor class, its static create method is used to create an object, which is added to startActors, and the mapping function returns "empty" for this background square.

+ +

The position of the actor is stored as a Vec object. This is a two-dimensional vector, an object with x and y properties, as seen in the exercises of Chapter 6.

+ +

As the game runs, actors will end up in different places or even disappear entirely (as coins do when collected). We’ll use a State class to track the state of a running game.

+ +
class State {
+  constructor(level, actors, status) {
+    this.level = level;
+    this.actors = actors;
+    this.status = status;
+  }
+
+  static start(level) {
+    return new State(level, level.startActors, "playing");
+  }
+
+  get player() {
+    return this.actors.find(a => a.type == "player");
+  }
+}
+ +

The status property will switch to "lost" or "won" when the game has ended.

+ +

This is again a persistent data structure—updating the game state creates a new state and leaves the old one intact.

+ +

Actors

+ +

Actor objects represent the current position and state of a given moving element in our game. All actor objects conform to the same interface. Their pos property holds the coordinates of the element’s top-left corner, and their size property holds its size.

+ +

Then they have an update method, which is used to compute their new state and position after a given time step. It simulates the thing the actor does—moving in response to the arrow keys for the player and bouncing back and forth for the lava—and returns a new, updated actor object.

+ +

A type property contains a string that identifies the type of the actor—"player", "coin", or "lava". This is useful when drawing the game—the look of the rectangle drawn for an actor is based on its type.

+ +

Actor classes have a static create method that is used by the Level constructor to create an actor from a character in the level plan. It is given the coordinates of the character and the character itself, which is needed because the Lava class handles several different characters.

+ +

This is the Vec class that we’ll use for our two-dimensional values, such as the position and size of actors.

+ +
class Vec {
+  constructor(x, y) {
+    this.x = x; this.y = y;
+  }
+  plus(other) {
+    return new Vec(this.x + other.x, this.y + other.y);
+  }
+  times(factor) {
+    return new Vec(this.x * factor, this.y * factor);
+  }
+}
+ +

The times method scales a vector by a given number. It will be useful when we need to multiply a speed vector by a time interval to get the distance traveled during that time.

+ +

The different types of actors get their own classes since their behavior is very different. Let’s define these classes. We’ll get to their update methods later.

+ +

The player class has a property speed that stores its current speed to simulate momentum and gravity.

+ +
class Player {
+  constructor(pos, speed) {
+    this.pos = pos;
+    this.speed = speed;
+  }
+
+  get type() { return "player"; }
+
+  static create(pos) {
+    return new Player(pos.plus(new Vec(0, -0.5)),
+                      new Vec(0, 0));
+  }
+}
+
+Player.prototype.size = new Vec(0.8, 1.5);
+ +

Because a player is one-and-a-half squares high, its initial position is set to be half a square above the position where the @ character appeared. This way, its bottom aligns with the bottom of the square it appeared in.

+ +

The size property is the same for all instances of Player, so we store it on the prototype rather than on the instances themselves. We could have used a getter like type, but that would create and return a new Vec object every time the property is read, which would be wasteful. (Strings, being immutable, don’t have to be re-created every time they are evaluated.)

+ +

When constructing a Lava actor, we need to initialize the object differently depending on the character it is based on. Dynamic lava moves along at its current speed until it hits an obstacle. At that point, if it has a reset property, it will jump back to its start position (dripping). If it does not, it will invert its speed and continue in the other direction (bouncing).

+ +

The create method looks at the character that the Level constructor passes and creates the appropriate lava actor.

+ +
class Lava {
+  constructor(pos, speed, reset) {
+    this.pos = pos;
+    this.speed = speed;
+    this.reset = reset;
+  }
+
+  get type() { return "lava"; }
+
+  static create(pos, ch) {
+    if (ch == "=") {
+      return new Lava(pos, new Vec(2, 0));
+    } else if (ch == "|") {
+      return new Lava(pos, new Vec(0, 2));
+    } else if (ch == "v") {
+      return new Lava(pos, new Vec(0, 3), pos);
+    }
+  }
+}
+
+Lava.prototype.size = new Vec(1, 1);
+ +

Coin actors are relatively simple. They mostly just sit in their place. But to liven up the game a little, they are given a “wobble”, a slight vertical back-and-forth motion. To track this, a coin object stores a base position as well as a wobble property that tracks the phase of the bouncing motion. Together, these determine the coin’s actual position (stored in the pos property).

+ +
class Coin {
+  constructor(pos, basePos, wobble) {
+    this.pos = pos;
+    this.basePos = basePos;
+    this.wobble = wobble;
+  }
+
+  get type() { return "coin"; }
+
+  static create(pos) {
+    let basePos = pos.plus(new Vec(0.2, 0.1));
+    return new Coin(basePos, basePos,
+                    Math.random() * Math.PI * 2);
+  }
+}
+
+Coin.prototype.size = new Vec(0.6, 0.6);
+ +

In Chapter 14, we saw that Math.sin gives us the y-coordinate of a point on a circle. That coordinate goes back and forth in a smooth waveform as we move along the circle, which makes the sine function useful for modeling a wavy motion.

+ +

To avoid a situation where all coins move up and down synchronously, the starting phase of each coin is randomized. The phase of Math.sin’s wave, the width of a wave it produces, is 2π. We multiply the value returned by Math.random by that number to give the coin a random starting position on the wave.

+ +

We can now define the levelChars object that maps plan characters to either background grid types or actor classes.

+ +
const levelChars = {
+  ".": "empty", "#": "wall", "+": "lava",
+  "@": Player, "o": Coin,
+  "=": Lava, "|": Lava, "v": Lava
+};
+ +

That gives us all the parts needed to create a Level instance.

+ +
let simpleLevel = new Level(simpleLevelPlan);
+console.log(`${simpleLevel.width} by ${simpleLevel.height}`);
+// → 22 by 9
+ +

The task ahead is to display such levels on the screen and to model time and motion inside them.

+ +

Encapsulation as a burden

+ +

Most of the code in this chapter does not worry about encapsulation very much for two reasons. First, encapsulation takes extra effort. It makes programs bigger and requires additional concepts and interfaces to be introduced. Since there is only so much code you can throw at a reader before their eyes glaze over, I’ve made an effort to keep the program small.

+ +

Second, the various elements in this game are so closely tied together that if the behavior of one of them changed, it is unlikely that any of the others would be able to stay the same. Interfaces between the elements would end up encoding a lot of assumptions about the way the game works. This makes them a lot less effective—whenever you change one part of the system, you still have to worry about the way it impacts the other parts because their interfaces wouldn’t cover the new situation.

+ +

Some cutting points in a system lend themselves well to separation through rigorous interfaces, but others don’t. Trying to encapsulate something that isn’t a suitable boundary is a sure way to waste a lot of energy. When you are making this mistake, you’ll usually notice that your interfaces are getting awkwardly large and detailed and that they need to be changed often, as the program evolves.

+ +

There is one thing that we will encapsulate, and that is the drawing subsystem. The reason for this is that we’ll display the same game in a different way in the next chapter. By putting the drawing behind an interface, we can load the same game program there and plug in a new display module.

+ +

Drawing

+ +

The encapsulation of the drawing code is done by defining a display object, which displays a given level and state. The display type we define in this chapter is called DOMDisplay because it uses DOM elements to show the level.

+ +

We’ll be using a style sheet to set the actual colors and other fixed properties of the elements that make up the game. It would also be possible to directly assign to the elements’ style property when we create them, but that would produce more verbose programs.

+ +

The following helper function provides a succinct way to create an element and give it some attributes and child nodes:

+ +
function elt(name, attrs, ...children) {
+  let dom = document.createElement(name);
+  for (let attr of Object.keys(attrs)) {
+    dom.setAttribute(attr, attrs[attr]);
+  }
+  for (let child of children) {
+    dom.appendChild(child);
+  }
+  return dom;
+}
+ +

A display is created by giving it a parent element to which it should append itself and a level object.

+ +
class DOMDisplay {
+  constructor(parent, level) {
+    this.dom = elt("div", {class: "game"}, drawGrid(level));
+    this.actorLayer = null;
+    parent.appendChild(this.dom);
+  }
+
+  clear() { this.dom.remove(); }
+}
+ +

The level’s background grid, which never changes, is drawn once. Actors are redrawn every time the display is updated with a given state. The actorLayer property will be used to track the element that holds the actors so that they can be easily removed and replaced.

+ +

Our coordinates and sizes are tracked in grid units, where a size or distance of 1 means one grid block. When setting pixel sizes, we will have to scale these coordinates up—everything in the game would be ridiculously small at a single pixel per square. The scale constant gives the number of pixels that a single unit takes up on the screen.

+ +
const scale = 20;
+
+function drawGrid(level) {
+  return elt("table", {
+    class: "background",
+    style: `width: ${level.width * scale}px`
+  }, ...level.rows.map(row =>
+    elt("tr", {style: `height: ${scale}px`},
+        ...row.map(type => elt("td", {class: type})))
+  ));
+}
+ +

As mentioned, the background is drawn as a <table> element. This nicely corresponds to the structure of the rows property of the level—each row of the grid is turned into a table row (<tr> element). The strings in the grid are used as class names for the table cell (<td>) elements. The spread (triple dot) operator is used to pass arrays of child nodes to elt as separate arguments.

+ +

The following CSS makes the table look like the background we want:

+ +
.background    { background: rgb(52, 166, 251);
+                 table-layout: fixed;
+                 border-spacing: 0;              }
+.background td { padding: 0;                     }
+.lava          { background: rgb(255, 100, 100); }
+.wall          { background: white;              }
+ +

Some of these (table-layout, border-spacing, and padding) are used to suppress unwanted default behavior. We don’t want the layout of the table to depend upon the contents of its cells, and we don’t want space between the table cells or padding inside them.

+ +

The background rule sets the background color. CSS allows colors to be specified both as words (white) or with a format such as rgb(R, G, B), where the red, green, and blue components of the color are separated into three numbers from 0 to 255. So, in rgb(52, 166, 251), the red component is 52, green is 166, and blue is 251. Since the blue component is the largest, the resulting color will be bluish. You can see that in the .lava rule, the first number (red) is the largest.

+ +

We draw each actor by creating a DOM element for it and setting that element’s position and size based on the actor’s properties. The values have to be multiplied by scale to go from game units to pixels.

+ +
function drawActors(actors) {
+  return elt("div", {}, ...actors.map(actor => {
+    let rect = elt("div", {class: `actor ${actor.type}`});
+    rect.style.width = `${actor.size.x * scale}px`;
+    rect.style.height = `${actor.size.y * scale}px`;
+    rect.style.left = `${actor.pos.x * scale}px`;
+    rect.style.top = `${actor.pos.y * scale}px`;
+    return rect;
+  }));
+}
+ +

To give an element more than one class, we separate the class names by spaces. In the CSS code shown next, the actor class gives the actors their absolute position. Their type name is used as an extra class to give them a color. We don’t have to define the lava class again because we’re reusing the class for the lava grid squares we defined earlier.

+ +
.actor  { position: absolute;            }
+.coin   { background: rgb(241, 229, 89); }
+.player { background: rgb(64, 64, 64);   }
+ +

The syncState method is used to make the display show a given state. It first removes the old actor graphics, if any, and then redraws the actors in their new positions. It may be tempting to try to reuse the DOM elements for actors, but to make that work, we would need a lot of additional bookkeeping to associate actors with DOM elements and to make sure we remove elements when their actors vanish. Since there will typically be only a handful of actors in the game, redrawing all of them is not expensive.

+ +
DOMDisplay.prototype.syncState = function(state) {
+  if (this.actorLayer) this.actorLayer.remove();
+  this.actorLayer = drawActors(state.actors);
+  this.dom.appendChild(this.actorLayer);
+  this.dom.className = `game ${state.status}`;
+  this.scrollPlayerIntoView(state);
+};
+ +

By adding the level’s current status as a class name to the wrapper, we can style the player actor slightly differently when the game is won or lost by adding a CSS rule that takes effect only when the player has an ancestor element with a given class.

+ +
.lost .player {
+  background: rgb(160, 64, 64);
+}
+.won .player {
+  box-shadow: -4px -7px 8px white, 4px -7px 8px white;
+}
+ +

After touching lava, the player’s color turns dark red, suggesting scorching. When the last coin has been collected, we add two blurred white shadows—one to the top left and one to the top right—to create a white halo effect.

+ +

We can’t assume that the level always fits in the viewport—the element into which we draw the game. That is why the scrollPlayerIntoView call is needed. It ensures that if the level is protruding outside the viewport, we scroll that viewport to make sure the player is near its center. The following CSS gives the game’s wrapping DOM element a maximum size and ensures that anything that sticks out of the element’s box is not visible. We also give it a relative position so that the actors inside it are positioned relative to the level’s top-left corner.

+ +
.game {
+  overflow: hidden;
+  max-width: 600px;
+  max-height: 450px;
+  position: relative;
+}
+ +

In the scrollPlayerIntoView method, we find the player’s position and update the wrapping element’s scroll position. We change the scroll position by manipulating that element’s scrollLeft and scrollTop properties when the player is too close to the edge.

+ +
DOMDisplay.prototype.scrollPlayerIntoView = function(state) {
+  let width = this.dom.clientWidth;
+  let height = this.dom.clientHeight;
+  let margin = width / 3;
+
+  // The viewport
+  let left = this.dom.scrollLeft, right = left + width;
+  let top = this.dom.scrollTop, bottom = top + height;
+
+  let player = state.player;
+  let center = player.pos.plus(player.size.times(0.5))
+                         .times(scale);
+
+  if (center.x < left + margin) {
+    this.dom.scrollLeft = center.x - margin;
+  } else if (center.x > right - margin) {
+    this.dom.scrollLeft = center.x + margin - width;
+  }
+  if (center.y < top + margin) {
+    this.dom.scrollTop = center.y - margin;
+  } else if (center.y > bottom - margin) {
+    this.dom.scrollTop = center.y + margin - height;
+  }
+};
+ +

The way the player’s center is found shows how the methods on our Vec type allow computations with objects to be written in a relatively readable way. To find the actor’s center, we add its position (its top-left corner) and half its size. That is the center in level coordinates, but we need it in pixel coordinates, so we then multiply the resulting vector by our display scale.

+ +

Next, a series of checks verifies that the player position isn’t outside of the allowed range. Note that sometimes this will set nonsense scroll coordinates that are below zero or beyond the element’s scrollable area. This is okay—the DOM will constrain them to acceptable values. Setting scrollLeft to -10 will cause it to become 0.

+ +

It would have been slightly simpler to always try to scroll the player to the center of the viewport. But this creates a rather jarring effect. As you are jumping, the view will constantly shift up and down. It is more pleasant to have a “neutral” area in the middle of the screen where you can move around without causing any scrolling.

+ +

We are now able to display our tiny level.

+ +
<link rel="stylesheet" href="css/game.css">
+
+<script>
+  let simpleLevel = new Level(simpleLevelPlan);
+  let display = new DOMDisplay(document.body, simpleLevel);
+  display.syncState(State.start(simpleLevel));
+</script>
+ +

The <link> tag, when used with rel="stylesheet", is a way to load a CSS file into a page. The file game.css contains the styles necessary for our game.

+ +

Motion and collision

+ +

Now we’re at the point where we can start adding motion—the most interesting aspect of the game. The basic approach, taken by most games like this, is to split time into small steps and, for each step, move the actors by a distance corresponding to their speed multiplied by the size of the time step. We’ll measure time in seconds, so speeds are expressed in units per second.

+ +

Moving things is easy. The difficult part is dealing with the interactions between the elements. When the player hits a wall or floor, they should not simply move through it. The game must notice when a given motion causes an object to hit another object and respond accordingly. For walls, the motion must be stopped. When hitting a coin, it must be collected. When touching lava, the game should be lost.

+ +

Solving this for the general case is a big task. You can find libraries, usually called physics engines, that simulate interaction between physical objects in two or three dimensions. We’ll take a more modest approach in this chapter, handling only collisions between rectangular objects and handling them in a rather simplistic way.

+ +

Before moving the player or a block of lava, we test whether the motion would take it inside of a wall. If it does, we simply cancel the motion altogether. The response to such a collision depends on the type of actor—the player will stop, whereas a lava block will bounce back.

+ +

This approach requires our time steps to be rather small since it will cause motion to stop before the objects actually touch. If the time steps (and thus the motion steps) are too big, the player would end up hovering a noticeable distance above the ground. Another approach, arguably better but more complicated, would be to find the exact collision spot and move there. We will take the simple approach and hide its problems by ensuring the animation proceeds in small steps.

+ +

This method tells us whether a rectangle (specified by a position and a size) touches a grid element of the given type.

+ +
Level.prototype.touches = function(pos, size, type) {
+  var xStart = Math.floor(pos.x);
+  var xEnd = Math.ceil(pos.x + size.x);
+  var yStart = Math.floor(pos.y);
+  var yEnd = Math.ceil(pos.y + size.y);
+
+  for (var y = yStart; y < yEnd; y++) {
+    for (var x = xStart; x < xEnd; x++) {
+      let isOutside = x < 0 || x >= this.width ||
+                      y < 0 || y >= this.height;
+      let here = isOutside ? "wall" : this.rows[y][x];
+      if (here == type) return true;
+    }
+  }
+  return false;
+};
+ +

The method computes the set of grid squares that the body overlaps with by using Math.floor and Math.ceil on its coordinates. Remember that grid squares are 1 by 1 units in size. By rounding the sides of a box up and down, we get the range of background squares that the box touches.

Finding collisions on a grid
+ +

We loop over the block of grid squares found by rounding the coordinates and return true when a matching square is found. Squares outside of the level are always treated as "wall" to ensure that the player can’t leave the world and that we won’t accidentally try to read outside of the bounds of our rows array.

+ +

The state update method uses touches to figure out whether the player is touching lava.

+ +
State.prototype.update = function(time, keys) {
+  let actors = this.actors
+    .map(actor => actor.update(time, this, keys));
+  let newState = new State(this.level, actors, this.status);
+
+  if (newState.status != "playing") return newState;
+
+  let player = newState.player;
+  if (this.level.touches(player.pos, player.size, "lava")) {
+    return new State(this.level, actors, "lost");
+  }
+
+  for (let actor of actors) {
+    if (actor != player && overlap(actor, player)) {
+      newState = actor.collide(newState);
+    }
+  }
+  return newState;
+};
+ +

The method is passed a time step and a data structure that tells it which keys are being held down. The first thing it does is call the update method on all actors, producing an array of updated actors. The actors also get the time step, the keys, and the state, so that they can base their update on those. Only the player will actually read keys, since that’s the only actor that’s controlled by the keyboard.

+ +

If the game is already over, no further processing has to be done (the game can’t be won after being lost, or vice versa). Otherwise, the method tests whether the player is touching background lava. If so, the game is lost, and we’re done. Finally, if the game really is still going on, it sees whether any other actors overlap the player.

+ +

Overlap between actors is detected with the overlap function. It takes two actor objects and returns true when they touch—which is the case when they overlap both along the x-axis and along the y-axis.

+ +
function overlap(actor1, actor2) {
+  return actor1.pos.x + actor1.size.x > actor2.pos.x &&
+         actor1.pos.x < actor2.pos.x + actor2.size.x &&
+         actor1.pos.y + actor1.size.y > actor2.pos.y &&
+         actor1.pos.y < actor2.pos.y + actor2.size.y;
+}
+ +

If any actor does overlap, its collide method gets a chance to update the state. Touching a lava actor sets the game status to "lost". Coins vanish when you touch them and set the status to "won" when they are the last coin of the level.

+ +
Lava.prototype.collide = function(state) {
+  return new State(state.level, state.actors, "lost");
+};
+
+Coin.prototype.collide = function(state) {
+  let filtered = state.actors.filter(a => a != this);
+  let status = state.status;
+  if (!filtered.some(a => a.type == "coin")) status = "won";
+  return new State(state.level, filtered, status);
+};
+ +

Actor updates

+ +

Actor objects’ update methods take as arguments the time step, the state object, and a keys object. The one for the Lava actor type ignores the keys object.

+ +
Lava.prototype.update = function(time, state) {
+  let newPos = this.pos.plus(this.speed.times(time));
+  if (!state.level.touches(newPos, this.size, "wall")) {
+    return new Lava(newPos, this.speed, this.reset);
+  } else if (this.reset) {
+    return new Lava(this.reset, this.speed, this.reset);
+  } else {
+    return new Lava(this.pos, this.speed.times(-1));
+  }
+};
+ +

This update method computes a new position by adding the product of the time step and the current speed to its old position. If no obstacle blocks that new position, it moves there. If there is an obstacle, the behavior depends on the type of the lava block—dripping lava has a reset position, to which it jumps back when it hits something. Bouncing lava inverts its speed by multiplying it by -1 so that it starts moving in the opposite direction.

+ +

Coins use their update method to wobble. They ignore collisions with the grid since they are simply wobbling around inside of their own square.

+ +
const wobbleSpeed = 8, wobbleDist = 0.07;
+
+Coin.prototype.update = function(time) {
+  let wobble = this.wobble + time * wobbleSpeed;
+  let wobblePos = Math.sin(wobble) * wobbleDist;
+  return new Coin(this.basePos.plus(new Vec(0, wobblePos)),
+                  this.basePos, wobble);
+};
+ +

The wobble property is incremented to track time and then used as an argument to Math.sin to find the new position on the wave. The coin’s current position is then computed from its base position and an offset based on this wave.

+ +

That leaves the player itself. Player motion is handled separately per axis because hitting the floor should not prevent horizontal motion, and hitting a wall should not stop falling or jumping motion.

+ +
const playerXSpeed = 7;
+const gravity = 30;
+const jumpSpeed = 17;
+
+Player.prototype.update = function(time, state, keys) {
+  let xSpeed = 0;
+  if (keys.ArrowLeft) xSpeed -= playerXSpeed;
+  if (keys.ArrowRight) xSpeed += playerXSpeed;
+  let pos = this.pos;
+  let movedX = pos.plus(new Vec(xSpeed * time, 0));
+  if (!state.level.touches(movedX, this.size, "wall")) {
+    pos = movedX;
+  }
+
+  let ySpeed = this.speed.y + time * gravity;
+  let movedY = pos.plus(new Vec(0, ySpeed * time));
+  if (!state.level.touches(movedY, this.size, "wall")) {
+    pos = movedY;
+  } else if (keys.ArrowUp && ySpeed > 0) {
+    ySpeed = -jumpSpeed;
+  } else {
+    ySpeed = 0;
+  }
+  return new Player(pos, new Vec(xSpeed, ySpeed));
+};
+ +

The horizontal motion is computed based on the state of the left and right arrow keys. When there’s no wall blocking the new position created by this motion, it is used. Otherwise, the old position is kept.

+ +

Vertical motion works in a similar way but has to simulate jumping and gravity. The player’s vertical speed (ySpeed) is first accelerated to account for gravity.

+ +

We check for walls again. If we don’t hit any, the new position is used. If there is a wall, there are two possible outcomes. When the up arrow is pressed and we are moving down (meaning the thing we hit is below us), the speed is set to a relatively large, negative value. This causes the player to jump. If that is not the case, the player simply bumped into something, and the speed is set to zero.

+ +

The gravity strength, jumping speed, and pretty much all other constants in this game have been set by trial and error. I tested values until I found a combination I liked.

+ +

Tracking keys

+ +

For a game like this, we do not want keys to take effect once per keypress. Rather, we want their effect (moving the player figure) to stay active as long as they are held.

+ +

We need to set up a key handler that stores the current state of the left, right, and up arrow keys. We will also want to call preventDefault for those keys so that they don’t end up scrolling the page.

+ +

The following function, when given an array of key names, will return an object that tracks the current position of those keys. It registers event handlers for "keydown" and "keyup" events and, when the key code in the event is present in the set of codes that it is tracking, updates the object.

+ +
function trackKeys(keys) {
+  let down = Object.create(null);
+  function track(event) {
+    if (keys.includes(event.key)) {
+      down[event.key] = event.type == "keydown";
+      event.preventDefault();
+    }
+  }
+  window.addEventListener("keydown", track);
+  window.addEventListener("keyup", track);
+  return down;
+}
+
+const arrowKeys =
+  trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]);
+ +

The same handler function is used for both event types. It looks at the event object’s type property to determine whether the key state should be updated to true ("keydown") or false ("keyup").

+ +

Running the game

+ +

The requestAnimationFrame function, which we saw in Chapter 14, provides a good way to animate a game. But its interface is quite primitive—using it requires us to track the time at which our function was called the last time around and call requestAnimationFrame again after every frame.

+ +

Let’s define a helper function that wraps those boring parts in a convenient interface and allows us to simply call runAnimation, giving it a function that expects a time difference as an argument and draws a single frame. When the frame function returns the value false, the animation stops.

+ +
function runAnimation(frameFunc) {
+  let lastTime = null;
+  function frame(time) {
+    if (lastTime != null) {
+      let timeStep = Math.min(time - lastTime, 100) / 1000;
+      if (frameFunc(timeStep) === false) return;
+    }
+    lastTime = time;
+    requestAnimationFrame(frame);
+  }
+  requestAnimationFrame(frame);
+}
+ +

I have set a maximum frame step of 100 milliseconds (one-tenth of a second). When the browser tab or window with our page is hidden, requestAnimationFrame calls will be suspended until the tab or window is shown again. In this case, the difference between lastTime and time will be the entire time in which the page was hidden. Advancing the game by that much in a single step would look silly and might cause weird side effects, such as the player falling through the floor.

+ +

The function also converts the time steps to seconds, which are an easier quantity to think about than milliseconds.

+ +

The runLevel function takes a Level object and a display constructor and returns a promise. It displays the level (in document.body) and lets the user play through it. When the level is finished (lost or won), runLevel waits one more second (to let the user see what happens) and then clears the display, stops the animation, and resolves the promise to the game’s end status.

+ +
function runLevel(level, Display) {
+  let display = new Display(document.body, level);
+  let state = State.start(level);
+  let ending = 1;
+  return new Promise(resolve => {
+    runAnimation(time => {
+      state = state.update(time, arrowKeys);
+      display.syncState(state);
+      if (state.status == "playing") {
+        return true;
+      } else if (ending > 0) {
+        ending -= time;
+        return true;
+      } else {
+        display.clear();
+        resolve(state.status);
+        return false;
+      }
+    });
+  });
+}
+ +

A game is a sequence of levels. Whenever the player dies, the current level is restarted. When a level is completed, we move on to the next level. This can be expressed by the following function, which takes an array of level plans (strings) and a display constructor:

+ +
async function runGame(plans, Display) {
+  for (let level = 0; level < plans.length;) {
+    let status = await runLevel(new Level(plans[level]),
+                                Display);
+    if (status == "won") level++;
+  }
+  console.log("You've won!");
+}
+ +

Because we made runLevel return a promise, runGame can be written using an async function, as shown in Chapter 11. It returns another promise, which resolves when the player finishes the game.

+ +

There is a set of level plans available in the GAME_LEVELS binding in this chapter’s sandbox. This page feeds them to runGame, starting an actual game.

+ +
<link rel="stylesheet" href="css/game.css">
+
+<body>
+  <script>
+    runGame(GAME_LEVELS, DOMDisplay);
+  </script>
+</body>
+ +

See if you can beat those. I had quite a lot of fun building them.

+ +

Exercises

+ +

Game over

+ +

It’s traditional for platform games to have the player start with a limited number of lives and subtract one life each time they die. When the player is out of lives, the game restarts from the beginning.

+ +

Adjust runGame to implement lives. Have the player start with three. Output the current number of lives (using console.log) every time a level starts.

+ +
<link rel="stylesheet" href="css/game.css">
+
+<body>
+<script>
+  // The old runGame function. Modify it...
+  async function runGame(plans, Display) {
+    for (let level = 0; level < plans.length;) {
+      let status = await runLevel(new Level(plans[level]),
+                                  Display);
+      if (status == "won") level++;
+    }
+    console.log("You've won!");
+  }
+  runGame(GAME_LEVELS, DOMDisplay);
+</script>
+</body>
+ +

Pausing the game

+ +

Make it possible to pause (suspend) and unpause the game by pressing the Esc key.

+ +

This can be done by changing the runLevel function to use another keyboard event handler and interrupting or resuming the animation whenever the Esc key is hit.

+ +

The runAnimation interface may not look like it is suitable for this at first glance, but it is if you rearrange the way runLevel calls it.

+ +

When you have that working, there is something else you could try. The way we have been registering keyboard event handlers is somewhat problematic. The arrowKeys object is currently a global binding, and its event handlers are kept around even when no game is running. You could say they leak out of our system. Extend trackKeys to provide a way to unregister its handlers and then change runLevel to register its handlers when it starts and unregister them again when it is finished.

+ +
<link rel="stylesheet" href="css/game.css">
+
+<body>
+<script>
+  // The old runLevel function. Modify this...
+  function runLevel(level, Display) {
+    let display = new Display(document.body, level);
+    let state = State.start(level);
+    let ending = 1;
+    return new Promise(resolve => {
+      runAnimation(time => {
+        state = state.update(time, arrowKeys);
+        display.syncState(state);
+        if (state.status == "playing") {
+          return true;
+        } else if (ending > 0) {
+          ending -= time;
+          return true;
+        } else {
+          display.clear();
+          resolve(state.status);
+          return false;
+        }
+      });
+    });
+  }
+  runGame(GAME_LEVELS, DOMDisplay);
+</script>
+</body>
+ +
+ +

An animation can be interrupted by returning false from the function given to runAnimation. It can be continued by calling runAnimation again.

+ +

So we need to communicate the fact that we are pausing the game to the function given to runAnimation. For that, you can use a binding that both the event handler and that function have access to.

+ +

When finding a way to unregister the handlers registered by trackKeys, remember that the exact same function value that was passed to addEventListener must be passed to removeEventListener to successfully remove a handler. Thus, the handler function value created in trackKeys must be available to the code that unregisters the handlers.

+ +

You can add a property to the object returned by trackKeys, containing either that function value or a method that handles the unregistering directly.

+ +
+ +

A monster

+ +

It is traditional for platform games to have enemies that you can jump on top of to defeat. This exercise asks you to add such an actor type to the game.

+ +

We’ll call it a monster. Monsters move only horizontally. You can make them move in the direction of the player, bounce back and forth like horizontal lava, or have any movement pattern you want. The class doesn’t have to handle falling, but it should make sure the monster doesn’t walk through walls.

+ +

When a monster touches the player, the effect depends on whether the player is jumping on top of them or not. You can approximate this by checking whether the player’s bottom is near the monster’s top. If this is the case, the monster disappears. If not, the game is lost.

+ +
<link rel="stylesheet" href="css/game.css">
+<style>.monster { background: purple }</style>
+
+<body>
+  <script>
+    // Complete the constructor, update, and collide methods
+    class Monster {
+      constructor(pos, /* ... */) {}
+
+      get type() { return "monster"; }
+
+      static create(pos) {
+        return new Monster(pos.plus(new Vec(0, -1)));
+      }
+
+      update(time, state) {}
+
+      collide(state) {}
+    }
+
+    Monster.prototype.size = new Vec(1.2, 2);
+
+    levelChars["M"] = Monster;
+
+    runLevel(new Level(`
+..................................
+.################################.
+.#..............................#.
+.#..............................#.
+.#..............................#.
+.#...........................o..#.
+.#..@...........................#.
+.##########..............########.
+..........#..o..o..o..o..#........
+..........#...........M..#........
+..........################........
+..................................
+`), DOMDisplay);
+  </script>
+</body>
+ +
+ +

If you want to implement a type of motion that is stateful, such as bouncing, make sure you store the necessary state in the actor object—include it as constructor argument and add it as a property.

+ +

Remember that update returns a new object, rather than changing the old one.

+ +

When handling collision, find the player in state.actors and compare its position to the monster’s position. To get the bottom of the player, you have to add its vertical size to its vertical position. The creation of an updated state will resemble either Coin’s collide method (removing the actor) or Lava’s (changing the status to "lost"), depending on the player position.

+ +
+
diff --git a/docs/17_canvas.html b/docs/17_canvas.html new file mode 100644 index 000000000..8e598a9c9 --- /dev/null +++ b/docs/17_canvas.html @@ -0,0 +1,746 @@ + + + + + Drawing on Canvas :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 17Drawing on Canvas

+ +
+ +

Drawing is deception.

+ +
M.C. Escher, cited by Bruno Ernst in The Magic Mirror of M.C. Escher
+ +
Picture of a robot arm drawing on paper
+ +

Browsers give us several ways to display graphics. The simplest way is to use styles to position and color regular DOM elements. This can get you quite far, as the game in the previous chapter showed. By adding partially transparent background images to the nodes, we can make them look exactly the way we want. It is even possible to rotate or skew nodes with the transform style.

+ +

But we’d be using the DOM for something that it wasn’t originally designed for. Some tasks, such as drawing a line between arbitrary points, are extremely awkward to do with regular HTML elements.

+ +

There are two alternatives. The first is DOM-based but utilizes Scalable Vector Graphics (SVG), rather than HTML. Think of SVG as a document-markup dialect that focuses on shapes rather than text. You can embed an SVG document directly in an HTML document or include it with an <img> tag.

+ +

The second alternative is called a canvas. A canvas is a single DOM element that encapsulates a picture. It provides a programming interface for drawing shapes onto the space taken up by the node. The main difference between a canvas and an SVG picture is that in SVG the original description of the shapes is preserved so that they can be moved or resized at any time. A canvas, on the other hand, converts the shapes to pixels (colored dots on a raster) as soon as they are drawn and does not remember what these pixels represent. The only way to move a shape on a canvas is to clear the canvas (or the part of the canvas around the shape) and redraw it with the shape in a new position.

+ +

SVG

+ +

This book will not go into SVG in detail, but I will briefly explain how it works. At the end of the chapter, I’ll come back to the trade-offs that you must consider when deciding which drawing mechanism is appropriate for a given application.

+ +

This is an HTML document with a simple SVG picture in it:

+ +
<p>Normal HTML here.</p>
+<svg xmlns="http://www.w3.org/2000/svg">
+  <circle r="50" cx="50" cy="50" fill="red"/>
+  <rect x="120" y="5" width="90" height="90"
+        stroke="blue" fill="none"/>
+</svg>
+ +

The xmlns attribute changes an element (and its children) to a different XML namespace. This namespace, identified by a URL, specifies the dialect that we are currently speaking. The <circle> and <rect> tags, which do not exist in HTML, do have a meaning in SVG—they draw shapes using the style and position specified by their attributes.

+ +

These tags create DOM elements, just like HTML tags, that scripts can interact with. For example, this changes the <circle> element to be colored cyan instead:

+ +
let circle = document.querySelector("circle");
+circle.setAttribute("fill", "cyan");
+ +

The canvas element

+ +

Canvas graphics can be drawn onto a <canvas> element. You can give such an element width and height attributes to determine its size in pixels.

+ +

A new canvas is empty, meaning it is entirely transparent and thus shows up as empty space in the document.

+ +

The <canvas> tag is intended to allow different styles of drawing. To get access to an actual drawing interface, we first need to create a context, an object whose methods provide the drawing interface. There are currently two widely supported drawing styles: "2d" for two-dimensional graphics and "webgl" for three-dimensional graphics through the OpenGL interface.

+ +

This book won’t discuss WebGL—we’ll stick to two dimensions. But if you are interested in three-dimensional graphics, I do encourage you to look into WebGL. It provides a direct interface to graphics hardware and allows you to render even complicated scenes efficiently, using JavaScript.

+ +

You create a context with the getContext method on the <canvas> DOM element.

+ +
<p>Before canvas.</p>
+<canvas width="120" height="60"></canvas>
+<p>After canvas.</p>
+<script>
+  let canvas = document.querySelector("canvas");
+  let context = canvas.getContext("2d");
+  context.fillStyle = "red";
+  context.fillRect(10, 10, 100, 50);
+</script>
+ +

After creating the context object, the example draws a red rectangle 100 pixels wide and 50 pixels high, with its top-left corner at coordinates (10,10).

+ +

Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-axis goes down from there. So (10,10) is 10 pixels below and to the right of the top-left corner.

+ +

Lines and surfaces

+ +

In the canvas interface, a shape can be filled, meaning its area is given a certain color or pattern, or it can be stroked, which means a line is drawn along its edge. The same terminology is used by SVG.

+ +

The fillRect method fills a rectangle. It takes first the x- and y-coordinates of the rectangle’s top-left corner, then its width, and then its height. A similar method, strokeRect, draws the outline of a rectangle.

+ +

Neither method takes any further parameters. The color of the fill, thickness of the stroke, and so on, are not determined by an argument to the method (as you might reasonably expect) but rather by properties of the context object.

+ +

The fillStyle property controls the way shapes are filled. It can be set to a string that specifies a color, using the color notation used by CSS.

+ +

The strokeStyle property works similarly but determines the color used for a stroked line. The width of that line is determined by the lineWidth property, which may contain any positive number.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.strokeStyle = "blue";
+  cx.strokeRect(5, 5, 50, 50);
+  cx.lineWidth = 5;
+  cx.strokeRect(135, 5, 50, 50);
+</script>
+ +

When no width or height attribute is specified, as in the example, a canvas element gets a default width of 300 pixels and height of 150 pixels.

+ +

Paths

+ +

A path is a sequence of lines. The 2D canvas interface takes a peculiar approach to describing such a path. It is done entirely through side effects. Paths are not values that can be stored and passed around. Instead, if you want to do something with a path, you make a sequence of method calls to describe its shape.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.beginPath();
+  for (let y = 10; y < 100; y += 10) {
+    cx.moveTo(10, y);
+    cx.lineTo(90, y);
+  }
+  cx.stroke();
+</script>
+ +

This example creates a path with a number of horizontal line segments and then strokes it using the stroke method. Each segment created with lineTo starts at the path’s current position. That position is usually the end of the last segment, unless moveTo was called. In that case, the next segment would start at the position passed to moveTo.

+ +

When filling a path (using the fill method), each shape is filled separately. A path can contain multiple shapes—each moveTo motion starts a new one. But the path needs to be closed (meaning its start and end are in the same position) before it can be filled. If the path is not already closed, a line is added from its end to its start, and the shape enclosed by the completed path is filled.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.beginPath();
+  cx.moveTo(50, 10);
+  cx.lineTo(10, 70);
+  cx.lineTo(90, 70);
+  cx.fill();
+</script>
+ +

This example draws a filled triangle. Note that only two of the triangle’s sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn’t be there when you stroke the path.

+ +

You could also use the closePath method to explicitly close a path by adding an actual line segment back to the path’s start. This segment is drawn when stroking the path.

+ +

Curves

+ +

A path may also contain curved lines. These are unfortunately a bit more involved to draw.

+ +

The quadraticCurveTo method draws a curve to a given point. To determine the curvature of the line, the method is given a control +point as well as a destination point. Imagine this control point as attracting the line, giving it its curve. The line won’t go through the control point, but its direction at the start and end points will be such that a straight line in that direction would point toward the control point. The following example illustrates this:

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.beginPath();
+  cx.moveTo(10, 90);
+  // control=(60,10) goal=(90,90)
+  cx.quadraticCurveTo(60, 10, 90, 90);
+  cx.lineTo(60, 10);
+  cx.closePath();
+  cx.stroke();
+</script>
+ +

We draw a quadratic curve from the left to the right, with (60,10) as control point, and then draw two line segments going through that control point and back to the start of the line. The result somewhat resembles a Star Trek insignia. You can see the effect of the control point: the lines leaving the lower corners start off in the direction of the control point and then curve toward their target.

+ +

The bezierCurveTo method draws a similar kind of curve. Instead of a single control point, this one has two—one for each of the line’s endpoints. Here is a similar sketch to illustrate the behavior of such a curve:

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.beginPath();
+  cx.moveTo(10, 90);
+  // control1=(10,10) control2=(90,10) goal=(50,90)
+  cx.bezierCurveTo(10, 10, 90, 10, 50, 90);
+  cx.lineTo(90, 10);
+  cx.lineTo(10, 10);
+  cx.closePath();
+  cx.stroke();
+</script>
+ +

The two control points specify the direction at both ends of the curve. The farther they are away from their corresponding point, the more the curve will “bulge” in that direction.

+ +

Such curves can be hard to work with—it’s not always clear how to find the control points that provide the shape you are looking for. Sometimes you can compute them, and sometimes you’ll just have to find a suitable value by trial and error.

+ +

The arc method is a way to draw a line that curves along the edge of a circle. It takes a pair of coordinates for the arc’s center, a radius, and then a start angle and end angle.

+ +

Those last two parameters make it possible to draw only part of the circle. The angles are measured in radians, not degrees. This means a full circle has an angle of 2π, or 2 * Math.PI, which is about 6.28. The angle starts counting at the point to the right of the circle’s center and goes clockwise from there. You can use a start of 0 and an end bigger than 2π (say, 7) to draw a full circle.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.beginPath();
+  // center=(50,50) radius=40 angle=0 to 7
+  cx.arc(50, 50, 40, 0, 7);
+  // center=(150,50) radius=40 angle=0 to ½π
+  cx.arc(150, 50, 40, 0, 0.5 * Math.PI);
+  cx.stroke();
+</script>
+ +

The resulting picture contains a line from the right of the full circle (first call to arc) to the right of the quarter-circle (second call). Like other path-drawing methods, a line drawn with arc is connected to the previous path segment. You can call moveTo or start a new path to avoid this.

+ +

Drawing a pie chart

+ +

Imagine you’ve just taken a job at EconomiCorp, Inc., and your first assignment is to draw a pie chart of its customer satisfaction survey results.

+ +

The results binding contains an array of objects that represent the survey responses.

+ +
const results = [
+  {name: "Satisfied", count: 1043, color: "lightblue"},
+  {name: "Neutral", count: 563, color: "lightgreen"},
+  {name: "Unsatisfied", count: 510, color: "pink"},
+  {name: "No comment", count: 175, color: "silver"}
+];
+ +

To draw a pie chart, we draw a number of pie slices, each made up of an arc and a pair of lines to the center of that arc. We can compute the angle taken up by each arc by dividing a full circle (2π) by the total number of responses and then multiplying that number (the angle per response) by the number of people who picked a given choice.

+ +
<canvas width="200" height="200"></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  let total = results
+    .reduce((sum, {count}) => sum + count, 0);
+  // Start at the top
+  let currentAngle = -0.5 * Math.PI;
+  for (let result of results) {
+    let sliceAngle = (result.count / total) * 2 * Math.PI;
+    cx.beginPath();
+    // center=100,100, radius=100
+    // from current angle, clockwise by slice's angle
+    cx.arc(100, 100, 100,
+           currentAngle, currentAngle + sliceAngle);
+    currentAngle += sliceAngle;
+    cx.lineTo(100, 100);
+    cx.fillStyle = result.color;
+    cx.fill();
+  }
+</script>
+ +

But a chart that doesn’t tell us what the slices mean isn’t very helpful. We need a way to draw text to the canvas.

+ +

Text

+ +

A 2D canvas drawing context provides the methods fillText and strokeText. The latter can be useful for outlining letters, but usually fillText is what you need. It will fill the outline of the given text with the current fillStyle.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.font = "28px Georgia";
+  cx.fillStyle = "fuchsia";
+  cx.fillText("I can draw text, too!", 10, 50);
+</script>
+ +

You can specify the size, style, and font of the text with the font property. This example just gives a font size and family name. It is also possible to add italic or bold to the start of the string to select a style.

+ +

The last two arguments to fillText and strokeText provide the position at which the font is drawn. By default, they indicate the position of the start of the text’s alphabetic baseline, which is the line that letters “stand” on, not counting hanging parts in letters such as j or p. You can change the horizontal position by setting the textAlign property to "end" or "center" and the vertical position by setting textBaseline to "top", "middle", or "bottom".

+ +

We’ll come back to our pie chart, and the problem of labeling the slices, in the exercises at the end of the chapter.

+ +

Images

+ +

In computer graphics, a distinction is often made between vector graphics and bitmap graphics. The first is what we have been doing so far in this chapter—specifying a picture by giving a logical description of shapes. Bitmap graphics, on the other hand, don’t specify actual shapes but rather work with pixel data (rasters of colored dots).

+ +

The drawImage method allows us to draw pixel data onto a canvas. This pixel data can originate from an <img> element or from another canvas. The following example creates a detached <img> element and loads an image file into it. But it cannot immediately start drawing from this picture because the browser may not have loaded it yet. To deal with this, we register a "load" event handler and do the drawing after the image has loaded.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  let img = document.createElement("img");
+  img.src = "img/hat.png";
+  img.addEventListener("load", () => {
+    for (let x = 10; x < 200; x += 30) {
+      cx.drawImage(img, x, 10);
+    }
+  });
+</script>
+ +

By default, drawImage will draw the image at its original size. You can also give it two additional arguments to set a different width and height.

+ +

When drawImage is given nine arguments, it can be used to draw only a fragment of an image. The second through fifth arguments indicate the rectangle (x, y, width, and height) in the source image that should be copied, and the sixth to ninth arguments give the rectangle (on the canvas) into which it should be copied.

+ +

This can be used to pack multiple sprites (image elements) into a single image file and then draw only the part you need. For example, we have this picture containing a game character in multiple poses:

Various poses of a game character
+ +

By alternating which pose we draw, we can show an animation that looks like a walking character.

+ +

To animate a picture on a canvas, the clearRect method is useful. It resembles fillRect, but instead of coloring the rectangle, it makes it transparent, removing the previously drawn pixels.

+ +

We know that each sprite, each subpicture, is 24 pixels wide and 30 pixels high. The following code loads the image and then sets up an interval (repeated timer) to draw the next frame:

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  let img = document.createElement("img");
+  img.src = "img/player.png";
+  let spriteW = 24, spriteH = 30;
+  img.addEventListener("load", () => {
+    let cycle = 0;
+    setInterval(() => {
+      cx.clearRect(0, 0, spriteW, spriteH);
+      cx.drawImage(img,
+                   // source rectangle
+                   cycle * spriteW, 0, spriteW, spriteH,
+                   // destination rectangle
+                   0,               0, spriteW, spriteH);
+      cycle = (cycle + 1) % 8;
+    }, 120);
+  });
+</script>
+ +

The cycle binding tracks our position in the animation. For each frame, it is incremented and then clipped back to the 0 to 7 range by using the remainder operator. This binding is then used to compute the x-coordinate that the sprite for the current pose has in the picture.

+ +

Transformation

+ +

But what if we want our character to walk to the left instead of to the right? We could draw another set of sprites, of course. But we can also instruct the canvas to draw the picture the other way round.

+ +

Calling the scale method will cause anything drawn after it to be scaled. This method takes two parameters, one to set a horizontal scale and one to set a vertical scale.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.scale(3, .5);
+  cx.beginPath();
+  cx.arc(50, 50, 40, 0, 7);
+  cx.lineWidth = 3;
+  cx.stroke();
+</script>
+ +

Scaling will cause everything about the drawn image, including the line width, to be stretched out or squeezed together as specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0,0), which means it will also flip the direction of the coordinate system. When a horizontal scaling of -1 is applied, a shape drawn at x position 100 will end up at what used to be position -100.

+ +

So to turn a picture around, we can’t simply add cx.scale(-1, 1) before the call to drawImage because that would move our picture outside of the canvas, where it won’t be visible. You could adjust the coordinates given to drawImage to compensate for this by drawing the image at x position -50 instead of 0. Another solution, which doesn’t require the code that does the drawing to know about the scale change, is to adjust the axis around which the scaling happens.

+ +

There are several other methods besides scale that influence the coordinate system for a canvas. You can rotate subsequently drawn shapes with the rotate method and move them with the translate method. The interesting—and confusing—thing is that these transformations stack, meaning that each one happens relative to the previous transformations.

+ +

So if we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50,50) and then rotate by 20 degrees (about 0.1π radians), that rotation will happen around point (50,50).

Stacking transformations
+ +

But if we first rotate by 20 degrees and then translate by (50,50), the translation will happen in the rotated coordinate system and thus produce a different orientation. The order in which transformations are applied matters.

+ +

To flip a picture around the vertical line at a given x position, we can do the following:

+ +
function flipHorizontally(context, around) {
+  context.translate(around, 0);
+  context.scale(-1, 1);
+  context.translate(-around, 0);
+}
+ +

We move the y-axis to where we want our mirror to be, apply the mirroring, and finally move the y-axis back to its proper place in the mirrored universe. The following picture explains why this works:

Mirroring around a vertical line
+ +

This shows the coordinate systems before and after mirroring across the central line. The triangles are numbered to illustrate each step. If we draw a triangle at a positive x position, it would, by default, be in the place where triangle 1 is. A call to flipHorizontally first does a translation to the right, which gets us to triangle 2. It then scales, flipping the triangle over to position 3. This is not where it should be, if it were mirrored in the given line. The second translate call fixes this—it “cancels” the initial translation and makes triangle 4 appear exactly where it should.

+ +

We can now draw a mirrored character at position (100,0) by flipping the world around the character’s vertical center.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  let img = document.createElement("img");
+  img.src = "img/player.png";
+  let spriteW = 24, spriteH = 30;
+  img.addEventListener("load", () => {
+    flipHorizontally(cx, 100 + spriteW / 2);
+    cx.drawImage(img, 0, 0, spriteW, spriteH,
+                 100, 0, spriteW, spriteH);
+  });
+</script>
+ +

Storing and clearing transformations

+ +

Transformations stick around. Everything else we draw after drawing that mirrored character would also be mirrored. That might be inconvenient.

+ +

It is possible to save the current transformation, do some drawing and transforming, and then restore the old transformation. This is usually the proper thing to do for a function that needs to temporarily transform the coordinate system. First, we save whatever transformation the code that called the function was using. Then the function does its thing, adding more transformations on top of the current transformation. Finally, we revert to the transformation we started with.

+ +

The save and restore methods on the 2D canvas context do this transformation management. They conceptually keep a stack of transformation states. When you call save, the current state is pushed onto the stack, and when you call restore, the state on top of the stack is taken off and used as the context’s current transformation. You can also call resetTransform to fully reset the transformation.

+ +

The branch function in the following example illustrates what you can do with a function that changes the transformation and then calls a function (in this case itself), which continues drawing with the given transformation.

+ +

This function draws a treelike shape by drawing a line, moving the center of the coordinate system to the end of the line, and calling itself twice—first rotated to the left and then rotated to the right. Every call reduces the length of the branch drawn, and the recursion stops when the length drops below 8.

+ +
<canvas width="600" height="300"></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  function branch(length, angle, scale) {
+    cx.fillRect(0, 0, 1, length);
+    if (length < 8) return;
+    cx.save();
+    cx.translate(0, length);
+    cx.rotate(-angle);
+    branch(length * scale, angle, scale);
+    cx.rotate(2 * angle);
+    branch(length * scale, angle, scale);
+    cx.restore();
+  }
+  cx.translate(300, 0);
+  branch(60, 0.5, 0.8);
+</script>
+ +

If the calls to save and restore were not there, the second recursive call to branch would end up with the position and rotation created by the first call. It wouldn’t be connected to the current branch but rather to the innermost, rightmost branch drawn by the first call. The resulting shape might also be interesting, but it is definitely not a tree.

+ +

Back to the game

+ +

We now know enough about canvas drawing to start working on a canvas-based display system for the game from the previous chapter. The new display will no longer be showing just colored boxes. Instead, we’ll use drawImage to draw pictures that represent the game’s elements.

+ +

We define another display object type called CanvasDisplay, supporting the same interface as DOMDisplay from Chapter 16, namely, the methods syncState and clear.

+ +

This object keeps a little more information than DOMDisplay. Rather than using the scroll position of its DOM element, it tracks its own viewport, which tells us what part of the level we are currently looking at. Finally, it keeps a flipPlayer property so that even when the player is standing still, it keeps facing the direction it last moved in.

+ +
class CanvasDisplay {
+  constructor(parent, level) {
+    this.canvas = document.createElement("canvas");
+    this.canvas.width = Math.min(600, level.width * scale);
+    this.canvas.height = Math.min(450, level.height * scale);
+    parent.appendChild(this.canvas);
+    this.cx = this.canvas.getContext("2d");
+
+    this.flipPlayer = false;
+
+    this.viewport = {
+      left: 0,
+      top: 0,
+      width: this.canvas.width / scale,
+      height: this.canvas.height / scale
+    };
+  }
+
+  clear() {
+    this.canvas.remove();
+  }
+}
+ +

The syncState method first computes a new viewport and then draws the game scene at the appropriate position.

+ +
CanvasDisplay.prototype.syncState = function(state) {
+  this.updateViewport(state);
+  this.clearDisplay(state.status);
+  this.drawBackground(state.level);
+  this.drawActors(state.actors);
+};
+ +

Contrary to DOMDisplay, this display style does have to redraw the background on every update. Because shapes on a canvas are just pixels, after we draw them there is no good way to move them (or remove them). The only way to update the canvas display is to clear it and redraw the scene. We may also have scrolled, which requires the background to be in a different position.

+ +

The updateViewport method is similar to DOMDisplay’s scrollPlayerIntoView method. It checks whether the player is too close to the edge of the screen and moves the viewport when this is the case.

+ +
CanvasDisplay.prototype.updateViewport = function(state) {
+  let view = this.viewport, margin = view.width / 3;
+  let player = state.player;
+  let center = player.pos.plus(player.size.times(0.5));
+
+  if (center.x < view.left + margin) {
+    view.left = Math.max(center.x - margin, 0);
+  } else if (center.x > view.left + view.width - margin) {
+    view.left = Math.min(center.x + margin - view.width,
+                         state.level.width - view.width);
+  }
+  if (center.y < view.top + margin) {
+    view.top = Math.max(center.y - margin, 0);
+  } else if (center.y > view.top + view.height - margin) {
+    view.top = Math.min(center.y + margin - view.height,
+                        state.level.height - view.height);
+  }
+};
+ +

The calls to Math.max and Math.min ensure that the viewport does not end up showing space outside of the level. Math.max(x, 0) makes sure the resulting number is not less than zero. Math.min similarly guarantees that a value stays below a given bound.

+ +

When clearing the display, we’ll use a slightly different color depending on whether the game is won (brighter) or lost (darker).

+ +
CanvasDisplay.prototype.clearDisplay = function(status) {
+  if (status == "won") {
+    this.cx.fillStyle = "rgb(68, 191, 255)";
+  } else if (status == "lost") {
+    this.cx.fillStyle = "rgb(44, 136, 214)";
+  } else {
+    this.cx.fillStyle = "rgb(52, 166, 251)";
+  }
+  this.cx.fillRect(0, 0,
+                   this.canvas.width, this.canvas.height);
+};
+ +

To draw the background, we run through the tiles that are visible in the current viewport, using the same trick used in the touches method from the previous chapter.

+ +
let otherSprites = document.createElement("img");
+otherSprites.src = "img/sprites.png";
+
+CanvasDisplay.prototype.drawBackground = function(level) {
+  let {left, top, width, height} = this.viewport;
+  let xStart = Math.floor(left);
+  let xEnd = Math.ceil(left + width);
+  let yStart = Math.floor(top);
+  let yEnd = Math.ceil(top + height);
+
+  for (let y = yStart; y < yEnd; y++) {
+    for (let x = xStart; x < xEnd; x++) {
+      let tile = level.rows[y][x];
+      if (tile == "empty") continue;
+      let screenX = (x - left) * scale;
+      let screenY = (y - top) * scale;
+      let tileX = tile == "lava" ? scale : 0;
+      this.cx.drawImage(otherSprites,
+                        tileX,         0, scale, scale,
+                        screenX, screenY, scale, scale);
+    }
+  }
+};
+ +

Tiles that are not empty are drawn with drawImage. The otherSprites image contains the pictures used for elements other than the player. It contains, from left to right, the wall tile, the lava tile, and the sprite for a coin.

Sprites for our game
+ +

Background tiles are 20 by 20 pixels since we will use the same scale that we used in DOMDisplay. Thus, the offset for lava tiles is 20 (the value of the scale binding), and the offset for walls is 0.

+ +

We don’t bother waiting for the sprite image to load. Calling drawImage with an image that hasn’t been loaded yet will simply do nothing. Thus, we might fail to draw the game properly for the first few frames, while the image is still loading, but that is not a serious problem. Since we keep updating the screen, the correct scene will appear as soon as the loading finishes.

+ +

The walking character shown earlier will be used to represent the player. The code that draws it needs to pick the right sprite and direction based on the player’s current motion. The first eight sprites contain a walking animation. When the player is moving along a floor, we cycle through them based on the current time. We want to switch frames every 60 milliseconds, so the time is divided by 60 first. When the player is standing still, we draw the ninth sprite. During jumps, which are recognized by the fact that the vertical speed is not zero, we use the tenth, rightmost sprite.

+ +

Because the sprites are slightly wider than the player object—24 instead of 16 pixels to allow some space for feet and arms—the method has to adjust the x-coordinate and width by a given amount (playerXOverlap).

+ +
let playerSprites = document.createElement("img");
+playerSprites.src = "img/player.png";
+const playerXOverlap = 4;
+
+CanvasDisplay.prototype.drawPlayer = function(player, x, y,
+                                              width, height){
+  width += playerXOverlap * 2;
+  x -= playerXOverlap;
+  if (player.speed.x != 0) {
+    this.flipPlayer = player.speed.x < 0;
+  }
+
+  let tile = 8;
+  if (player.speed.y != 0) {
+    tile = 9;
+  } else if (player.speed.x != 0) {
+    tile = Math.floor(Date.now() / 60) % 8;
+  }
+
+  this.cx.save();
+  if (this.flipPlayer) {
+    flipHorizontally(this.cx, x + width / 2);
+  }
+  let tileX = tile * width;
+  this.cx.drawImage(playerSprites, tileX, 0, width, height,
+                                   x,     y, width, height);
+  this.cx.restore();
+};
+ +

The drawPlayer method is called by drawActors, which is responsible for drawing all the actors in the game.

+ +
CanvasDisplay.prototype.drawActors = function(actors) {
+  for (let actor of actors) {
+    let width = actor.size.x * scale;
+    let height = actor.size.y * scale;
+    let x = (actor.pos.x - this.viewport.left) * scale;
+    let y = (actor.pos.y - this.viewport.top) * scale;
+    if (actor.type == "player") {
+      this.drawPlayer(actor, x, y, width, height);
+    } else {
+      let tileX = (actor.type == "coin" ? 2 : 1) * scale;
+      this.cx.drawImage(otherSprites,
+                        tileX, 0, width, height,
+                        x,     y, width, height);
+    }
+  }
+};
+ +

When drawing something that is not the player, we look at its type to find the offset of the correct sprite. The lava tile is found at offset 20, and the coin sprite is found at 40 (two times scale).

+ +

We have to subtract the viewport’s position when computing the actor’s position since (0,0) on our canvas corresponds to the top left of the viewport, not the top left of the level. We could also have used translate for this. Either way works.

+ +

This document plugs the new display into runGame:

+ +
<body>
+  <script>
+    runGame(GAME_LEVELS, CanvasDisplay);
+  </script>
+</body>
+ +

Choosing a graphics interface

+ +

So when you need to generate graphics in the browser, you can choose between plain HTML, SVG, and canvas. There is no single best approach that works in all situations. Each option has strengths and weaknesses.

+ +

Plain HTML has the advantage of being simple. It also integrates well with text. Both SVG and canvas allow you to draw text, but they won’t help you position that text or wrap it when it takes up more than one line. In an HTML-based picture, it is much easier to include blocks of text.

+ +

SVG can be used to produce crisp graphics that look good at any zoom level. Unlike HTML, it is designed for drawing and is thus more suitable for that purpose.

+ +

Both SVG and HTML build up a data structure (the DOM) that represents your picture. This makes it possible to modify elements after they are drawn. If you need to repeatedly change a small part of a big picture in response to what the user is doing or as part of an animation, doing it in a canvas can be needlessly expensive. The DOM also allows us to register mouse event handlers on every element in the picture (even on shapes drawn with SVG). You can’t do that with canvas.

+ +

But canvas’s pixel-oriented approach can be an advantage when drawing a huge number of tiny elements. The fact that it does not build up a data structure but only repeatedly draws onto the same pixel surface gives canvas a lower cost per shape.

+ +

There are also effects, such as rendering a scene one pixel at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that can be realistically handled only by a pixel-based approach.

+ +

In some cases, you may want to combine several of these techniques. For example, you might draw a graph with SVG or canvas but show textual information by positioning an HTML element on top of the picture.

+ +

For nondemanding applications, it really doesn’t matter much which interface you choose. The display we built for our game in this chapter could have been implemented using any of these three graphics technologies since it does not need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements.

+ +

Summary

+ +

In this chapter we discussed techniques for drawing graphics in the browser, focusing on the <canvas> element.

+ +

A canvas node represents an area in a document that our program may draw on. This drawing is done through a drawing context object, created with the getContext method.

+ +

The 2D drawing interface allows us to fill and stroke various shapes. The context’s fillStyle property determines how shapes are filled. The strokeStyle and lineWidth properties control the way lines are drawn.

+ +

Rectangles and pieces of text can be drawn with a single method call. The fillRect and strokeRect methods draw rectangles, and the fillText and strokeText methods draw text. To create custom shapes, we must first build up a path.

+ +

Calling beginPath starts a new path. A number of other methods add lines and curves to the current path. For example, lineTo can add a straight line. When a path is finished, it can be filled with the fill method or stroked with the stroke method.

+ +

Moving pixels from an image or another canvas onto our canvas is done with the drawImage method. By default, this method draws the whole source image, but by giving it more parameters, you can copy a specific area of the image. We used this for our game by copying individual poses of the game character out of an image that contained many such poses.

+ +

Transformations allow you to draw a shape in multiple orientations. A 2D drawing context has a current transformation that can be changed with the translate, scale, and rotate methods. These will affect all subsequent drawing operations. A transformation state can be saved with the save method and restored with the restore method.

+ +

When showing an animation on a canvas, the clearRect method can be used to clear part of the canvas before redrawing it.

+ +

Exercises

+ +

Shapes

+ +

Write a program that draws the following shapes on a canvas:

+ +
    + +
  1. + +

    A trapezoid (a rectangle that is wider on one side)

  2. + +
  3. + +

    A red diamond (a rectangle rotated 45 degrees or ¼π radians)

  4. + +
  5. + +

    A zigzagging line

  6. + +
  7. + +

    A spiral made up of 100 straight line segments

  8. + +
  9. + +

    A yellow star

  10. + +
The shapes to draw
+ +

When drawing the last two, you may want to refer to the explanation of Math.cos and Math.sin in Chapter 14, which describes how to get coordinates on a circle using these functions.

+ +

I recommend creating a function for each shape. Pass the position, and optionally other properties such as the size or the number of points, as parameters. The alternative, which is to hard-code numbers all over your code, tends to make the code needlessly hard to read and modify.

+ +
<canvas width="600" height="200"></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+
+  // Your code here.
+</script>
+ +
+ +

The trapezoid (1) is easiest to draw using a path. Pick suitable center coordinates and add each of the four corners around the center.

+ +

The diamond (2) can be drawn the straightforward way, with a path, or the interesting way, with a rotate transformation. To use rotation, you will have to apply a trick similar to what we did in the flipHorizontally function. Because you want to rotate around the center of your rectangle and not around the point (0,0), you must first translate to there, then rotate, and then translate back.

+ +

Make sure you reset the transformation after drawing any shape that creates one.

+ +

For the zigzag (3) it becomes impractical to write a new call to lineTo for each line segment. Instead, you should use a loop. You can have each iteration draw either two line segments (right and then left again) or one, in which case you must use the evenness (% 2) of the loop index to determine whether to go left or right.

+ +

You’ll also need a loop for the spiral (4). If you draw a series of points, with each point moving further along a circle around the spiral’s center, you get a circle. If, during the loop, you vary the radius of the circle on which you are putting the current point and go around more than once, the result is a spiral.

+ +

The star (5) depicted is built out of quadraticCurveTo lines. You could also draw one with straight lines. Divide a circle into eight pieces for a star with eight points, or however many pieces you want. Draw lines between these points, making them curve toward the center of the star. With quadraticCurveTo, you can use the center as the control point.

+ +
+ +

The pie chart

+ +

Earlier in the chapter, we saw an example program that drew a pie chart. Modify this program so that the name of each category is shown next to the slice that represents it. Try to find a pleasing-looking way to automatically position this text that would work for other data sets as well. You may assume that categories are big enough to leave ample room for their labels.

+ +

You might need Math.sin and Math.cos again, which are described in Chapter 14.

+ +
<canvas width="600" height="300"></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  let total = results
+    .reduce((sum, {count}) => sum + count, 0);
+  let currentAngle = -0.5 * Math.PI;
+  let centerX = 300, centerY = 150;
+
+  // Add code to draw the slice labels in this loop.
+  for (let result of results) {
+    let sliceAngle = (result.count / total) * 2 * Math.PI;
+    cx.beginPath();
+    cx.arc(centerX, centerY, 100,
+           currentAngle, currentAngle + sliceAngle);
+    currentAngle += sliceAngle;
+    cx.lineTo(centerX, centerY);
+    cx.fillStyle = result.color;
+    cx.fill();
+  }
+</script>
+ +
+ +

You will need to call fillText and set the context’s textAlign and textBaseline properties in such a way that the text ends up where you want it.

+ +

A sensible way to position the labels would be to put the text on the line going from the center of the pie through the middle of the slice. You don’t want to put the text directly against the side of the pie but rather move the text out to the side of the pie by a given number of pixels.

+ +

The angle of this line is currentAngle + 0.5 * sliceAngle. The following code finds a position on this line 120 pixels from the center:

+ +
let middleAngle = currentAngle + 0.5 * sliceAngle;
+let textX = Math.cos(middleAngle) * 120 + centerX;
+let textY = Math.sin(middleAngle) * 120 + centerY;
+ +

For textBaseline, the value "middle" is probably appropriate when using this approach. What to use for textAlign depends on which side of the circle we are on. On the left, it should be "right", and on the right, it should be "left", so that the text is positioned away from the pie.

+ +

If you are not sure how to find out which side of the circle a given angle is on, look to the explanation of Math.cos in Chapter 14. The cosine of an angle tells us which x-coordinate it corresponds to, which in turn tells us exactly which side of the circle we are on.

+ +
+ +

A bouncing ball

+ +

Use the requestAnimationFrame technique that we saw in Chapter 14 and Chapter 16 to draw a box with a bouncing ball in it. The ball moves at a constant speed and bounces off the box’s sides when it hits them.

+ +
<canvas width="400" height="400"></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+
+  let lastTime = null;
+  function frame(time) {
+    if (lastTime != null) {
+      updateAnimation(Math.min(100, time - lastTime) / 1000);
+    }
+    lastTime = time;
+    requestAnimationFrame(frame);
+  }
+  requestAnimationFrame(frame);
+
+  function updateAnimation(step) {
+    // Your code here.
+  }
+</script>
+ +
+ +

A box is easy to draw with strokeRect. Define a binding that holds its size or define two bindings if your box’s width and height differ. To create a round ball, start a path and call arc(x, y, radius, 0, 7), which creates an arc going from zero to more than a whole circle. Then fill the path.

+ +

To model the ball’s position and speed, you can use the Vec class from Chapter 16 (which is available on this page). Give it a starting speed, preferably one that is not purely vertical or horizontal, and for every frame multiply that speed by the amount of time that elapsed. When the ball gets too close to a vertical wall, invert the x component in its speed. Likewise, invert the y component when it hits a horizontal wall.

+ +

After finding the ball’s new position and speed, use clearRect to delete the scene and redraw it using the new position.

+ +
+ +

Precomputed mirroring

+ +

One unfortunate thing about transformations is that they slow down the drawing of bitmaps. The position and size of each pixel has to be transformed, and though it is possible that browsers will get cleverer about transformation in the future, they currently cause a measurable increase in the time it takes to draw a bitmap.

+ +

In a game like ours, where we are drawing only a single transformed sprite, this is a nonissue. But imagine that we need to draw hundreds of characters or thousands of rotating particles from an explosion.

+ +

Think of a way to allow us to draw an inverted character without loading additional image files and without having to make transformed drawImage calls every frame.

+ +
+ +

The key to the solution is the fact that we can use a canvas element as a source image when using drawImage. It is possible to create an extra <canvas> element, without adding it to the document, and draw our inverted sprites to it, once. When drawing an actual frame, we just copy the already inverted sprites to the main canvas.

+ +

Some care would be required because images do not load instantly. We do the inverted drawing only once, and if we do it before the image loads, it won’t draw anything. A "load" handler on the image can be used to draw the inverted images to the extra canvas. This canvas can be used as a drawing source immediately (it’ll simply be blank until we draw the character onto it).

+ +
+
diff --git a/docs/18_http.html b/docs/18_http.html new file mode 100644 index 000000000..2ae7edf4e --- /dev/null +++ b/docs/18_http.html @@ -0,0 +1,695 @@ + + + + + HTTP and Forms :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 18HTTP and Forms

+ +
+ +

Communication must be stateless in nature [...] such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server.

+ +
Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures
+ +
Picture of a web form on a medieval scroll
+ +

The Hypertext Transfer Protocol, already mentioned in Chapter 13, is the mechanism through which data is requested and provided on the World Wide Web. This chapter describes the protocol in more detail and explains the way browser JavaScript has access to it.

+ +

The protocol

+ +

If you type eloquentjavascript.net/18_http.html into your browser’s address bar, the browser first looks up the address of the server associated with eloquentjavascript.net and tries to open a TCP connection to it on port 80, the default port for HTTP traffic. If the server exists and accepts the connection, the browser might send something like this:

+ +
GET /18_http.html HTTP/1.1
+Host: eloquentjavascript.net
+User-Agent: Your browser's name
+ +

Then the server responds, through that same connection.

+ +
HTTP/1.1 200 OK
+Content-Length: 65585
+Content-Type: text/html
+Last-Modified: Mon, 08 Jan 2018 10:29:45 GMT
+
+<!doctype html>
+... the rest of the document
+ +

The browser takes the part of the response after the blank line, its body (not to be confused with the HTML <body> tag), and displays it as an HTML document.

+ +

The information sent by the client is called the request. It starts with this line:

+ +
GET /18_http.html HTTP/1.1
+ +

The first word is the method of the request. GET means that we want to get the specified resource. Other common methods are DELETE to delete a resource, PUT to create or replace it, and POST to send information to it. Note that the server is not obliged to carry out every request it gets. If you walk up to a random website and tell it to DELETE its main page, it’ll probably refuse.

+ +

The part after the method name is the path of the resource the request applies to. In the simplest case, a resource is simply a file on the server, but the protocol doesn’t require it to be. A resource may be anything that can be transferred as if it is a file. Many servers generate the responses they produce on the fly. For example, if you open https://github.com/marijnh, the server looks in its database for a user named “marijnh”, and if it finds one, it will generate a profile page for that user.

+ +

After the resource path, the first line of the request mentions HTTP/1.1 to indicate the version of the HTTP protocol it is using.

+ +

In practice, many sites use HTTP version 2, which supports the same concepts as version 1.1 but is a lot more complicated so that it can be faster. Browsers will automatically switch to the appropriate protocol version when talking to a given server, and the outcome of a request is the same regardless of which version is used. Because version 1.1 is more straightforward and easier to play around with, we’ll focus on that.

+ +

The server’s response will start with a version as well, followed by the status of the response, first as a three-digit status code and then as a human-readable string.

+ +
HTTP/1.1 200 OK
+ +

Status codes starting with a 2 indicate that the request succeeded. Codes starting with 4 mean there was something wrong with the request. 404 is probably the most famous HTTP status code—it means that the resource could not be found. Codes that start with 5 mean an error happened on the server and the request is not to blame.

+ +

The first line of a request or response may be followed by any number of headers. These are lines in the form name: value that specify extra information about the request or response. These headers were part of the example response:

+ +
Content-Length: 65585
+Content-Type: text/html
+Last-Modified: Thu, 04 Jan 2018 14:05:30 GMT
+ +

This tells us the size and type of the response document. In this case, it is an HTML document of 65,585 bytes. It also tells us when that document was last modified.

+ +

For most headers, the client and server are free to decide whether to include them in a request or response. But a few are required. For example, the Host header, which specifies the hostname, should be included in a request because a server might be serving multiple hostnames on a single IP address, and without that header, the server won’t know which hostname the client is trying to talk to.

+ +

After the headers, both requests and responses may include a blank line followed by a body, which contains the data being sent. GET and DELETE requests don’t send along any data, but PUT and POST requests do. Similarly, some response types, such as error responses, do not require a body.

+ +

Browsers and HTTP

+ +

As we saw in the example, a browser will make a request when we enter a URL in its address bar. When the resulting HTML page references other files, such as images and JavaScript files, those are also retrieved.

+ +

A moderately complicated website can easily include anywhere from 10 to 200 resources. To be able to fetch those quickly, browsers will make several GET requests simultaneously, rather than waiting for the responses one at a time.

+ +

HTML pages may include forms, which allow the user to fill out information and send it to the server. This is an example of a form:

+ +
<form method="GET" action="example/message.html">
+  <p>Name: <input type="text" name="name"></p>
+  <p>Message:<br><textarea name="message"></textarea></p>
+  <p><button type="submit">Send</button></p>
+</form>
+ +

This code describes a form with two fields: a small one asking for a name and a larger one to write a message in. When you click the Send button, the form is submitted, meaning that the content of its field is packed into an HTTP request and the browser navigates to the result of that request.

+ +

When the <form> element’s method attribute is GET (or is omitted), the information in the form is added to the end of the action URL as a query string. The browser might make a request to this URL:

+ +
GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1
+ +

The question mark indicates the end of the path part of the URL and the start of the query. It is followed by pairs of names and values, corresponding to the name attribute on the form field elements and the content of those elements, respectively. An ampersand character (&) is used to separate the pairs.

+ +

The actual message encoded in the URL is “Yes?”, but the question mark is replaced by a strange code. Some characters in query strings must be escaped. The question mark, represented as %3F, is one of those. There seems to be an unwritten rule that every format needs its own way of escaping characters. This one, called URL encoding, uses a percent sign followed by two hexadecimal (base 16) digits that encode the character code. In this case, 3F, which is 63 in decimal notation, is the code of a question mark character. JavaScript provides the encodeURIComponent and decodeURIComponent functions to encode and decode this format.

+ +
console.log(encodeURIComponent("Yes?"));
+// → Yes%3F
+console.log(decodeURIComponent("Yes%3F"));
+// → Yes?
+ +

If we change the method attribute of the HTML form in the example we saw earlier to POST, the HTTP request made to submit the form will use the POST method and put the query string in the body of the request, rather than adding it to the URL.

+ +
POST /example/message.html HTTP/1.1
+Content-length: 24
+Content-type: application/x-www-form-urlencoded
+
+name=Jean&message=Yes%3F
+ +

GET requests should be used for requests that do not have side +effects but simply ask for information. Requests that change something on the server, for example creating a new account or posting a message, should be expressed with other methods, such as POST. Client-side software such as a browser knows that it shouldn’t blindly make POST requests but will often implicitly make GET requests—for example to prefetch a resource it believes the user will soon need.

+ +

We’ll come back to forms and how to interact with them from JavaScript later in the chapter.

+ +

Fetch

+ +

The interface through which browser JavaScript can make HTTP requests is called fetch. Since it is relatively new, it conveniently uses promises (which is rare for browser interfaces).

+ +
fetch("example/data.txt").then(response => {
+  console.log(response.status);
+  // → 200
+  console.log(response.headers.get("Content-Type"));
+  // → text/plain
+});
+ +

Calling fetch returns a promise that resolves to a Response object holding information about the server’s response, such as its status code and its headers. The headers are wrapped in a Map-like object that treats its keys (the header names) as case insensitive because header names are not supposed to be case sensitive. This means headers.get("Content-Type") and headers.get("content-TYPE") will return the same value.

+ +

Note that the promise returned by fetch resolves successfully even if the server responded with an error code. It might also be rejected if there is a network error or if the server that the request is addressed to can’t be found.

+ +

The first argument to fetch is the URL that should be requested. When that URL doesn’t start with a protocol name (such as http:), it is treated as relative, which means it is interpreted relative to the current document. When it starts with a slash (/), it replaces the current path, which is the part after the server name. When it does not, the part of the current path up to and including its last slash character is put in front of the relative URL.

+ +

To get at the actual content of a response, you can use its text method. Because the initial promise is resolved as soon as the response’s headers have been received and because reading the response body might take a while longer, this again returns a promise.

+ +
fetch("example/data.txt")
+  .then(resp => resp.text())
+  .then(text => console.log(text));
+// → This is the content of data.txt
+ +

A similar method, called json, returns a promise that resolves to the value you get when parsing the body as JSON or rejects if it’s not valid JSON.

+ +

By default, fetch uses the GET method to make its request and does not include a request body. You can configure it differently by passing an object with extra options as a second argument. For example, this request tries to delete example/data.txt:

+ +
fetch("example/data.txt", {method: "DELETE"}).then(resp => {
+  console.log(resp.status);
+  // → 405
+});
+ +

The 405 status code means “method not allowed”, an HTTP server’s way of saying “I can’t do that”.

+ +

To add a request body, you can include a body option. To set headers, there’s the headers option. For example, this request includes a Range header, which instructs the server to return only part of a response.

+ +
fetch("example/data.txt", {headers: {Range: "bytes=8-19"}})
+  .then(resp => resp.text())
+  .then(console.log);
+// → the content
+ +

The browser will automatically add some request headers, such as “Host” and those needed for the server to figure out the size of the body. But adding your own headers is often useful to include things such as authentication information or to tell the server which file format you’d like to receive.

+ +

HTTP sandboxing

+ +

Making HTTP requests in web page scripts once again raises concerns about security. The person who controls the script might not have the same interests as the person on whose computer it is running. More specifically, if I visit themafia.org, I do not want its scripts to be able to make a request to mybank.com, using identifying information from my browser, with instructions to transfer all my money to some random account.

+ +

For this reason, browsers protect us by disallowing scripts to make HTTP requests to other domains (names such as themafia.org and mybank.com).

+ +

This can be an annoying problem when building systems that want to access several domains for legitimate reasons. Fortunately, servers can include a header like this in their response to explicitly indicate to the browser that it is okay for the request to come from another domain:

+ +
Access-Control-Allow-Origin: *
+ +

Appreciating HTTP

+ +

When building a system that requires communication between a JavaScript program running in the browser (client-side) and a program on a server (server-side), there are several different ways to model this communication.

+ +

A commonly used model is that of remote procedure calls. In this model, communication follows the patterns of normal function calls, except that the function is actually running on another machine. Calling it involves making a request to the server that includes the function’s name and arguments. The response to that request contains the returned value.

+ +

When thinking in terms of remote procedure calls, HTTP is just a vehicle for communication, and you will most likely write an abstraction layer that hides it entirely.

+ +

Another approach is to build your communication around the concept of resources and HTTP methods. Instead of a remote procedure called addUser, you use a PUT request to /users/larry. Instead of encoding that user’s properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the PUT request to create a new resource is then such a document. A resource is fetched by making a GET request to the resource’s URL (for example, /user/larry), which again returns the document representing the resource.

+ +

This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy on the client for fast access). The concepts used in HTTP, which are well designed, can provide a helpful set of principles to design your server interface around.

+ +

Security and HTTPS

+ +

Data traveling over the Internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from coffee shop Wi-Fi hotspots to networks controlled by various companies and states. At any point along its route it may be inspected or even modified.

+ +

If it is important that something remain secret, such as the password to your email account, or that it arrive at its destination unmodified, such as the account number you transfer money to via your bank’s website, plain HTTP is not good enough.

+ +

The secure HTTP protocol, used for URLs starting with https://, wraps HTTP traffic in a way that makes it harder to read and tamper with. Before exchanging data, the client verifies that the server is who it claims to be by asking it to prove that it has a cryptographic certificate issued by a certificate authority that the browser recognizes. Next, all data going over the connection is encrypted in a way that should prevent eavesdropping and tampering.

+ +

Thus, when it works right, HTTPS prevents other people from impersonating the website you are trying to talk to and from snooping on your communication. It is not perfect, and there have been various incidents where HTTPS failed because of forged or stolen certificates and broken software, but it is a lot safer than plain HTTP.

+ +

Form fields

+ +

Forms were originally designed for the pre-JavaScript Web to allow web sites to send user-submitted information in an HTTP request. This design assumes that interaction with the server always happens by navigating to a new page.

+ +

But their elements are part of the DOM like the rest of the page, and the DOM elements that represent form fields support a number of properties and events that are not present on other elements. These make it possible to inspect and control such input fields with JavaScript programs and do things such as adding new functionality to a form or using forms and fields as building blocks in a JavaScript application.

+ +

A web form consists of any number of input fields grouped in a <form> tag. HTML allows several different styles of fields, ranging from simple on/off checkboxes to drop-down menus and fields for text input. This book won’t try to comprehensively discuss all field types, but we’ll start with a rough overview.

+ +

A lot of field types use the <input> tag. This tag’s type attribute is used to select the field’s style. These are some commonly used <input> types:

+ + + + + + + + + + + + + + + + + + + + + + + +
textA single-line text field
passwordSame as text but hides the text that is typed
checkboxAn on/off switch
radio(Part of) a multiple-choice field
fileAllows the user to choose a file from their computer
+ +

Form fields do not necessarily have to appear in a <form> tag. You can put them anywhere in a page. Such form-less fields cannot be submitted (only a form as a whole can), but when responding to input with JavaScript, we often don’t want to submit our fields normally anyway.

+ +
<p><input type="text" value="abc"> (text)</p>
+<p><input type="password" value="abc"> (password)</p>
+<p><input type="checkbox" checked> (checkbox)</p>
+<p><input type="radio" value="A" name="choice">
+   <input type="radio" value="B" name="choice" checked>
+   <input type="radio" value="C" name="choice"> (radio)</p>
+<p><input type="file"> (file)</p>
+ +

The JavaScript interface for such elements differs with the type of the element.

+ +

Multiline text fields have their own tag, <textarea>, mostly because using an attribute to specify a multiline starting value would be awkward. The <textarea> tag requires a matching </textarea> closing tag and uses the text between those two, instead of the value attribute, as starting text.

+ +
<textarea>
+one
+two
+three
+</textarea>
+ +

Finally, the <select> tag is used to create a field that allows the user to select from a number of predefined options.

+ +
<select>
+  <option>Pancakes</option>
+  <option>Pudding</option>
+  <option>Ice cream</option>
+</select>
+ +

Whenever the value of a form field changes, it will fire a "change" event.

+ +

Focus

+ +

Unlike most elements in HTML documents, form fields can get keyboard focus. When clicked or activated in some other way, they become the currently active element and the recipient of keyboard input.

+ +

Thus, you can type into a text field only when it is focused. Other fields respond differently to keyboard events. For example, a <select> menu tries to move to the option that contains the text the user typed and responds to the arrow keys by moving its selection up and down.

+ +

We can control focus from JavaScript with the focus and blur methods. The first moves focus to the DOM element it is called on, and the second removes focus. The value in document.activeElement corresponds to the currently focused element.

+ +
<input type="text">
+<script>
+  document.querySelector("input").focus();
+  console.log(document.activeElement.tagName);
+  // → INPUT
+  document.querySelector("input").blur();
+  console.log(document.activeElement.tagName);
+  // → BODY
+</script>
+ +

For some pages, the user is expected to want to interact with a form field immediately. JavaScript can be used to focus this field when the document is loaded, but HTML also provides the autofocus attribute, which produces the same effect while letting the browser know what we are trying to achieve. This gives the browser the option to disable the behavior when it is not appropriate, such as when the user has put the focus on something else.

+ +

Browsers traditionally also allow the user to move the focus through the document by pressing the tab key. We can influence the order in which elements receive focus with the tabindex attribute. The following example document will let the focus jump from the text input to the OK button, rather than going through the help link first:

+ +
<input type="text" tabindex=1> <a href=".">(help)</a>
+<button onclick="console.log('ok')" tabindex=2>OK</button>
+ +

By default, most types of HTML elements cannot be focused. But you can add a tabindex attribute to any element that will make it focusable. A tabindex of -1 makes tabbing skip over an element, even if it is normally focusable.

+ +

Disabled fields

+ +

All form fields can be disabled through their disabled attribute. It is an attribute that can be specified without value—the fact that it is present at all disables the element.

+ +
<button>I'm all right</button>
+<button disabled>I'm out</button>
+ +

Disabled fields cannot be focused or changed, and browsers make them look gray and faded.

+ +

When a program is in the process of handling an action caused by some button or other control that might require communication with the server and thus take a while, it can be a good idea to disable the control until the action finishes. That way, when the user gets impatient and clicks it again, they don’t accidentally repeat their action.

+ +

The form as a whole

+ +

When a field is contained in a <form> element, its DOM element will have a form property linking back to the form’s DOM element. The <form> element, in turn, has a property called elements that contains an array-like collection of the fields inside it.

+ +

The name attribute of a form field determines the way its value will be identified when the form is submitted. It can also be used as a property name when accessing the form’s elements property, which acts both as an array-like object (accessible by number) and a map (accessible by name).

+ +
<form action="example/submit.html">
+  Name: <input type="text" name="name"><br>
+  Password: <input type="password" name="password"><br>
+  <button type="submit">Log in</button>
+</form>
+<script>
+  let form = document.querySelector("form");
+  console.log(form.elements[1].type);
+  // → password
+  console.log(form.elements.password.type);
+  // → password
+  console.log(form.elements.name.form == form);
+  // → true
+</script>
+ +

A button with a type attribute of submit will, when pressed, cause the form to be submitted. Pressing enter when a form field is focused has the same effect.

+ +

Submitting a form normally means that the browser navigates to the page indicated by the form’s action attribute, using either a GET or a POST request. But before that happens, a "submit" event is fired. You can handle this event with JavaScript and prevent this default behavior by calling preventDefault on the event object.

+ +
<form action="example/submit.html">
+  Value: <input type="text" name="value">
+  <button type="submit">Save</button>
+</form>
+<script>
+  let form = document.querySelector("form");
+  form.addEventListener("submit", event => {
+    console.log("Saving value", form.elements.value.value);
+    event.preventDefault();
+  });
+</script>
+ +

Intercepting "submit" events in JavaScript has various uses. We can write code to verify that the values the user entered make sense and immediately show an error message instead of submitting the form. Or we can disable the regular way of submitting the form entirely, as in the example, and have our program handle the input, possibly using fetch to send it to a server without reloading the page.

+ +

Text fields

+ +

Fields created by <textarea> tags, or <input> tags with a type of text or password, share a common interface. Their DOM elements have a value property that holds their current content as a string value. Setting this property to another string changes the field’s content.

+ +

The selectionStart and selectionEnd properties of text fields give us information about the cursor and selection in the text. When nothing is selected, these two properties hold the same number, indicating the position of the cursor. For example, 0 indicates the start of the text, and 10 indicates the cursor is after the 10th character. When part of the field is selected, the two properties will differ, giving us the start and end of the selected text. Like value, these properties may also be written to.

+ +

Imagine you are writing an article about Khasekhemwy but have some trouble spelling his name. The following code wires up a <textarea> tag with an event handler that, when you press F2, inserts the string “Khasekhemwy” for you.

+ +
<textarea></textarea>
+<script>
+  let textarea = document.querySelector("textarea");
+  textarea.addEventListener("keydown", event => {
+    // The key code for F2 happens to be 113
+    if (event.keyCode == 113) {
+      replaceSelection(textarea, "Khasekhemwy");
+      event.preventDefault();
+    }
+  });
+  function replaceSelection(field, word) {
+    let from = field.selectionStart, to = field.selectionEnd;
+    field.value = field.value.slice(0, from) + word +
+                  field.value.slice(to);
+    // Put the cursor after the word
+    field.selectionStart = from + word.length;
+    field.selectionEnd = from + word.length;
+  }
+</script>
+ +

The replaceSelection function replaces the currently selected part of a text field’s content with the given word and then moves the cursor after that word so that the user can continue typing.

+ +

The "change" event for a text +field does not fire every time something is typed. Rather, it fires when the field loses focus after its content was changed. To respond immediately to changes in a text field, you should register a handler for the "input" event instead, which fires for every time the user types a character, deletes text, or otherwise manipulates the field’s content.

+ +

The following example shows a text field and a counter displaying the current length of the text in the field:

+ +
<input type="text"> length: <span id="length">0</span>
+<script>
+  let text = document.querySelector("input");
+  let output = document.querySelector("#length");
+  text.addEventListener("input", () => {
+    output.textContent = text.value.length;
+  });
+</script>
+ +

Checkboxes and radio buttons

+ +

A checkbox field is a binary toggle. Its value can be extracted or changed through its checked property, which holds a Boolean value.

+ +
<label>
+  <input type="checkbox" id="purple"> Make this page purple
+</label>
+<script>
+  let checkbox = document.querySelector("#purple");
+  checkbox.addEventListener("change", () => {
+    document.body.style.background =
+      checkbox.checked ? "mediumpurple" : "";
+  });
+</script>
+ +

The <label> tag associates a piece of document with an input field. Clicking anywhere on the label will activate the field, which focuses it and toggles its value when it is a checkbox or radio button.

+ +

A radio button is similar to a checkbox, but it’s implicitly linked to other radio buttons with the same name attribute so that only one of them can be active at any time.

+ +
Color:
+<label>
+  <input type="radio" name="color" value="orange"> Orange
+</label>
+<label>
+  <input type="radio" name="color" value="lightgreen"> Green
+</label>
+<label>
+  <input type="radio" name="color" value="lightblue"> Blue
+</label>
+<script>
+  let buttons = document.querySelectorAll("[name=color]");
+  for (let button of Array.from(buttons)) {
+    button.addEventListener("change", () => {
+      document.body.style.background = button.value;
+    });
+  }
+</script>
+ +

The square brackets in the CSS query given to querySelectorAll are used to match attributes. It selects elements whose name attribute is "color".

+ +

Select fields

+ +

Select fields are conceptually similar to radio buttons—they also allow the user to choose from a set of options. But where a radio button puts the layout of the options under our control, the appearance of a <select> tag is determined by the browser.

+ +

Select fields also have a variant that is more akin to a list of checkboxes, rather than radio boxes. When given the multiple attribute, a <select> tag will allow the user to select any number of options, rather than just a single option. This will, in most browsers, show up differently than a normal select field, which is typically drawn as a drop-down control that shows the options only when you open it.

+ +

Each <option> tag has a value. This value can be defined with a value attribute. When that is not given, the text inside the option will count as its value. The value property of a <select> element reflects the currently selected option. For a multiple field, though, this property doesn’t mean much since it will give the value of only one of the currently selected options.

+ +

The <option> tags for a <select> field can be accessed as an array-like object through the field’s options property. Each option has a property called selected, which indicates whether that option is currently selected. The property can also be written to select or deselect an option.

+ +

This example extracts the selected values from a multiple select field and uses them to compose a binary number from individual bits. Hold control (or command on a Mac) to select multiple options.

+ +
<select multiple>
+  <option value="1">0001</option>
+  <option value="2">0010</option>
+  <option value="4">0100</option>
+  <option value="8">1000</option>
+</select> = <span id="output">0</span>
+<script>
+  let select = document.querySelector("select");
+  let output = document.querySelector("#output");
+  select.addEventListener("change", () => {
+    let number = 0;
+    for (let option of Array.from(select.options)) {
+      if (option.selected) {
+        number += Number(option.value);
+      }
+    }
+    output.textContent = number;
+  });
+</script>
+ +

File fields

+ +

File fields were originally designed as a way to upload files from the user’s machine through a form. In modern browsers, they also provide a way to read such files from JavaScript programs. The field acts as a kind of gatekeeper. The script cannot simply start reading private files from the user’s computer, but if the user selects a file in such a field, the browser interprets that action to mean that the script may read the file.

+ +

A file field usually looks like a button labeled with something like “choose file” or “browse”, with information about the chosen file next to it.

+ +
<input type="file">
+<script>
+  let input = document.querySelector("input");
+  input.addEventListener("change", () => {
+    if (input.files.length > 0) {
+      let file = input.files[0];
+      console.log("You chose", file.name);
+      if (file.type) console.log("It has type", file.type);
+    }
+  });
+</script>
+ +

The files property of a file field element is an array-like object (again, not a real array) containing the files chosen in the field. It is initially empty. The reason there isn’t simply a file property is that file fields also support a multiple attribute, which makes it possible to select multiple files at the same time.

+ +

Objects in the files object have properties such as name (the filename), size (the file’s size in bytes, which are chunks of 8 bits), and type (the media type of the file, such as text/plain or image/jpeg).

+ +

What it does not have is a property that contains the content of the file. Getting at that is a little more involved. Since reading a file from disk can take time, the interface must be asynchronous to avoid freezing the document.

+ +
<input type="file" multiple>
+<script>
+  let input = document.querySelector("input");
+  input.addEventListener("change", () => {
+    for (let file of Array.from(input.files)) {
+      let reader = new FileReader();
+      reader.addEventListener("load", () => {
+        console.log("File", file.name, "starts with",
+                    reader.result.slice(0, 20));
+      });
+      reader.readAsText(file);
+    }
+  });
+</script>
+ +

Reading a file is done by creating a FileReader object, registering a "load" event handler for it, and calling its readAsText method, giving it the file we want to read. Once loading finishes, the reader’s result property contains the file’s content.

+ +

FileReaders also fire an "error" event when reading the file fails for any reason. The error object itself will end up in the reader’s error property. This interface was designed before promises became part of the language. You could wrap it in a promise like this:

+ +
function readFileText(file) {
+  return new Promise((resolve, reject) => {
+    let reader = new FileReader();
+    reader.addEventListener(
+      "load", () => resolve(reader.result));
+    reader.addEventListener(
+      "error", () => reject(reader.error));
+    reader.readAsText(file);
+  });
+}
+ +

Storing data client-side

+ +

Simple HTML pages with a bit of JavaScript can be a great format for “mini applications”—small helper programs that automate basic tasks. By connecting a few form fields with event handlers, you can do anything from converting between centimeters and inches to computing passwords from a master password and a website name.

+ +

When such an application needs to remember something between sessions, you cannot use JavaScript bindings—those are thrown away every time the page is closed. You could set up a server, connect it to the Internet, and have your application store something there. We will see how to do that in Chapter 20. But that’s a lot of extra work and complexity. Sometimes it is enough to just keep the data in the browser.

+ +

The localStorage object can be used to store data in a way that survives page reloads. This object allows you to file string values under names.

+ +
localStorage.setItem("username", "marijn");
+console.log(localStorage.getItem("username"));
+// → marijn
+localStorage.removeItem("username");
+ +

A value in localStorage sticks around until it is overwritten, it is removed with removeItem, or the user clears their local data.

+ +

Sites from different domains get different storage compartments. That means data stored in localStorage by a given website can, in principle, be read (and overwritten) only by scripts on that same site.

+ +

Browsers do enforce a limit on the size of the data a site can store in localStorage. That restriction, along with the fact that filling up people’s hard drives with junk is not really profitable, prevents the feature from eating up too much space.

+ +

The following code implements a crude note-taking application. It keeps a set of named notes and allows the user to edit notes and create new ones.

+ +
Notes: <select></select> <button>Add</button><br>
+<textarea style="width: 100%"></textarea>
+
+<script>
+  let list = document.querySelector("select");
+  let note = document.querySelector("textarea");
+
+  let state;
+  function setState(newState) {
+    list.textContent = "";
+    for (let name of Object.keys(newState.notes)) {
+      let option = document.createElement("option");
+      option.textContent = name;
+      if (newState.selected == name) option.selected = true;
+      list.appendChild(option);
+    }
+    note.value = newState.notes[newState.selected];
+
+    localStorage.setItem("Notes", JSON.stringify(newState));
+    state = newState;
+  }
+  setState(JSON.parse(localStorage.getItem("Notes")) || {
+    notes: {"shopping list": "Carrots\nRaisins"},
+    selected: "shopping list"
+  });
+
+  list.addEventListener("change", () => {
+    setState({notes: state.notes, selected: list.value});
+  });
+  note.addEventListener("change", () => {
+    setState({
+      notes: Object.assign({}, state.notes,
+                           {[state.selected]: note.value}),
+      selected: state.selected
+    });
+  });
+  document.querySelector("button")
+    .addEventListener("click", () => {
+      let name = prompt("Note name");
+      if (name) setState({
+        notes: Object.assign({}, state.notes, {[name]: ""}),
+        selected: name
+      });
+    });
+</script>
+ +

The script gets its starting state from the "Notes" value stored in localStorage or, if that is missing, creates an example state that has only a shopping list in it. Reading a field that does not exist from localStorage will yield null. Passing null to JSON.parse will make it parse the string "null" and return null. Thus, the || operator can be used to provide a default value in a situation like this.

+ +

The setState method makes sure the DOM is showing a given state and stores the new state to localStorage. Event handlers call this function to move to a new state.

+ +

The use of Object.assign in the example is intended to create a new object that is a clone of the old state.notes, but with one property added or overwritten. Object.assign takes its first argument and adds all properties from any further arguments to it. Thus, giving it an empty object will cause it to fill a fresh object. The square +brackets notation in the third argument is used to create a property whose name is based on some dynamic value.

+ +

There is another object, similar to localStorage, called sessionStorage. The difference between the two is that the content of sessionStorage is forgotten at the end of each session, which for most browsers means whenever the browser is closed.

+ +

Summary

+ +

In this chapter, we discussed how the HTTP protocol works. A client sends a request, which contains a method (usually GET) and a path that identifies a resource. The server then decides what to do with the request and responds with a status code and a response body. Both requests and responses may contain headers that provide additional information.

+ +

The interface through which browser JavaScript can make HTTP requests is called fetch. Making a request looks like this:

+ +
fetch("/18_http.html").then(r => r.text()).then(text => {
+  console.log(`The page starts with ${text.slice(0, 15)}`);
+});
+ +

Browsers make GET requests to fetch the resources needed to display a web page. A page may also contain forms, which allow information entered by the user to be sent as a request for a new page when the form is submitted.

+ +

HTML can represent various types of form fields, such as text fields, checkboxes, multiple-choice fields, and file pickers.

+ +

Such fields can be inspected and manipulated with JavaScript. They fire the "change" event when changed, fire the "input" event when text is typed, and receive keyboard events when they have keyboard focus. Properties like value (for text and select fields) or checked (for checkboxes and radio buttons) are used to read or set the field’s content.

+ +

When a form is submitted, a "submit" event is fired on it. A JavaScript handler can call preventDefault on that event to disable the browser’s default behavior. Form field elements may also occur outside of a form tag.

+ +

When the user has selected a file from their local file system in a file picker field, the FileReader interface can be used to access the content of this file from a JavaScript program.

+ +

The localStorage and sessionStorage objects can be used to save information in a way that survives page reloads. The first object saves the data forever (or until the user decides to clear it), and the second saves it until the browser is closed.

+ +

Exercises

+ +

Content negotiation

+ +

One of the things HTTP can do is called content negotiation. The Accept request header is used to tell the server what type of document the client would like to get. Many servers ignore this header, but when a server knows of various ways to encode a resource, it can look at this header and send the one that the client prefers.

+ +

The URL https://eloquentjavascript.net/author is configured to respond with either plaintext, HTML, or JSON, depending on what the client asks for. These formats are identified by the standardized media types text/plain, text/html, and application/json.

+ +

Send requests to fetch all three formats of this resource. Use the headers property in the options object passed to fetch to set the header named Accept to the desired media type.

+ +

Finally, try asking for the media type application/rainbows+unicorns and see which status code that produces.

+ +
// Your code here.
+ +
+ +

Base your code on the fetch examples earlier in the chapter.

+ +

Asking for a bogus media type will return a response with code 406, “Not acceptable”, which is the code a server should return when it can’t fulfill the Accept header.

+ +
+ +

A JavaScript workbench

+ +

Build an interface that allows people to type and run pieces of JavaScript code.

+ +

Put a button next to a <textarea> field that, when pressed, uses the Function constructor we saw in Chapter 10 to wrap the text in a function and call it. Convert the return value of the function, or any error it raises, to a string and display it below the text field.

+ +
<textarea id="code">return "hi";</textarea>
+<button id="button">Run</button>
+<pre id="output"></pre>
+
+<script>
+  // Your code here.
+</script>
+ +
+ +

Use document.querySelector or document.getElementById to get access to the elements defined in your HTML. An event handler for "click" or "mousedown" events on the button can get the value property of the text field and call Function on it.

+ +

Make sure you wrap both the call to Function and the call to its result in a try block so you can catch the exceptions it produces. In this case, we really don’t know what type of exception we are looking for, so catch everything.

+ +

The textContent property of the output element can be used to fill it with a string message. Or, if you want to keep the old content around, create a new text node using document.createTextNode and append it to the element. Remember to add a newline character to the end so that not all output appears on a single line.

+ +
+ +

Conway’s Game of Life

+ +

Conway’s Game of Life is a simple simulation that creates artificial “life” on a grid, each cell of which is either alive or not. Each generation (turn), the following rules are applied:

+ +
    + +
  • + +

    Any live cell with fewer than two or more than three live neighbors dies.

  • + +
  • + +

    Any live cell with two or three live neighbors lives on to the next generation.

  • + +
  • + +

    Any dead cell with exactly three live neighbors becomes a live cell.

+ +

A neighbor is defined as any adjacent cell, including diagonally adjacent ones.

+ +

Note that these rules are applied to the whole grid at once, not one square at a time. That means the counting of neighbors is based on the situation at the start of the generation, and changes happening to neighbor cells during this generation should not influence the new state of a given cell.

+ +

Implement this game using whichever data structure you find appropriate. Use Math.random to populate the grid with a random pattern initially. Display it as a grid of checkbox fields, with a button next to it to advance to the next generation. When the user checks or unchecks the checkboxes, their changes should be included when computing the next generation.

+ +
<div id="grid"></div>
+<button id="next">Next generation</button>
+
+<script>
+  // Your code here.
+</script>
+ +
+ +

To solve the problem of having the changes conceptually happen at the same time, try to see the computation of a generation as a pure +function, which takes one grid and produces a new grid that represents the next turn.

+ +

Representing the matrix can be done in the way shown in Chapter 6. You can count live neighbors with two nested loops, looping over adjacent coordinates in both dimensions. Take care not to count cells outside of the field and to ignore the cell in the center, whose neighbors we are counting.

+ +

Ensuring that changes to checkboxes take effect on the next generation can be done in two ways. An event handler could notice these changes and update the current grid to reflect them, or you could generate a fresh grid from the values in the checkboxes before computing the next turn.

+ +

If you choose to go with event handlers, you might want to attach attributes that identify the position that each checkbox corresponds to so that it is easy to find out which cell to change.

+ +

To draw the grid of checkboxes, you can either use a <table> element (see Chapter 14) or simply put them all in the same element and put <br> (line break) elements between the rows.

+ +
+
diff --git a/docs/19_paint.html b/docs/19_paint.html new file mode 100644 index 000000000..6cae4a0b5 --- /dev/null +++ b/docs/19_paint.html @@ -0,0 +1,767 @@ + + + + + Project: A Pixel Art Editor :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 19Project: A Pixel Art Editor

+ +
+ +

I look at the many colors before me. I look at my blank canvas. Then, I try to apply colors like words that shape poems, like notes that shape music.

+ +
Joan Miro
+ +
Picture of a tiled mosaic
+ +

The material from the previous chapters gives you all the elements you need to build a basic web application. In this chapter, we will do just that.

+ +

Our application will be a pixel drawing program, where you can modify a picture pixel by pixel by manipulating a zoomed-in view of it, shown as a grid of colored squares. You can use the program to open image files, scribble on them with your mouse or other pointer device, and save them. This is what it will look like:

The pixel editor interface, with colored pixels at the top and a number of controls below that
+ +

Painting on a computer is great. You don’t need to worry about materials, skill, or talent. You just start smearing.

+ +

Components

+ +

The interface for the application shows a big <canvas> element on top, with a number of form fields below it. The user draws on the picture by selecting a tool from a <select> field and then clicking, touching, or dragging across the canvas. There are tools for drawing single pixels or rectangles, for filling an area, and for picking a color from the picture.

+ +

We will structure the editor interface as a number of components, objects that are responsible for a piece of the DOM and that may contain other components inside them.

+ +

The state of the application consists of the current picture, the selected tool, and the selected color. We’ll set things up so that the state lives in a single value, and the interface components always base the way they look on the current state.

+ +

To see why this is important, let’s consider the alternative—distributing pieces of state throughout the interface. Up to a certain point, this is easier to program. We can just put in a color field and read its value when we need to know the current color.

+ +

But then we add the color picker—a tool that lets you click the picture to select the color of a given pixel. To keep the color field showing the correct color, that tool would have to know that it exists and update it whenever it picks a new color. If you ever add another place that makes the color visible (maybe the mouse cursor could show it), you have to update your color-changing code to keep that synchronized.

+ +

In effect, this creates a problem where each part of the interface needs to know about all other parts, which is not very modular. For small applications like the one in this chapter, that may not be a problem. For bigger projects, it can turn into a real nightmare.

+ +

To avoid this nightmare on principle, we’re going to be strict about data flow. There is a state, and the interface is drawn based on that state. An interface component may respond to user actions by updating the state, at which point the components get a chance to synchronize themselves with this new state.

+ +

In practice, each component is set up so that when it is given a new state, it also notifies its child components, insofar as those need to be updated. Setting this up is a bit of a hassle. Making this more convenient is the main selling point of many browser programming libraries. But for a small application like this, we can do it without such infrastructure.

+ +

Updates to the state are represented as objects, which we’ll call actions. Components may create such actions and dispatch them—give them to a central state management function. That function computes the next state, after which the interface components update themselves to this new state.

+ +

We’re taking the messy task of running a user interface and applying some structure to it. Though the DOM-related pieces are still full of side effects, they are held up by a conceptually simple backbone: the state update cycle. The state determines what the DOM looks like, and the only way DOM events can change the state is by dispatching actions to the state.

+ +

There are many variants of this approach, each with its own benefits and problems, but their central idea is the same: state changes should go through a single well-defined channel, not happen all over the place.

+ +

Our components will be classes conforming to an interface. Their constructor is given a state—which may be the whole application state or some smaller value if it doesn’t need access to everything—and uses that to build up a dom property. This is the DOM element that represents the component. Most constructors will also take some other values that won’t change over time, such as the function they can use to dispatch an action.

+ +

Each component has a syncState method that is used to synchronize it to a new state value. The method takes one argument, the state, which is of the same type as the first argument to its constructor.

+ +

The state

+ +

The application state will be an object with picture, tool, and color properties. The picture is itself an object that stores the width, height, and pixel content of the picture. The pixels are stored in an array, in the same way as the matrix class from Chapter 6—row by row, from top to bottom.

+ +
class Picture {
+  constructor(width, height, pixels) {
+    this.width = width;
+    this.height = height;
+    this.pixels = pixels;
+  }
+  static empty(width, height, color) {
+    let pixels = new Array(width * height).fill(color);
+    return new Picture(width, height, pixels);
+  }
+  pixel(x, y) {
+    return this.pixels[x + y * this.width];
+  }
+  draw(pixels) {
+    let copy = this.pixels.slice();
+    for (let {x, y, color} of pixels) {
+      copy[x + y * this.width] = color;
+    }
+    return new Picture(this.width, this.height, copy);
+  }
+}
+ +

We want to be able to treat a picture as an immutable value, for reasons that we’ll get back to later in the chapter. But we also sometimes need to update a whole bunch of pixels at a time. To be able to do that, the class has a draw method that expects an array of updated pixels—objects with x, y, and color properties—and creates a new picture with those pixels overwritten. This method uses slice without arguments to copy the entire pixel array—the start of the slice defaults to 0, and the end defaults to the array’s length.

+ +

The empty method uses two pieces of array functionality that we haven’t seen before. The Array constructor can be called with a number to create an empty array of the given length. The fill method can then be used to fill this array with a given value. These are used to create an array in which all pixels have the same color.

+ +

Colors are stored as strings containing traditional CSS color +codes made up of a hash sign (#) followed by six hexadecimal (base-16) digits—two for the red component, two for the green component, and two for the blue component. This is a somewhat cryptic and inconvenient way to write colors, but it is the format the HTML color input field uses, and it can be used in the fillColor property of a canvas drawing context, so for the ways we’ll use colors in this program, it is practical enough.

+ +

Black, where all components are zero, is written "#000000", and bright pink looks like "#ff00ff", where the red and blue components have the maximum value of 255, written ff in hexadecimal digits (which use a to f to represent digits 10 to 15).

+ +

We’ll allow the interface to dispatch actions as objects whose properties overwrite the properties of the previous state. The color field, when the user changes it, could dispatch an object like {color: field.value}, from which this update function can compute a new state.

+ +
function updateState(state, action) {
+  return Object.assign({}, state, action);
+}
+ +

This rather cumbersome pattern, in which Object.assign is used to first add the properties of state to an empty object and then overwrite some of those with the properties from action, is common in JavaScript code that uses immutable objects. A more convenient notation for this, in which the triple-dot operator is used to include all properties from another object in an object expression, is in the final stages of being standardized. With that addition, you could write {...state, ...action} instead. At the time of writing, this doesn’t yet work in all browsers.

+ +

DOM building

+ +

One of the main things that interface components do is creating DOM structure. We again don’t want to directly use the verbose DOM methods for that, so here’s a slightly expanded version of the elt function:

+ +
function elt(type, props, ...children) {
+  let dom = document.createElement(type);
+  if (props) Object.assign(dom, props);
+  for (let child of children) {
+    if (typeof child != "string") dom.appendChild(child);
+    else dom.appendChild(document.createTextNode(child));
+  }
+  return dom;
+}
+ +

The main difference between this version and the one we used in Chapter 16 is that it assigns properties to DOM nodes, not attributes. This means we can’t use it to set arbitrary attributes, but we can use it to set properties whose value isn’t a string, such as onclick, which can be set to a function to register a click event handler.

+ +

This allows the following style of registering event handlers:

+ +
<body>
+  <script>
+    document.body.appendChild(elt("button", {
+      onclick: () => console.log("click")
+    }, "The button"));
+  </script>
+</body>
+ +

The canvas

+ +

The first component we’ll define is the part of the interface that displays the picture as a grid of colored boxes. This component is responsible for two things: showing a picture and communicating pointer events on that picture to the rest of the application.

+ +

As such, we can define it as a component that knows about only the current picture, not the whole application state. Because it doesn’t know how the application as a whole works, it cannot directly dispatch actions. Rather, when responding to pointer events, it calls a callback function provided by the code that created it, which will handle the application-specific parts.

+ +
const scale = 10;
+
+class PictureCanvas {
+  constructor(picture, pointerDown) {
+    this.dom = elt("canvas", {
+      onmousedown: event => this.mouse(event, pointerDown),
+      ontouchstart: event => this.touch(event, pointerDown)
+    });
+    this.syncState(picture);
+  }
+  syncState(picture) {
+    if (this.picture == picture) return;
+    this.picture = picture;
+    drawPicture(this.picture, this.dom, scale);
+  }
+}
+ +

We draw each pixel as a 10-by-10 square, as determined by the scale constant. To avoid unnecessary work, the component keeps track of its current picture and does a redraw only when syncState is given a new picture.

+ +

The actual drawing function sets the size of the canvas based on the scale and picture size and fills it with a series of squares, one for each pixel.

+ +
function drawPicture(picture, canvas, scale) {
+  canvas.width = picture.width * scale;
+  canvas.height = picture.height * scale;
+  let cx = canvas.getContext("2d");
+
+  for (let y = 0; y < picture.height; y++) {
+    for (let x = 0; x < picture.width; x++) {
+      cx.fillStyle = picture.pixel(x, y);
+      cx.fillRect(x * scale, y * scale, scale, scale);
+    }
+  }
+}
+ +

When the left mouse button is pressed while the mouse is over the picture canvas, the component calls the pointerDown callback, giving it the position of the pixel that was clicked—in picture coordinates. This will be used to implement mouse interaction with the picture. The callback may return another callback function to be notified when the pointer is moved to a different pixel while the button is held down.

+ +
PictureCanvas.prototype.mouse = function(downEvent, onDown) {
+  if (downEvent.button != 0) return;
+  let pos = pointerPosition(downEvent, this.dom);
+  let onMove = onDown(pos);
+  if (!onMove) return;
+  let move = moveEvent => {
+    if (moveEvent.buttons == 0) {
+      this.dom.removeEventListener("mousemove", move);
+    } else {
+      let newPos = pointerPosition(moveEvent, this.dom);
+      if (newPos.x == pos.x && newPos.y == pos.y) return;
+      pos = newPos;
+      onMove(newPos);
+    }
+  };
+  this.dom.addEventListener("mousemove", move);
+};
+
+function pointerPosition(pos, domNode) {
+  let rect = domNode.getBoundingClientRect();
+  return {x: Math.floor((pos.clientX - rect.left) / scale),
+          y: Math.floor((pos.clientY - rect.top) / scale)};
+}
+ +

Since we know the size of the pixels and we can use getBoundingClientRect to find the position of the canvas on the screen, it is possible to go from mouse event coordinates (clientX and clientY) to picture coordinates. These are always rounded down so that they refer to a specific pixel.

+ +

With touch events, we have to do something similar, but using different events and making sure we call preventDefault on the "touchstart" event to prevent panning.

+ +
PictureCanvas.prototype.touch = function(startEvent,
+                                         onDown) {
+  let pos = pointerPosition(startEvent.touches[0], this.dom);
+  let onMove = onDown(pos);
+  startEvent.preventDefault();
+  if (!onMove) return;
+  let move = moveEvent => {
+    let newPos = pointerPosition(moveEvent.touches[0],
+                                 this.dom);
+    if (newPos.x == pos.x && newPos.y == pos.y) return;
+    pos = newPos;
+    onMove(newPos);
+  };
+  let end = () => {
+    this.dom.removeEventListener("touchmove", move);
+    this.dom.removeEventListener("touchend", end);
+  };
+  this.dom.addEventListener("touchmove", move);
+  this.dom.addEventListener("touchend", end);
+};
+ +

For touch events, clientX and clientY aren’t available directly on the event object, but we can use the coordinates of the first touch object in the touches property.

+ +

The application

+ +

To make it possible to build the application piece by piece, we’ll implement the main component as a shell around a picture canvas and a dynamic set of tools and controls that we pass to its constructor.

+ +

The controls are the interface elements that appear below the picture. They’ll be provided as an array of component constructors.

+ +

The tools do things like drawing pixels or filling in an area. The application shows the set of available tools as a <select> field. The currently selected tool determines what happens when the user interacts with the picture with a pointer device. The set of available tools is provided as an object that maps the names that appear in the drop-down field to functions that implement the tools. Such functions get a picture position, a current application state, and a dispatch function as arguments. They may return a move handler function that gets called with a new position and a current state when the pointer moves to a different pixel.

+ +
class PixelEditor {
+  constructor(state, config) {
+    let {tools, controls, dispatch} = config;
+    this.state = state;
+
+    this.canvas = new PictureCanvas(state.picture, pos => {
+      let tool = tools[this.state.tool];
+      let onMove = tool(pos, this.state, dispatch);
+      if (onMove) return pos => onMove(pos, this.state);
+    });
+    this.controls = controls.map(
+      Control => new Control(state, config));
+    this.dom = elt("div", {}, this.canvas.dom, elt("br"),
+                   ...this.controls.reduce(
+                     (a, c) => a.concat(" ", c.dom), []));
+  }
+  syncState(state) {
+    this.state = state;
+    this.canvas.syncState(state.picture);
+    for (let ctrl of this.controls) ctrl.syncState(state);
+  }
+}
+ +

The pointer handler given to PictureCanvas calls the currently selected tool with the appropriate arguments and, if that returns a move handler, adapts it to also receive the state.

+ +

All controls are constructed and stored in this.controls so that they can be updated when the application state changes. The call to reduce introduces spaces between the controls’ DOM elements. That way they don’t look so pressed together.

+ +

The first control is the tool selection menu. It creates a <select> element with an option for each tool and sets up a "change" event handler that updates the application state when the user selects a different tool.

+ +
class ToolSelect {
+  constructor(state, {tools, dispatch}) {
+    this.select = elt("select", {
+      onchange: () => dispatch({tool: this.select.value})
+    }, ...Object.keys(tools).map(name => elt("option", {
+      selected: name == state.tool
+    }, name)));
+    this.dom = elt("label", null, "🖌 Tool: ", this.select);
+  }
+  syncState(state) { this.select.value = state.tool; }
+}
+ +

By wrapping the label text and the field in a <label> element, we tell the browser that the label belongs to that field so that you can, for example, click the label to focus the field.

+ +

We also need to be able to change the color, so let’s add a control for that. An HTML <input> element with a type attribute of color gives us a form field that is specialized for selecting colors. Such a field’s value is always a CSS color code in "#RRGGBB" format (red, green, and blue components, two digits per color). The browser will show a color picker interface when the user interacts with it.

+ +

This control creates such a field and wires it up to stay synchronized with the application state’s color property.

+ +
class ColorSelect {
+  constructor(state, {dispatch}) {
+    this.input = elt("input", {
+      type: "color",
+      value: state.color,
+      onchange: () => dispatch({color: this.input.value})
+    });
+    this.dom = elt("label", null, "🎨 Color: ", this.input);
+  }
+  syncState(state) { this.input.value = state.color; }
+}
+ +

Drawing tools

+ +

Before we can draw anything, we need to implement the tools that will control the functionality of mouse or touch events on the canvas.

+ +

The most basic tool is the draw tool, which changes any pixel you click or tap to the currently selected color. It dispatches an action that updates the picture to a version in which the pointed-at pixel is given the currently selected color.

+ +
function draw(pos, state, dispatch) {
+  function drawPixel({x, y}, state) {
+    let drawn = {x, y, color: state.color};
+    dispatch({picture: state.picture.draw([drawn])});
+  }
+  drawPixel(pos, state);
+  return drawPixel;
+}
+ +

The function immediately calls the drawPixel function but then also returns it so that it is called again for newly touched pixels when the user drags or swipes over the picture.

+ +

To draw larger shapes, it can be useful to quickly create rectangles. The rectangle tool draws a rectangle between the point where you start dragging and the point that you drag to.

+ +
function rectangle(start, state, dispatch) {
+  function drawRectangle(pos) {
+    let xStart = Math.min(start.x, pos.x);
+    let yStart = Math.min(start.y, pos.y);
+    let xEnd = Math.max(start.x, pos.x);
+    let yEnd = Math.max(start.y, pos.y);
+    let drawn = [];
+    for (let y = yStart; y <= yEnd; y++) {
+      for (let x = xStart; x <= xEnd; x++) {
+        drawn.push({x, y, color: state.color});
+      }
+    }
+    dispatch({picture: state.picture.draw(drawn)});
+  }
+  drawRectangle(start);
+  return drawRectangle;
+}
+ +

An important detail in this implementation is that when dragging, the rectangle is redrawn on the picture from the original state. That way, you can make the rectangle larger and smaller again while creating it, without the intermediate rectangles sticking around in the final picture. This is one of the reasons why immutable picture objects are useful—we’ll see another reason later.

+ +

Implementing flood fill is somewhat more involved. This is a tool that fills the pixel under the pointer and all adjacent pixels that have the same color. “Adjacent” means directly horizontally or vertically adjacent, not diagonally. This picture illustrates the set of pixels colored when the flood fill tool is used at the marked pixel:

A pixel grid showing the area filled by a flood fill operation
+ +

Interestingly, the way we’ll do this looks a bit like the pathfinding code from Chapter 7. Whereas that code searched through a graph to find a route, this code searches through a grid to find all “connected” pixels. The problem of keeping track of a branching set of possible routes is similar.

+ +
const around = [{dx: -1, dy: 0}, {dx: 1, dy: 0},
+                {dx: 0, dy: -1}, {dx: 0, dy: 1}];
+
+function fill({x, y}, state, dispatch) {
+  let targetColor = state.picture.pixel(x, y);
+  let drawn = [{x, y, color: state.color}];
+  for (let done = 0; done < drawn.length; done++) {
+    for (let {dx, dy} of around) {
+      let x = drawn[done].x + dx, y = drawn[done].y + dy;
+      if (x >= 0 && x < state.picture.width &&
+          y >= 0 && y < state.picture.height &&
+          state.picture.pixel(x, y) == targetColor &&
+          !drawn.some(p => p.x == x && p.y == y)) {
+        drawn.push({x, y, color: state.color});
+      }
+    }
+  }
+  dispatch({picture: state.picture.draw(drawn)});
+}
+ +

The array of drawn pixels doubles as the function’s work list. For each pixel reached, we have to see whether any adjacent pixels have the same color and haven’t already been painted over. The loop counter lags behind the length of the drawn array as new pixels are added. Any pixels ahead of it still need to be explored. When it catches up with the length, no unexplored pixels remain, and the function is done.

+ +

The final tool is a color picker, which allows you to point at a color in the picture to use it as the current drawing color.

+ +
function pick(pos, state, dispatch) {
+  dispatch({color: state.picture.pixel(pos.x, pos.y)});
+}
+ +

We can now test our application!

+ +
<div></div>
+<script>
+  let state = {
+    tool: "draw",
+    color: "#000000",
+    picture: Picture.empty(60, 30, "#f0f0f0")
+  };
+  let app = new PixelEditor(state, {
+    tools: {draw, fill, rectangle, pick},
+    controls: [ToolSelect, ColorSelect],
+    dispatch(action) {
+      state = updateState(state, action);
+      app.syncState(state);
+    }
+  });
+  document.querySelector("div").appendChild(app.dom);
+</script>
+ +

Saving and loading

+ +

When we’ve drawn our masterpiece, we’ll want to save it for later. We should add a button for downloading the current picture as an image file. This control provides that button:

+ +
class SaveButton {
+  constructor(state) {
+    this.picture = state.picture;
+    this.dom = elt("button", {
+      onclick: () => this.save()
+    }, "💾 Save");
+  }
+  save() {
+    let canvas = elt("canvas");
+    drawPicture(this.picture, canvas, 1);
+    let link = elt("a", {
+      href: canvas.toDataURL(),
+      download: "pixelart.png"
+    });
+    document.body.appendChild(link);
+    link.click();
+    link.remove();
+  }
+  syncState(state) { this.picture = state.picture; }
+}
+ +

The component keeps track of the current picture so that it can access it when saving. To create the image file, it uses a <canvas> element that it draws the picture on (at a scale of one pixel per pixel).

+ +

The toDataURL method on a canvas element creates a URL that starts with data:. Unlike http: and https: URLs, data URLs contain the whole resource in the URL. They are usually very long, but they allow us to create working links to arbitrary pictures, right here in the browser.

+ +

To actually get the browser to download the picture, we then create a link element that points at this URL and has a download attribute. Such links, when clicked, make the browser show a file save dialog. We add that link to the document, simulate a click on it, and remove it again.

+ +

You can do a lot with browser technology, but sometimes the way to do it is rather odd.

+ +

And it gets worse. We’ll also want to be able to load existing image files into our application. To do that, we again define a button component.

+ +
class LoadButton {
+  constructor(_, {dispatch}) {
+    this.dom = elt("button", {
+      onclick: () => startLoad(dispatch)
+    }, "📁 Load");
+  }
+  syncState() {}
+}
+
+function startLoad(dispatch) {
+  let input = elt("input", {
+    type: "file",
+    onchange: () => finishLoad(input.files[0], dispatch)
+  });
+  document.body.appendChild(input);
+  input.click();
+  input.remove();
+}
+ +

To get access to a file on the user’s computer, we need the user to select the file through a file input field. But I don’t want the load button to look like a file input field, so we create the file input when the button is clicked and then pretend that this file input itself was clicked.

+ +

When the user has selected a file, we can use FileReader to get access to its contents, again as a data URL. That URL can be used to create an <img> element, but because we can’t get direct access to the pixels in such an image, we can’t create a Picture object from that.

+ +
function finishLoad(file, dispatch) {
+  if (file == null) return;
+  let reader = new FileReader();
+  reader.addEventListener("load", () => {
+    let image = elt("img", {
+      onload: () => dispatch({
+        picture: pictureFromImage(image)
+      }),
+      src: reader.result
+    });
+  });
+  reader.readAsDataURL(file);
+}
+ +

To get access to the pixels, we must first draw the picture to a <canvas> element. The canvas context has a getImageData method that allows a script to read its pixels. So, once the picture is on the canvas, we can access it and construct a Picture object.

+ +
function pictureFromImage(image) {
+  let width = Math.min(100, image.width);
+  let height = Math.min(100, image.height);
+  let canvas = elt("canvas", {width, height});
+  let cx = canvas.getContext("2d");
+  cx.drawImage(image, 0, 0);
+  let pixels = [];
+  let {data} = cx.getImageData(0, 0, width, height);
+
+  function hex(n) {
+    return n.toString(16).padStart(2, "0");
+  }
+  for (let i = 0; i < data.length; i += 4) {
+    let [r, g, b] = data.slice(i, i + 3);
+    pixels.push("#" + hex(r) + hex(g) + hex(b));
+  }
+  return new Picture(width, height, pixels);
+}
+ +

We’ll limit the size of images to 100 by 100 pixels since anything bigger will look huge on our display and might slow down the interface.

+ +

The data property of the object returned by getImageData is an array of color components. For each pixel in the rectangle specified by the arguments, it contains four values, which represent the red, green, blue, and alpha components of the pixel’s color, as numbers between 0 and 255. The alpha part represents opacity—when it is zero, the pixel is fully transparent, and when it is 255, it is fully opaque. For our purpose, we can ignore it.

+ +

The two hexadecimal digits per component, as used in our color notation, correspond precisely to the 0 to 255 range—two base-16 digits can express 162 = 256 different numbers. The toString method of numbers can be given a base as argument, so n.toString(16) will produce a string representation in base 16. We have to make sure that each number takes up two digits, so the hex helper function calls padStart to add a leading zero when necessary.

+ +

We can load and save now! That leaves one more feature before we’re done.

+ +

Undo history

+ +

Half of the process of editing is making little mistakes and correcting them. So an important feature in a drawing program is an undo history.

+ +

To be able to undo changes, we need to store previous versions of the picture. Since it’s an immutable value, that is easy. But it does require an additional field in the application state.

+ +

We’ll add a done array to keep previous versions of the picture. Maintaining this property requires a more complicated state update function that adds pictures to the array.

+ +

But we don’t want to store every change, only changes a certain amount of time apart. To be able to do that, we’ll need a second property, doneAt, tracking the time at which we last stored a picture in the history.

+ +
function historyUpdateState(state, action) {
+  if (action.undo == true) {
+    if (state.done.length == 0) return state;
+    return Object.assign({}, state, {
+      picture: state.done[0],
+      done: state.done.slice(1),
+      doneAt: 0
+    });
+  } else if (action.picture &&
+             state.doneAt < Date.now() - 1000) {
+    return Object.assign({}, state, action, {
+      done: [state.picture, ...state.done],
+      doneAt: Date.now()
+    });
+  } else {
+    return Object.assign({}, state, action);
+  }
+}
+ +

When the action is an undo action, the function takes the most recent picture from the history and makes that the current picture. It sets doneAt to zero so that the next change is guaranteed to store the picture back in the history, allowing you to revert to it another time if you want.

+ +

Otherwise, if the action contains a new picture and the last time we stored something is more than a second (1000 milliseconds) ago, the done and doneAt properties are updated to store the previous picture.

+ +

The undo button component doesn’t do much. It dispatches undo actions when clicked and disables itself when there is nothing to undo.

+ +
class UndoButton {
+  constructor(state, {dispatch}) {
+    this.dom = elt("button", {
+      onclick: () => dispatch({undo: true}),
+      disabled: state.done.length == 0
+    }, "⮪ Undo");
+  }
+  syncState(state) {
+    this.dom.disabled = state.done.length == 0;
+  }
+}
+ +

Let’s draw

+ +

To set up the application, we need to create a state, a set of tools, a set of controls, and a dispatch function. We can pass them to the PixelEditor constructor to create the main component. Since we’ll need to create several editors in the exercises, we first define some bindings.

+ +
const startState = {
+  tool: "draw",
+  color: "#000000",
+  picture: Picture.empty(60, 30, "#f0f0f0"),
+  done: [],
+  doneAt: 0
+};
+
+const baseTools = {draw, fill, rectangle, pick};
+
+const baseControls = [
+  ToolSelect, ColorSelect, SaveButton, LoadButton, UndoButton
+];
+
+function startPixelEditor({state = startState,
+                           tools = baseTools,
+                           controls = baseControls}) {
+  let app = new PixelEditor(state, {
+    tools,
+    controls,
+    dispatch(action) {
+      state = historyUpdateState(state, action);
+      app.syncState(state);
+    }
+  });
+  return app.dom;
+}
+ +

When destructuring an object or array, you can use = after a binding name to give the binding a default value, which is used when the property is missing or holds undefined. The startPixelEditor function makes use of this to accept an object with a number of optional properties as an argument. If you don’t provide a tools property, for example, tools will be bound to baseTools.

+ +

This is how we get an actual editor on the screen:

+ +
<div></div>
+<script>
+  document.querySelector("div")
+    .appendChild(startPixelEditor({}));
+</script>
+ +

Go ahead and draw something. I’ll wait.

+ +

Why is this so hard?

+ +

Browser technology is amazing. It provides a powerful set of interface building blocks, ways to style and manipulate them, and tools to inspect and debug your applications. The software you write for the browser can be run on almost every computer and phone on the planet.

+ +

At the same time, browser technology is ridiculous. You have to learn a large number of silly tricks and obscure facts to master it, and the default programming model it provides is so problematic that most programmers prefer to cover it in several layers of abstraction rather than deal with it directly.

+ +

And though the situation is definitely improving, it mostly does so in the form of more elements being added to address shortcomings—creating even more complexity. A feature used by a million websites can’t really be replaced. Even if it could, it would be hard to decide what it should be replaced with.

+ +

Technology never exists in a vacuum—we’re constrained by our tools and the social, economic, and historical factors that produced them. This can be annoying, but it is generally more productive to try to build a good understanding of how the existing technical reality works—and why it is the way it is—than to rage against it or hold out for another reality.

+ +

New abstractions can be helpful. The component model and data +flow convention I used in this chapter is a crude form of that. As mentioned, there are libraries that try to make user interface programming more pleasant. At the time of writing, React and Angular are popular choices, but there’s a whole cottage industry of such frameworks. If you’re interested in programming web applications, I recommend investigating a few of them to understand how they work and what benefits they provide.

+ +

Exercises

+ +

There is still room for improvement in our program. Let’s add a few more features as exercises.

+ +

Keyboard bindings

+ +

Add keyboard shortcuts to the application. The first letter of a tool’s name selects the tool, and control-Z or command-Z activates undo.

+ +

Do this by modifying the PixelEditor component. Add a tabIndex property of 0 to the wrapping <div> element so that it can receive keyboard focus. Note that the property corresponding to the tabindex attribute is called tabIndex, with a capital I, and our elt function expects property names. Register the key event handlers directly on that element. This means you have to click, touch, or tab to the application before you can interact with it with the keyboard.

+ +

Remember that keyboard events have ctrlKey and metaKey (for the command key on Mac) properties that you can use to see whether those keys are held down.

+ +
<div></div>
+<script>
+  // The original PixelEditor class. Extend the constructor.
+  class PixelEditor {
+    constructor(state, config) {
+      let {tools, controls, dispatch} = config;
+      this.state = state;
+
+      this.canvas = new PictureCanvas(state.picture, pos => {
+        let tool = tools[this.state.tool];
+        let onMove = tool(pos, this.state, dispatch);
+        if (onMove) {
+          return pos => onMove(pos, this.state, dispatch);
+        }
+      });
+      this.controls = controls.map(
+        Control => new Control(state, config));
+      this.dom = elt("div", {}, this.canvas.dom, elt("br"),
+                     ...this.controls.reduce(
+                       (a, c) => a.concat(" ", c.dom), []));
+    }
+    syncState(state) {
+      this.state = state;
+      this.canvas.syncState(state.picture);
+      for (let ctrl of this.controls) ctrl.syncState(state);
+    }
+  }
+
+  document.querySelector("div")
+    .appendChild(startPixelEditor({}));
+</script>
+ +
+ +

The key property of events for letter keys will be the lowercase letter itself, if shift isn’t being held. We’re not interested in key events with shift here.

+ +

A "keydown" handler can inspect its event object to see whether it matches any of the shortcuts. You can automatically get the list of first letters from the tools object so that you don’t have to write them out.

+ +

When the key event matches a shortcut, call preventDefault on it and dispatch the appropriate action.

+ +
+ +

Efficient drawing

+ +

During drawing, the majority of work that our application does happens in drawPicture. Creating a new state and updating the rest of the DOM isn’t very expensive, but repainting all the pixels on the canvas is quite a bit of work.

+ +

Find a way to make the syncState method of PictureCanvas faster by redrawing only the pixels that actually changed.

+ +

Remember that drawPicture is also used by the save button, so if you change it, either make sure the changes don’t break the old use or create a new version with a different name.

+ +

Also note that changing the size of a <canvas> element, by setting its width or height properties, clears it, making it entirely transparent again.

+ +
<div></div>
+<script>
+  // Change this method
+  PictureCanvas.prototype.syncState = function(picture) {
+    if (this.picture == picture) return;
+    this.picture = picture;
+    drawPicture(this.picture, this.dom, scale);
+  };
+
+  // You may want to use or change this as well
+  function drawPicture(picture, canvas, scale) {
+    canvas.width = picture.width * scale;
+    canvas.height = picture.height * scale;
+    let cx = canvas.getContext("2d");
+
+    for (let y = 0; y < picture.height; y++) {
+      for (let x = 0; x < picture.width; x++) {
+        cx.fillStyle = picture.pixel(x, y);
+        cx.fillRect(x * scale, y * scale, scale, scale);
+      }
+    }
+  }
+
+  document.querySelector("div")
+    .appendChild(startPixelEditor({}));
+</script>
+ +
+ +

This exercise is a good example of how immutable data structures can make code faster. Because we have both the old and the new picture, we can compare them and redraw only the pixels that changed color, saving more than 99 percent of the drawing work in most cases.

+ +

You can either write a new function updatePicture or have drawPicture take an extra argument, which may be undefined or the previous picture. For each pixel, the function checks whether a previous picture was passed with the same color at this position and skips the pixel when that is the case.

+ +

Because the canvas gets cleared when we change its size, you should also avoid touching its width and height properties when the old picture and the new picture have the same size. If they are different, which will happen when a new picture has been loaded, you can set the binding holding the old picture to null after changing the canvas size because you shouldn’t skip any pixels after you’ve changed the canvas size.

+ +
+ +

Circles

+ +

Define a tool called circle that draws a filled circle when you drag. The center of the circle lies at the point where the drag or touch gesture starts, and its radius is determined by the distance dragged.

+ +
<div></div>
+<script>
+  function circle(pos, state, dispatch) {
+    // Your code here
+  }
+
+  let dom = startPixelEditor({
+    tools: Object.assign({}, baseTools, {circle})
+  });
+  document.querySelector("div").appendChild(dom);
+</script>
+ +
+ +

You can take some inspiration from the rectangle tool. Like that tool, you’ll want to keep drawing on the starting picture, rather than the current picture, when the pointer moves.

+ +

To figure out which pixels to color, you can use the Pythagorean +theorem. First figure out the distance between the current pointer position and the start position by taking the square root (Math.sqrt) of the sum of the square (Math.pow(x, 2)) of the difference in x-coordinates and the square of the difference in y-coordinates. Then loop over a square of pixels around the start position, whose sides are at least twice the radius, and color those that are within the circle’s radius, again using the Pythagorean formula to figure out their distance from the center.

+ +

Make sure you don’t try to color pixels that are outside of the picture’s boundaries.

+ +
+ +

Proper lines

+ +

This is a more advanced exercise than the preceding two, and it will require you to design a solution to a nontrivial problem. Make sure you have plenty of time and patience before starting to work on this exercise, and do not get discouraged by initial failures.

+ +

On most browsers, when you select the draw tool and quickly drag across the picture, you don’t get a closed line. Rather, you get dots with gaps between them because the "mousemove" or "touchmove" events did not fire quickly enough to hit every pixel.

+ +

Improve the draw tool to make it draw a full line. This means you have to make the motion handler function remember the previous position and connect that to the current one.

+ +

To do this, since the pixels can be an arbitrary distance apart, you’ll have to write a general line drawing function.

+ +

A line between two pixels is a connected chain of pixels, as straight as possible, going from the start to the end. Diagonally adjacent pixels count as a connected. So a slanted line should look like the picture on the left, not the picture on the right.

Two pixelated lines, one light, skipping across pixels diagonally, and one heavy, with all pixels connected horizontally or vertically
+ +

Finally, if we have code that draws a line between two arbitrary points, we might as well use it to also define a line tool, which draws a straight line between the start and end of a drag.

+ +
<div></div>
+<script>
+  // The old draw tool. Rewrite this.
+  function draw(pos, state, dispatch) {
+    function drawPixel({x, y}, state) {
+      let drawn = {x, y, color: state.color};
+      dispatch({picture: state.picture.draw([drawn])});
+    }
+    drawPixel(pos, state);
+    return drawPixel;
+  }
+
+  function line(pos, state, dispatch) {
+    // Your code here
+  }
+
+  let dom = startPixelEditor({
+    tools: {draw, line, fill, rectangle, pick}
+  });
+  document.querySelector("div").appendChild(dom);
+</script>
+ +
+ +

The thing about the problem of drawing a pixelated line is that it is really four similar but slightly different problems. Drawing a horizontal line from the left to the right is easy—you loop over the x-coordinates and color a pixel at every step. If the line has a slight slope (less than 45 degrees or ¼π radians), you can interpolate the y-coordinate along the slope. You still need one pixel per x position, with the y position of those pixels determined by the slope.

+ +

But as soon as your slope goes across 45 degrees, you need to switch the way you treat the coordinates. You now need one pixel per y position since the line goes up more than it goes left. And then, when you cross 135 degrees, you have to go back to looping over the x-coordinates, but from right to left.

+ +

You don’t actually have to write four loops. Since drawing a line from A to B is the same as drawing a line from B to A, you can swap the start and end positions for lines going from right to left and treat them as going left to right.

+ +

So you need two different loops. The first thing your line drawing function should do is check whether the difference between the x-coordinates is larger than the difference between the y-coordinates. If it is, this is a horizontal-ish line, and if not, a vertical-ish one.

+ +

Make sure you compare the absolute values of the x and y difference, which you can get with Math.abs.

+ +

Once you know along which axis you will be looping, you can check whether the start point has a higher coordinate along that axis than the endpoint and swap them if necessary. A succinct way to swap the values of two bindings in JavaScript uses destructuring assignment like this:

+ +
[start, end] = [end, start];
+ +

Then you can compute the slope of the line, which determines the amount the coordinate on the other axis changes for each step you take along your main axis. With that, you can run a loop along the main axis while also tracking the corresponding position on the other axis, and you can draw pixels on every iteration. Make sure you round the non-main axis coordinates since they are likely to be fractional and the draw method doesn’t respond well to fractional coordinates.

+ +
+
diff --git a/docs/20_node.html b/docs/20_node.html new file mode 100644 index 000000000..3e748eda1 --- /dev/null +++ b/docs/20_node.html @@ -0,0 +1,548 @@ + + + + + Node.js :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 20Node.js

+ +
+ +

A student asked, ‘The programmers of old used only simple machines and no programming languages, yet they made beautiful programs. Why do we use complicated machines and programming languages?’. Fu-Tzu replied, ‘The builders of old used only sticks and clay, yet they made beautiful huts.’

+ +
Master Yuan-Ma, The Book of Programming
+ +
Picture of a telephone pole
+ +

So far, we have used the JavaScript language in a single environment: the browser. This chapter and the next one will briefly introduce Node.js, a program that allows you to apply your JavaScript skills outside of the browser. With it, you can build anything from small command line tools to HTTP servers that power dynamic websites.

+ +

These chapters aim to teach you the main concepts that Node.js uses and to give you enough information to write useful programs for it. They do not try to be a complete, or even a thorough, treatment of the platform.

+ +

Whereas you could run the code in previous chapters directly on these pages, because it was either raw JavaScript or written for the browser, the code samples in this chapter are written for Node and often won’t run in the browser.

+ +

If you want to follow along and run the code in this chapter, you’ll need to install Node.js version 10.1 or higher. To do so, go to https://nodejs.org and follow the installation instructions for your operating system. You can also find further documentation for Node.js there.

+ +

Background

+ +

One of the more difficult problems with writing systems that communicate over the network is managing input and output—that is, the reading and writing of data to and from the network and hard +drive. Moving data around takes time, and scheduling it cleverly can make a big difference in how quickly a system responds to the user or to network requests.

+ +

In such programs, asynchronous programming is often helpful. It allows the program to send and receive data from and to multiple devices at the same time without complicated thread management and synchronization.

+ +

Node was initially conceived for the purpose of making asynchronous programming easy and convenient. JavaScript lends itself well to a system like Node. It is one of the few programming languages that does not have a built-in way to do in- and output. Thus, JavaScript could be fit onto Node’s rather eccentric approach to in- and output without ending up with two inconsistent interfaces. In 2009, when Node was being designed, people were already doing callback-based programming in the browser, so the community around the language was used to an asynchronous programming style.

+ +

The node command

+ +

When Node.js is installed on a system, it provides a program called node, which is used to run JavaScript files. Say you have a file hello.js, containing this code:

+ +
let message = "Hello world";
+console.log(message);
+ +

You can then run node from the command line like this to execute the program:

+ +
$ node hello.js
+Hello world
+ +

The console.log method in Node does something similar to what it does in the browser. It prints out a piece of text. But in Node, the text will go to the process’s standard output stream, rather than to a browser’s JavaScript console. When running node from the command line, that means you see the logged values in your terminal.

+ +

If you run node without giving it a file, it provides you with a prompt at which you can type JavaScript code and immediately see the result.

+ +
$ node
+> 1 + 1
+2
+> [-1, -2, -3].map(Math.abs)
+[1, 2, 3]
+> process.exit(0)
+$
+ +

The process binding, just like the console binding, is available globally in Node. It provides various ways to inspect and manipulate the current program. The exit method ends the process and can be given an exit status code, which tells the program that started node (in this case, the command line shell) whether the program completed successfully (code zero) or encountered an error (any other code).

+ +

To find the command line arguments given to your script, you can read process.argv, which is an array of strings. Note that it also includes the name of the node command and your script name, so the actual arguments start at index 2. If showargv.js contains the statement console.log(process.argv), you could run it like this:

+ +
$ node showargv.js one --and two
+["node", "/tmp/showargv.js", "one", "--and", "two"]
+ +

All the standard JavaScript global bindings, such as Array, Math, and JSON, are also present in Node’s environment. Browser-related functionality, such as document or prompt, is not.

+ +

Modules

+ +

Beyond the bindings I mentioned, such as console and process, Node puts few additional bindings in the global scope. If you want to access built-in functionality, you have to ask the module system for it.

+ +

The CommonJS module system, based on the require function, was described in Chapter 10. This system is built into Node and is used to load anything from built-in modules to downloaded packages to files that are part of your own program.

+ +

When require is called, Node has to resolve the given string to an actual file that it can load. Pathnames that start with /, ./, or ../ are resolved relative to the current module’s path, where . stands for the current directory, ../ for one directory up, and / for the root of the file system. So if you ask for "./graph" from the file /tmp/robot/robot.js, Node will try to load the file /tmp/robot/graph.js.

+ +

The .js extension may be omitted, and Node will add it if such a file exists. If the required path refers to a directory, Node will try to load the file named index.js in that directory.

+ +

When a string that does not look like a relative or absolute path is given to require, it is assumed to refer to either a built-in module or a module installed in a node_modules directory. For example, require("fs") will give you Node’s built-in file system module. And require("robot") might try to load the library found in node_modules/robot/. A common way to install such libraries is by using NPM, which we’ll come back to in a moment.

+ +

Let’s set up a small project consisting of two files. The first one, called main.js, defines a script that can be called from the command line to reverse a string.

+ +
const {reverse} = require("./reverse");
+
+// Index 2 holds the first actual command line argument
+let argument = process.argv[2];
+
+console.log(reverse(argument));
+ +

The file reverse.js defines a library for reversing strings, which can be used both by this command line tool and by other scripts that need direct access to a string-reversing function.

+ +
exports.reverse = function(string) {
+  return Array.from(string).reverse().join("");
+};
+ +

Remember that adding properties to exports adds them to the interface of the module. Since Node.js treats files as CommonJS modules, main.js can take the exported reverse function from reverse.js.

+ +

We can now call our tool like this:

+ +
$ node main.js JavaScript
+tpircSavaJ
+ +

Installing with NPM

+ +

NPM, which was introduced in Chapter 10, is an online repository of JavaScript modules, many of which are specifically written for Node. When you install Node on your computer, you also get the npm command, which you can use to interact with this repository.

+ +

NPM’s main use is downloading packages. We saw the ini package in Chapter 10. We can use NPM to fetch and install that package on our computer.

+ +
$ npm install ini
+npm WARN enoent ENOENT: no such file or directory,
+         open '/tmp/package.json'
++ ini@1.3.5
+added 1 package in 0.552s
+
+$ node
+> const {parse} = require("ini");
+> parse("x = 1\ny = 2");
+{ x: '1', y: '2' }
+ +

After running npm install, NPM will have created a directory called node_modules. Inside that directory will be an ini directory that contains the library. You can open it and look at the code. When we call require("ini"), this library is loaded, and we can call its parse property to parse a configuration file.

+ +

By default NPM installs packages under the current directory, rather than in a central place. If you are used to other package managers, this may seem unusual, but it has advantages—it puts each application in full control of the packages it installs and makes it easier to manage versions and clean up when removing an application.

+ +

Package files

+ +

In the npm install example, you could see a warning about the fact that the package.json file did not exist. It is recommended to create such a file for each project, either manually or by running npm init. It contains some information about the project, such as its name and version, and lists its dependencies.

+ +

The robot simulation from Chapter 7, as modularized in the exercise in Chapter 10, might have a package.json file like this:

+ +
{
+  "author": "Marijn Haverbeke",
+  "name": "eloquent-javascript-robot",
+  "description": "Simulation of a package-delivery robot",
+  "version": "1.0.0",
+  "main": "run.js",
+  "dependencies": {
+    "dijkstrajs": "^1.0.1",
+    "random-item": "^1.0.0"
+  },
+  "license": "ISC"
+}
+ +

When you run npm install without naming a package to install, NPM will install the dependencies listed in package.json. When you install a specific package that is not already listed as a dependency, NPM will add it to package.json.

+ +

Versions

+ +

A package.json file lists both the program’s own version and versions for its dependencies. Versions are a way to deal with the fact that packages evolve separately, and code written to work with a package as it existed at one point may not work with a later, modified version of the package.

+ +

NPM demands that its packages follow a schema called semantic +versioning, which encodes some information about which versions are compatible (don’t break the old interface) in the version number. A semantic version consists of three numbers, separated by periods, such as 2.3.0. Every time new functionality is added, the middle number has to be incremented. Every time compatibility is broken, so that existing code that uses the package might not work with the new version, the first number has to be incremented.

+ +

A caret character (^) in front of the version number for a dependency in package.json indicates that any version compatible with the given number may be installed. So, for example, "^2.3.0" would mean that any version greater than or equal to 2.3.0 and less than 3.0.0 is allowed.

+ +

The npm command is also used to publish new packages or new versions of packages. If you run npm publish in a directory that has a package.json file, it will publish a package with the name and version listed in the JSON file to the registry. Anyone can publish packages to NPM—though only under a package name that isn’t in use yet since it would be somewhat scary if random people could update existing packages.

+ +

Since the npm program is a piece of software that talks to an open system—the package registry—there is nothing unique about what it does. Another program, yarn, which can be installed from the NPM registry, fills the same role as npm using a somewhat different interface and installation strategy.

+ +

This book won’t delve further into the details of NPM usage. Refer to https://npmjs.org for further documentation and a way to search for packages.

+ +

The file system module

+ +

One of the most commonly used built-in modules in Node is the fs module, which stands for file system. It exports functions for working with files and directories.

+ +

For example, the function called readFile reads a file and then calls a callback with the file’s contents.

+ +
let {readFile} = require("fs");
+readFile("file.txt", "utf8", (error, text) => {
+  if (error) throw error;
+  console.log("The file contains:", text);
+});
+ +

The second argument to readFile indicates the character +encoding used to decode the file into a string. There are several ways in which text can be encoded to binary data, but most modern systems use UTF-8. So unless you have reasons to believe another encoding is used, pass "utf8" when reading a text file. If you do not pass an encoding, Node will assume you are interested in the binary data and will give you a Buffer object instead of a string. This is an array-like object that contains numbers representing the bytes (8-bit chunks of data) in the files.

+ +
const {readFile} = require("fs");
+readFile("file.txt", (error, buffer) => {
+  if (error) throw error;
+  console.log("The file contained", buffer.length, "bytes.",
+              "The first byte is:", buffer[0]);
+});
+ +

A similar function, writeFile, is used to write a file to disk.

+ +
const {writeFile} = require("fs");
+writeFile("graffiti.txt", "Node was here", err => {
+  if (err) console.log(`Failed to write file: ${err}`);
+  else console.log("File written.");
+});
+ +

Here it was not necessary to specify the encoding—writeFile will assume that when it is given a string to write, rather than a Buffer object, it should write it out as text using its default character encoding, which is UTF-8.

+ +

The fs module contains many other useful functions: readdir will return the files in a directory as an array of strings, stat will retrieve information about a file, rename will rename a file, unlink will remove one, and so on. See the documentation at https://nodejs.org for specifics.

+ +

Most of these take a callback function as the last parameter, which they call either with an error (the first argument) or with a successful result (the second). As we saw in Chapter 11, there are downsides to this style of programming—the biggest one being that error handling becomes verbose and error-prone.

+ +

Though promises have been part of JavaScript for a while, at the time of writing their integration into Node.js is still a work in progress. There is an object promises exported from the fs package since version 10.1 that contains most of the same functions as fs but uses promises rather than callback functions.

+ +
const {readFile} = require("fs").promises;
+readFile("file.txt", "utf8")
+  .then(text => console.log("The file contains:", text));
+ +

Sometimes you don’t need asynchronicity, and it just gets in the way. Many of the functions in fs also have a synchronous variant, which has the same name with Sync added to the end. For example, the synchronous version of readFile is called readFileSync.

+ +
const {readFileSync} = require("fs");
+console.log("The file contains:",
+            readFileSync("file.txt", "utf8"));
+ +

Do note that while such a synchronous operation is being performed, your program is stopped entirely. If it should be responding to the user or to other machines on the network, being stuck on a synchronous action might produce annoying delays.

+ +

The HTTP module

+ +

Another central module is called http. It provides functionality for running HTTP servers and making HTTP requests.

+ +

This is all it takes to start an HTTP server:

+ +
const {createServer} = require("http");
+let server = createServer((request, response) => {
+  response.writeHead(200, {"Content-Type": "text/html"});
+  response.write(`
+    <h1>Hello!</h1>
+    <p>You asked for <code>${request.url}</code></p>`);
+  response.end();
+});
+server.listen(8000);
+console.log("Listening! (port 8000)");
+ +

If you run this script on your own machine, you can point your web browser at http://localhost:8000/hello to make a request to your server. It will respond with a small HTML page.

+ +

The function passed as argument to createServer is called every time a client connects to the server. The request and response bindings are objects representing the incoming and outgoing data. The first contains information about the request, such as its url property, which tells us to what URL the request was made.

+ +

So, when you open that page in your browser, it sends a request to your own computer. This causes the server function to run and send back a response, which you can then see in the browser.

+ +

To send something back, you call methods on the response object. The first, writeHead, will write out the response headers (see Chapter 18). You give it the status code (200 for “OK” in this case) and an object that contains header values. The example sets the Content-Type header to inform the client that we’ll be sending back an HTML document.

+ +

Next, the actual response body (the document itself) is sent with response.write. You are allowed to call this method multiple times if you want to send the response piece by piece, for example to stream data to the client as it becomes available. Finally, response.end signals the end of the response.

+ +

The call to server.listen causes the server to start waiting for connections on port 8000. This is why you have to connect to localhost:8000 to speak to this server, rather than just localhost, which would use the default port 80.

+ +

When you run this script, the process just sits there and waits. When a script is listening for events—in this case, network connections—node will not automatically exit when it reaches the end of the script. To close it, press control-C.

+ +

A real web server usually does more than the one in the example—it looks at the request’s method (the method property) to see what action the client is trying to perform and looks at the request’s URL to find out which resource this action is being performed on. We’ll see a more advanced server later in this chapter.

+ +

To act as an HTTP client, we can use the request function in the http module.

+ +
const {request} = require("http");
+let requestStream = request({
+  hostname: "eloquentjavascript.net",
+  path: "/20_node.html",
+  method: "GET",
+  headers: {Accept: "text/html"}
+}, response => {
+  console.log("Server responded with status code",
+              response.statusCode);
+});
+requestStream.end();
+ +

The first argument to request configures the request, telling Node what server to talk to, what path to request from that server, which method to use, and so on. The second argument is the function that should be called when a response comes in. It is given an object that allows us to inspect the response, for example to find out its status code.

+ +

Just like the response object we saw in the server, the object returned by request allows us to stream data into the request with the write method and finish the request with the end method. The example does not use write because GET requests should not contain data in their request body.

+ +

There’s a similar request function in the https module that can be used to make requests to https: URLs.

+ +

Making requests with Node’s raw functionality is rather verbose. There are much more convenient wrapper packages available on NPM. For example, node-fetch provides the promise-based fetch interface that we know from the browser.

+ +

Streams

+ +

We have seen two instances of writable streams in the HTTP examples—namely, the response object that the server could write to and the request object that was returned from request.

+ +

Writable streams are a widely used concept in Node. Such objects have a write method that can be passed a string or a Buffer object to write something to the stream. Their end method closes the stream and optionally takes a value to write to the stream before closing. Both of these methods can also be given a callback as an additional argument, which they will call when the writing or closing has finished.

+ +

It is possible to create a writable stream that points at a file with the createWriteStream function from the fs module. Then you can use the write method on the resulting object to write the file one piece at a time, rather than in one shot as with writeFile.

+ +

Readable streams are a little more involved. Both the request binding that was passed to the HTTP server’s callback and the response binding passed to the HTTP client’s callback are readable streams—a server reads requests and then writes responses, whereas a client first writes a request and then reads a response. Reading from a stream is done using event handlers, rather than methods.

+ +

Objects that emit events in Node have a method called on that is similar to the addEventListener method in the browser. You give it an event name and then a function, and it will register that function to be called whenever the given event occurs.

+ +

Readable streams have "data" and "end" events. The first is fired every time data comes in, and the second is called whenever the stream is at its end. This model is most suited for streaming data that can be immediately processed, even when the whole document isn’t available yet. A file can be read as a readable stream by using the createReadStream function from fs.

+ +

This code creates a server that reads request bodies and streams them back to the client as all-uppercase text:

+ +
const {createServer} = require("http");
+createServer((request, response) => {
+  response.writeHead(200, {"Content-Type": "text/plain"});
+  request.on("data", chunk =>
+    response.write(chunk.toString().toUpperCase()));
+  request.on("end", () => response.end());
+}).listen(8000);
+ +

The chunk value passed to the data handler will be a binary Buffer. We can convert this to a string by decoding it as UTF-8 encoded characters with its toString method.

+ +

The following piece of code, when run with the uppercasing server active, will send a request to that server and write out the response it gets:

+ +
const {request} = require("http");
+request({
+  hostname: "localhost",
+  port: 8000,
+  method: "POST"
+}, response => {
+  response.on("data", chunk =>
+    process.stdout.write(chunk.toString()));
+}).end("Hello server");
+// → HELLO SERVER
+ +

The example writes to process.stdout (the process’s standard output, which is a writable stream) instead of using console.log. We can’t use console.log because it adds an extra newline character after each piece of text that it writes, which isn’t appropriate here since the response may come in as multiple chunks.

+ +

A file server

+ +

Let’s combine our newfound knowledge about HTTP servers and working with the file system to create a bridge between the two: an HTTP server that allows remote access to a file system. Such a server has all kinds of uses—it allows web applications to store and share data, or it can give a group of people shared access to a bunch of files.

+ +

When we treat files as HTTP resources, the HTTP methods GET, PUT, and DELETE can be used to read, write, and delete the files, respectively. We will interpret the path in the request as the path of the file that the request refers to.

+ +

We probably don’t want to share our whole file system, so we’ll interpret these paths as starting in the server’s working directory, which is the directory in which it was started. If I ran the server from /tmp/public/ (or C:\tmp\public\ on Windows), then a request for /file.txt should refer to /tmp/public/file.txt (or C:\tmp\public\file.txt).

+ +

We’ll build the program piece by piece, using an object called methods to store the functions that handle the various HTTP methods. Method handlers are async functions that get the request object as argument and return a promise that resolves to an object that describes the response.

+ +
const {createServer} = require("http");
+
+const methods = Object.create(null);
+
+createServer((request, response) => {
+  let handler = methods[request.method] || notAllowed;
+  handler(request)
+    .catch(error => {
+      if (error.status != null) return error;
+      return {body: String(error), status: 500};
+    })
+    .then(({body, status = 200, type = "text/plain"}) => {
+       response.writeHead(status, {"Content-Type": type});
+       if (body && body.pipe) body.pipe(response);
+       else response.end(body);
+    });
+}).listen(8000);
+
+async function notAllowed(request) {
+  return {
+    status: 405,
+    body: `Method ${request.method} not allowed.`
+  };
+}
+ +

This starts a server that just returns 405 error responses, which is the code used to indicate that the server refuses to handle a given method.

+ +

When a request handler’s promise is rejected, the catch call translates the error into a response object, if it isn’t one already, so that the server can send back an error response to inform the client that it failed to handle the request.

+ +

The status field of the response description may be omitted, in which case it defaults to 200 (OK). The content type, in the type property, can also be left off, in which case the response is assumed to be plain text.

+ +

When the value of body is a readable stream, it will have a pipe method that is used to forward all content from a readable stream to a writable stream. If not, it is assumed to be either null (no body), a string, or a buffer, and it is passed directly to the response’s end method.

+ +

To figure out which file path corresponds to a request URL, the urlPath function uses Node’s built-in url module to parse the URL. It takes its pathname, which will be something like "/file.txt", decodes that to get rid of the %20-style escape codes, and resolves it relative to the program’s working directory.

+ +
const {parse} = require("url");
+const {resolve, sep} = require("path");
+
+const baseDirectory = process.cwd();
+
+function urlPath(url) {
+  let {pathname} = parse(url);
+  let path = resolve(decodeURIComponent(pathname).slice(1));
+  if (path != baseDirectory &&
+      !path.startsWith(baseDirectory + sep)) {
+    throw {status: 403, body: "Forbidden"};
+  }
+  return path;
+}
+ +

As soon as you set up a program to accept network requests, you have to start worrying about security. In this case, if we aren’t careful, it is likely that we’ll accidentally expose our whole file +system to the network.

+ +

File paths are strings in Node. To map such a string to an actual file, there is a nontrivial amount of interpretation going on. Paths may, for example, include ../ to refer to a parent directory. So one obvious source of problems would be requests for paths like /../secret_file.

+ +

To avoid such problems, urlPath uses the resolve function from the path module, which resolves relative paths. It then verifies that the result is below the working directory. The process.cwd function (where cwd stands for “current working directory”) can be used to find this working directory. The sep binding from the path package is the system’s path separator—a backslash on Windows and a forward slash on most other systems. When the path doesn’t start with the base directory, the function throws an error response object, using the HTTP status code indicating that access to the resource is forbidden.

+ +

We’ll set up the GET method to return a list of files when reading a directory and to return the file’s content when reading a regular file.

+ +

One tricky question is what kind of Content-Type header we should set when returning a file’s content. Since these files could be anything, our server can’t simply return the same content type for all of them. NPM can help us again here. The mime package (content type indicators like text/plain are also called MIME types) knows the correct type for a large number of file extensions.

+ +

The following npm command, in the directory where the server script lives, installs a specific version of mime:

+ +
$ npm install mime@2.2.0
+ +

When a requested file does not exist, the correct HTTP status code to return is 404. We’ll use the stat function, which looks up information about a file, to find out both whether the file exists and whether it is a directory.

+ +
const {createReadStream} = require("fs");
+const {stat, readdir} = require("fs").promises;
+const mime = require("mime");
+
+methods.GET = async function(request) {
+  let path = urlPath(request.url);
+  let stats;
+  try {
+    stats = await stat(path);
+  } catch (error) {
+    if (error.code != "ENOENT") throw error;
+    else return {status: 404, body: "File not found"};
+  }
+  if (stats.isDirectory()) {
+    return {body: (await readdir(path)).join("\n")};
+  } else {
+    return {body: createReadStream(path),
+            type: mime.getType(path)};
+  }
+};
+ +

Because it has to touch the disk and thus might take a while, stat is asynchronous. Since we’re using promises rather than callback style, it has to be imported from promises instead of directly from fs.

+ +

When the file does not exist, stat will throw an error object with a code property of "ENOENT". These somewhat obscure, Unix-inspired codes are how you recognize error types in Node.

+ +

The stats object returned by stat tells us a number of things about a file, such as its size (size property) and its modification date (mtime property). Here we are interested in the question of whether it is a directory or a regular file, which the isDirectory method tells us.

+ +

We use readdir to read the array of files in a directory and return it to the client. For normal files, we create a readable stream with createReadStream and return that as the body, along with the content type that the mime package gives us for the file’s name.

+ +

The code to handle DELETE requests is slightly simpler.

+ +
const {rmdir, unlink} = require("fs").promises;
+
+methods.DELETE = async function(request) {
+  let path = urlPath(request.url);
+  let stats;
+  try {
+    stats = await stat(path);
+  } catch (error) {
+    if (error.code != "ENOENT") throw error;
+    else return {status: 204};
+  }
+  if (stats.isDirectory()) await rmdir(path);
+  else await unlink(path);
+  return {status: 204};
+};
+ +

When an HTTP response does not contain any data, the status code 204 (“no content”) can be used to indicate this. Since the response to deletion doesn’t need to transmit any information beyond whether the operation succeeded, that is a sensible thing to return here.

+ +

You may be wondering why trying to delete a nonexistent file returns a success status code, rather than an error. When the file that is being deleted is not there, you could say that the request’s objective is already fulfilled. The HTTP standard encourages us to make requests idempotent, which means that making the same request multiple times produces the same result as making it once. In a way, if you try to delete something that’s already gone, the effect you were trying to do has been achieved—the thing is no longer there.

+ +

This is the handler for PUT requests:

+ +
const {createWriteStream} = require("fs");
+
+function pipeStream(from, to) {
+  return new Promise((resolve, reject) => {
+    from.on("error", reject);
+    to.on("error", reject);
+    to.on("finish", resolve);
+    from.pipe(to);
+  });
+}
+
+methods.PUT = async function(request) {
+  let path = urlPath(request.url);
+  await pipeStream(request, createWriteStream(path));
+  return {status: 204};
+};
+ +

We don’t need to check whether the file exists this time—if it does, we’ll just overwrite it. We again use pipe to move data from a readable stream to a writable one, in this case from the request to the file. But since pipe isn’t written to return a promise, we have to write a wrapper, pipeStream, that creates a promise around the outcome of calling pipe.

+ +

When something goes wrong when opening the file, createWriteStream will still return a stream, but that stream will fire an "error" event. The output stream to the request may also fail, for example if the network goes down. So we wire up both streams’ "error" events to reject the promise. When pipe is done, it will close the output stream, which causes it to fire a "finish" event. That’s the point where we can successfully resolve the promise (returning nothing).

+ +

The full script for the server is available at https://eloquentjavascript.net/code/file_server.js. You can download that and, after installing its dependencies, run it with Node to start your own file server. And, of course, you can modify and extend it to solve this chapter’s exercises or to experiment.

+ +

The command line tool curl, widely available on Unix-like systems (such as macOS and Linux), can be used to make HTTP requests. The following session briefly tests our server. The -X option is used to set the request’s method, and -d is used to include a request body.

+ +
$ curl http://localhost:8000/file.txt
+File not found
+$ curl -X PUT -d hello http://localhost:8000/file.txt
+$ curl http://localhost:8000/file.txt
+hello
+$ curl -X DELETE http://localhost:8000/file.txt
+$ curl http://localhost:8000/file.txt
+File not found
+ +

The first request for file.txt fails since the file does not exist yet. The PUT request creates the file, and behold, the next request successfully retrieves it. After deleting it with a DELETE request, the file is again missing.

+ +

Summary

+ +

Node is a nice, small system that lets us run JavaScript in a nonbrowser context. It was originally designed for network tasks to play the role of a node in a network. But it lends itself to all kinds of scripting tasks, and if writing JavaScript is something you enjoy, automating tasks with Node works well.

+ +

NPM provides packages for everything you can think of (and quite a few things you’d probably never think of), and it allows you to fetch and install those packages with the npm program. Node comes with a number of built-in modules, including the fs module for working with the file system and the http module for running HTTP servers and making HTTP requests.

+ +

All input and output in Node is done asynchronously, unless you explicitly use a synchronous variant of a function, such as readFileSync. When calling such asynchronous functions, you provide callback functions, and Node will call them with an error value and (if available) a result when it is ready.

+ +

Exercises

+ +

Search tool

+ +

On Unix systems, there is a command line tool called grep that can be used to quickly search files for a regular expression.

+ +

Write a Node script that can be run from the command line and acts somewhat like grep. It treats its first command line argument as a regular expression and treats any further arguments as files to search. It should output the names of any file whose content matches the regular expression.

+ +

When that works, extend it so that when one of the arguments is a directory, it searches through all files in that directory and its subdirectories.

+ +

Use asynchronous or synchronous file system functions as you see fit. Setting things up so that multiple asynchronous actions are requested at the same time might speed things up a little, but not a huge amount, since most file systems can read only one thing at a time.

+ +
+ +

Your first command line argument, the regular expression, can be found in process.argv[2]. The input files come after that. You can use the RegExp constructor to go from a string to a regular expression object.

+ +

Doing this synchronously, with readFileSync, is more straightforward, but if you use fs.promises again to get promise-returning functions and write an async function, the code looks similar.

+ +

To figure out whether something is a directory, you can again use stat (or statSync) and the stats object’s isDirectory method.

+ +

Exploring a directory is a branching process. You can do it either by using a recursive function or by keeping an array of work (files that still need to be explored). To find the files in a directory, you can call readdir or readdirSync. The strange capitalization—Node’s file system function naming is loosely based on standard Unix functions, such as readdir, that are all lowercase, but then it adds Sync with a capital letter.

+ +

To go from a filename read with readdir to a full path name, you have to combine it with the name of the directory, putting a slash +character (/) between them.

+ +
+ +

Directory creation

+ +

Though the DELETE method in our file server is able to delete directories (using rmdir), the server currently does not provide any way to create a directory.

+ +

Add support for the MKCOL method (“make collection”), which should create a directory by calling mkdir from the fs module. MKCOL is not a widely used HTTP method, but it does exist for this same purpose in the WebDAV standard, which specifies a set of conventions on top of HTTP that make it suitable for creating documents.

+ +
+ +

You can use the function that implements the DELETE method as a blueprint for the MKCOL method. When no file is found, try to create a directory with mkdir. When a directory exists at that path, you can return a 204 response so that directory creation requests are idempotent. If a nondirectory file exists here, return an error code. Code 400 (“bad request”) would be appropriate.

+ +
+ +

A public space on the web

+ +

Since the file server serves up any kind of file and even includes the right Content-Type header, you can use it to serve a website. Since it allows everybody to delete and replace files, it would be an interesting kind of website: one that can be modified, improved, and vandalized by everybody who takes the time to create the right HTTP request.

+ +

Write a basic HTML page that includes a simple JavaScript file. Put the files in a directory served by the file server and open them in your browser.

+ +

Next, as an advanced exercise or even a weekend project, combine all the knowledge you gained from this book to build a more user-friendly interface for modifying the website—from inside the website.

+ +

Use an HTML form to edit the content of the files that make up the website, allowing the user to update them on the server by using HTTP requests, as described in Chapter 18.

+ +

Start by making only a single file editable. Then make it so that the user can select which file to edit. Use the fact that our file server returns lists of files when reading a directory.

+ +

Don’t work directly in the code exposed by the file server since if you make a mistake, you are likely to damage the files there. Instead, keep your work outside of the publicly accessible directory and copy it there when testing.

+ +
+ +

You can create a <textarea> element to hold the content of the file that is being edited. A GET request, using fetch, can retrieve the current content of the file. You can use relative URLs like index.html, instead of http://localhost:8000/index.html, to refer to files on the same server as the running script.

+ +

Then, when the user clicks a button (you can use a <form> element and "submit" event), make a PUT request to the same URL, with the content of the <textarea> as request body, to save the file.

+ +

You can then add a <select> element that contains all the files in the server’s top directory by adding <option> elements containing the lines returned by a GET request to the URL /. When the user selects another file (a "change" event on the field), the script must fetch and display that file. When saving a file, use the currently selected filename.

+ +
+
diff --git a/docs/21_skillsharing.html b/docs/21_skillsharing.html new file mode 100644 index 000000000..08f987908 --- /dev/null +++ b/docs/21_skillsharing.html @@ -0,0 +1,636 @@ + + + + + Project: Skill-Sharing Website :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 21Project: Skill-Sharing Website

+ +
+ +

If you have knowledge, let others light their candles at it.

+ +
Margaret Fuller
+ +
Picture of two unicycles
+ +

A skill-sharing meeting is an event where people with a shared interest come together and give small, informal presentations about things they know. At a gardening skill-sharing meeting, someone might explain how to cultivate celery. Or in a programming skill-sharing group, you could drop by and tell people about Node.js.

+ +

Such meetups—also often called users’ groups when they are about computers—are a great way to broaden your horizon, learn about new developments, or simply meet people with similar interests. Many larger cities have JavaScript meetups. They are typically free to attend, and I’ve found the ones I’ve visited to be friendly and welcoming.

+ +

In this final project chapter, our goal is to set up a website for managing talks given at a skill-sharing meeting. Imagine a small group of people meeting up regularly in the office of one of the members to talk about unicycling. The previous organizer of the meetings moved to another town, and nobody stepped forward to take over this task. We want a system that will let the participants propose and discuss talks among themselves, without a central organizer.

+ +

Just like in the previous chapter, some of the code in this chapter is written for Node.js, and running it directly in the HTML page that you are looking at is unlikely to work. The full code for the project can be downloaded from https://eloquentjavascript.net/code/skillsharing.zip.

+ +

Design

+ +

There is a server part to this project, written for Node.js, and a client part, written for the browser. The server stores the system’s data and provides it to the client. It also serves the files that implement the client-side system.

+ +

The server keeps the list of talks proposed for the next meeting, and the client shows this list. Each talk has a presenter name, a title, a summary, and an array of comments associated with it. The client allows users to propose new talks (adding them to the list), delete talks, and comment on existing talks. Whenever the user makes such a change, the client makes an HTTP request to tell the server about it.

Screenshot of the skill-sharing website
+ +

The application will be set up to show a live view of the current proposed talks and their comments. Whenever someone, somewhere, submits a new talk or adds a comment, all people who have the page open in their browsers should immediately see the change. This poses a bit of a challenge—there is no way for a web server to open a connection to a client, nor is there a good way to know which clients are currently looking at a given website.

+ +

A common solution to this problem is called long polling, which happens to be one of the motivations for Node’s design.

+ +

Long polling

+ +

To be able to immediately notify a client that something changed, we need a connection to that client. Since web browsers do not traditionally accept connections and clients are often behind routers that would block such connections anyway, having the server initiate this connection is not practical.

+ +

We can arrange for the client to open the connection and keep it around so that the server can use it to send information when it needs to do so.

+ +

But an HTTP request allows only a simple flow of information: the client sends a request, the server comes back with a single response, and that is it. There is a technology called WebSockets, supported by modern browsers, that makes it possible to open connections for arbitrary data exchange. But using them properly is somewhat tricky.

+ +

In this chapter, we use a simpler technique—long polling—where clients continuously ask the server for new information using regular HTTP requests, and the server stalls its answer when it has nothing new to report.

+ +

As long as the client makes sure it constantly has a polling request open, it will receive information from the server quickly after it becomes available. For example, if Fatma has our skill-sharing application open in her browser, that browser will have made a request for updates and will be waiting for a response to that request. When Iman submits a talk on Extreme Downhill Unicycling, the server will notice that Fatma is waiting for updates and send a response containing the new talk to her pending request. Fatma’s browser will receive the data and update the screen to show the talk.

+ +

To prevent connections from timing out (being aborted because of a lack of activity), long polling techniques usually set a maximum time for each request, after which the server will respond anyway, even though it has nothing to report, after which the client will start a new request. Periodically restarting the request also makes the technique more robust, allowing clients to recover from temporary connection failures or server problems.

+ +

A busy server that is using long polling may have thousands of waiting requests, and thus TCP connections, open. Node, which makes it easy to manage many connections without creating a separate thread of control for each one, is a good fit for such a system.

+ +

HTTP interface

+ +

Before we start designing either the server or the client, let’s think about the point where they touch: the HTTP interface over which they communicate.

+ +

We will use JSON as the format of our request and response body. Like in the file server from Chapter 20, we’ll try to make good use of HTTP methods and headers. The interface is centered around the /talks path. Paths that do not start with /talks will be used for serving static files—the HTML and JavaScript code for the client-side system.

+ +

A GET request to /talks returns a JSON document like this:

+ +
[{"title": "Unituning",
+  "presenter": "Jamal",
+  "summary": "Modifying your cycle for extra style",
+  "comments": []}]
+ +

Creating a new talk is done by making a PUT request to a URL like /talks/Unituning, where the part after the second slash is the title of the talk. The PUT request’s body should contain a JSON object that has presenter and summary properties.

+ +

Since talk titles may contain spaces and other characters that may not appear normally in a URL, title strings must be encoded with the encodeURIComponent function when building up such a URL.

+ +
console.log("/talks/" + encodeURIComponent("How to Idle"));
+// → /talks/How%20to%20Idle
+ +

A request to create a talk about idling might look something like this:

+ +
PUT /talks/How%20to%20Idle HTTP/1.1
+Content-Type: application/json
+Content-Length: 92
+
+{"presenter": "Maureen",
+ "summary": "Standing still on a unicycle"}
+ +

Such URLs also support GET requests to retrieve the JSON representation of a talk and DELETE requests to delete a talk.

+ +

Adding a comment to a talk is done with a POST request to a URL like /talks/Unituning/comments, with a JSON body that has author and message properties.

+ +
POST /talks/Unituning/comments HTTP/1.1
+Content-Type: application/json
+Content-Length: 72
+
+{"author": "Iman",
+ "message": "Will you talk about raising a cycle?"}
+ +

To support long polling, GET requests to /talks may include extra headers that inform the server to delay the response if no new information is available. We’ll use a pair of headers normally intended to manage caching: ETag and If-None-Match.

+ +

Servers may include an ETag (“entity tag”) header in a response. Its value is a string that identifies the current version of the resource. Clients, when they later request that resource again, may make a conditional request by including an If-None-Match header whose value holds that same string. If the resource hasn’t changed, the server will respond with status code 304, which means “not modified”, telling the client that its cached version is still current. When the tag does not match, the server responds as normal.

+ +

We need something like this, where the client can tell the server which version of the list of talks it has, and the server responds only when that list has changed. But instead of immediately returning a 304 response, the server should stall the response and return only when something new is available or a given amount of time has elapsed. To distinguish long polling requests from normal conditional requests, we give them another header, Prefer: wait=90, which tells the server that the client is willing to wait up to 90 seconds for the response.

+ +

The server will keep a version number that it updates every time the talks change and will use that as the ETag value. Clients can make requests like this to be notified when the talks change:

+ +
GET /talks HTTP/1.1
+If-None-Match: "4"
+Prefer: wait=90
+
+(time passes)
+
+HTTP/1.1 200 OK
+Content-Type: application/json
+ETag: "5"
+Content-Length: 295
+
+[....]
+ +

The protocol described here does not do any access control. Everybody can comment, modify talks, and even delete them. (Since the Internet is full of hooligans, putting such a system online without further protection probably wouldn’t end well.)

+ +

The server

+ +

Let’s start by building the server-side part of the program. The code in this section runs on Node.js.

+ +

Routing

+ +

Our server will use createServer to start an HTTP server. In the function that handles a new request, we must distinguish between the various kinds of requests (as determined by the method and the path) that we support. This can be done with a long chain of if statements, but there is a nicer way.

+ +

A router is a component that helps dispatch a request to the function that can handle it. You can tell the router, for example, that PUT requests with a path that matches the regular expression /^\/talks\/([^\/]+)$/ (/talks/ followed by a talk title) can be handled by a given function. In addition, it can help extract the meaningful parts of the path (in this case the talk title), wrapped in parentheses in the regular expression, and pass them to the handler function.

+ +

There are a number of good router packages on NPM, but here we’ll write one ourselves to illustrate the principle.

+ +

This is router.js, which we will later require from our server module:

+ +
const {parse} = require("url");
+
+module.exports = class Router {
+  constructor() {
+    this.routes = [];
+  }
+  add(method, url, handler) {
+    this.routes.push({method, url, handler});
+  }
+  resolve(context, request) {
+    let path = parse(request.url).pathname;
+
+    for (let {method, url, handler} of this.routes) {
+      let match = url.exec(path);
+      if (!match || request.method != method) continue;
+      let urlParts = match.slice(1).map(decodeURIComponent);
+      return handler(context, ...urlParts, request);
+    }
+    return null;
+  }
+};
+ +

The module exports the Router class. A router object allows new handlers to be registered with the add method and can resolve requests with its resolve method.

+ +

The latter will return a response when a handler was found, and null otherwise. It tries the routes one at a time (in the order in which they were defined) until a matching one is found.

+ +

The handler functions are called with the context value (which will be the server instance in our case), match strings for any groups they defined in their regular expression, and the request object. The strings have to be URL-decoded since the raw URL may contain %20-style codes.

+ +

Serving files

+ +

When a request matches none of the request types defined in our router, the server must interpret it as a request for a file in the public directory. It would be possible to use the file server defined in Chapter 20 to serve such files, but we neither need nor want to support PUT and DELETE requests on files, and we would like to have advanced features such as support for caching. So let’s use a solid, well-tested static file server from NPM instead.

+ +

I opted for ecstatic. This isn’t the only such server on NPM, but it works well and fits our purposes. The ecstatic package exports a function that can be called with a configuration object to produce a request handler function. We use the root option to tell the server where it should look for files. The handler function accepts request and response parameters and can be passed directly to createServer to create a server that serves only files. We want to first check for requests that we should handle specially, though, so we wrap it in another function.

+ +
const {createServer} = require("http");
+const Router = require("./router");
+const ecstatic = require("ecstatic");
+
+const router = new Router();
+const defaultHeaders = {"Content-Type": "text/plain"};
+
+class SkillShareServer {
+  constructor(talks) {
+    this.talks = talks;
+    this.version = 0;
+    this.waiting = [];
+
+    let fileServer = ecstatic({root: "./public"});
+    this.server = createServer((request, response) => {
+      let resolved = router.resolve(this, request);
+      if (resolved) {
+        resolved.catch(error => {
+          if (error.status != null) return error;
+          return {body: String(error), status: 500};
+        }).then(({body,
+                  status = 200,
+                  headers = defaultHeaders}) => {
+          response.writeHead(status, headers);
+          response.end(body);
+        });
+      } else {
+        fileServer(request, response);
+      }
+    });
+  }
+  start(port) {
+    this.server.listen(port);
+  }
+  stop() {
+    this.server.close();
+  }
+}
+ +

This uses a similar convention as the file server from the previous chapter for responses—handlers return promises that resolve to objects describing the response. It wraps the server in an object that also holds its state.

+ +

Talks as resources

+ +

The talks that have been proposed are stored in the talks property of the server, an object whose property names are the talk titles. These will be exposed as HTTP resources under /talks/[title], so we need to add handlers to our router that implement the various methods that clients can use to work with them.

+ +

The handler for requests that GET a single talk must look up the talk and respond either with the talk’s JSON data or with a 404 error response.

+ +
const talkPath = /^\/talks\/([^\/]+)$/;
+
+router.add("GET", talkPath, async (server, title) => {
+  if (title in server.talks) {
+    return {body: JSON.stringify(server.talks[title]),
+            headers: {"Content-Type": "application/json"}};
+  } else {
+    return {status: 404, body: `No talk '${title}' found`};
+  }
+});
+ +

Deleting a talk is done by removing it from the talks object.

+ +
router.add("DELETE", talkPath, async (server, title) => {
+  if (title in server.talks) {
+    delete server.talks[title];
+    server.updated();
+  }
+  return {status: 204};
+});
+ +

The updated method, which we will define later, notifies waiting long polling requests about the change.

+ +

To retrieve the content of a request body, we define a function called readStream, which reads all content from a readable stream and returns a promise that resolves to a string.

+ +
function readStream(stream) {
+  return new Promise((resolve, reject) => {
+    let data = "";
+    stream.on("error", reject);
+    stream.on("data", chunk => data += chunk.toString());
+    stream.on("end", () => resolve(data));
+  });
+}
+ +

One handler that needs to read request bodies is the PUT handler, which is used to create new talks. It has to check whether the data it was given has presenter and summary properties, which are strings. Any data coming from outside the system might be nonsense, and we don’t want to corrupt our internal data model or crash when bad requests come in.

+ +

If the data looks valid, the handler stores an object that represents the new talk in the talks object, possibly overwriting an existing talk with this title, and again calls updated.

+ +
router.add("PUT", talkPath,
+           async (server, title, request) => {
+  let requestBody = await readStream(request);
+  let talk;
+  try { talk = JSON.parse(requestBody); }
+  catch (_) { return {status: 400, body: "Invalid JSON"}; }
+
+  if (!talk ||
+      typeof talk.presenter != "string" ||
+      typeof talk.summary != "string") {
+    return {status: 400, body: "Bad talk data"};
+  }
+  server.talks[title] = {title,
+                         presenter: talk.presenter,
+                         summary: talk.summary,
+                         comments: []};
+  server.updated();
+  return {status: 204};
+});
+ +

Adding a comment to a talk works similarly. We use readStream to get the content of the request, validate the resulting data, and store it as a comment when it looks valid.

+ +
router.add("POST", /^\/talks\/([^\/]+)\/comments$/,
+           async (server, title, request) => {
+  let requestBody = await readStream(request);
+  let comment;
+  try { comment = JSON.parse(requestBody); }
+  catch (_) { return {status: 400, body: "Invalid JSON"}; }
+
+  if (!comment ||
+      typeof comment.author != "string" ||
+      typeof comment.message != "string") {
+    return {status: 400, body: "Bad comment data"};
+  } else if (title in server.talks) {
+    server.talks[title].comments.push(comment);
+    server.updated();
+    return {status: 204};
+  } else {
+    return {status: 404, body: `No talk '${title}' found`};
+  }
+});
+ +

Trying to add a comment to a nonexistent talk returns a 404 error.

+ +

Long polling support

+ +

The most interesting aspect of the server is the part that handles long polling. When a GET request comes in for /talks, it may be either a regular request or a long polling request.

+ +

There will be multiple places in which we have to send an array of talks to the client, so we first define a helper method that builds up such an array and includes an ETag header in the response.

+ +
SkillShareServer.prototype.talkResponse = function() {
+  let talks = [];
+  for (let title of Object.keys(this.talks)) {
+    talks.push(this.talks[title]);
+  }
+  return {
+    body: JSON.stringify(talks),
+    headers: {"Content-Type": "application/json",
+              "ETag": `"${this.version}"`}
+  };
+};
+ +

The handler itself needs to look at the request headers to see whether If-None-Match and Prefer headers are present. Node stores headers, whose names are specified to be case insensitive, under their lowercase names.

+ +
router.add("GET", /^\/talks$/, async (server, request) => {
+  let tag = /"(.*)"/.exec(request.headers["if-none-match"]);
+  let wait = /\bwait=(\d+)/.exec(request.headers["prefer"]);
+  if (!tag || tag[1] != server.version) {
+    return server.talkResponse();
+  } else if (!wait) {
+    return {status: 304};
+  } else {
+    return server.waitForChanges(Number(wait[1]));
+  }
+});
+ +

If no tag was given or a tag was given that doesn’t match the server’s current version, the handler responds with the list of talks. If the request is conditional and the talks did not change, we consult the Prefer header to see whether we should delay the response or respond right away.

+ +

Callback functions for delayed requests are stored in the server’s waiting array so that they can be notified when something happens. The waitForChanges method also immediately sets a timer to respond with a 304 status when the request has waited long enough.

+ +
SkillShareServer.prototype.waitForChanges = function(time) {
+  return new Promise(resolve => {
+    this.waiting.push(resolve);
+    setTimeout(() => {
+      if (!this.waiting.includes(resolve)) return;
+      this.waiting = this.waiting.filter(r => r != resolve);
+      resolve({status: 304});
+    }, time * 1000);
+  });
+};
+ +

Registering a change with updated increases the version property and wakes up all waiting requests.

+ +
SkillShareServer.prototype.updated = function() {
+  this.version++;
+  let response = this.talkResponse();
+  this.waiting.forEach(resolve => resolve(response));
+  this.waiting = [];
+};
+ +

That concludes the server code. If we create an instance of SkillShareServer and start it on port 8000, the resulting HTTP server serves files from the public subdirectory alongside a talk-managing interface under the /talks URL.

+ +
new SkillShareServer(Object.create(null)).start(8000);
+ +

The client

+ +

The client-side part of the skill-sharing website consists of three files: a tiny HTML page, a style sheet, and a JavaScript file.

+ +

HTML

+ +

It is a widely used convention for web servers to try to serve a file named index.html when a request is made directly to a path that corresponds to a directory. The file server module we use, ecstatic, supports this convention. When a request is made to the path /, the server looks for the file ./public/index.html (./public being the root we gave it) and returns that file if found.

+ +

Thus, if we want a page to show up when a browser is pointed at our server, we should put it in public/index.html. This is our index file:

+ +
<!doctype html>
+<meta charset="utf-8">
+<title>Skill Sharing</title>
+<link rel="stylesheet" href="skillsharing.css">
+
+<h1>Skill Sharing</h1>
+
+<script src="skillsharing_client.js"></script>
+ +

It defines the document title and includes a style sheet, which defines a few styles to, among other things, make sure there is some space between talks.

+ +

At the bottom, it adds a heading at the top of the page and loads the script that contains the client-side application.

+ +

Actions

+ +

The application state consists of the list of talks and the name of the user, and we’ll store it in a {talks, user} object. We don’t allow the user interface to directly manipulate the state or send off HTTP requests. Rather, it may emit actions that describe what the user is trying to do.

+ +

The handleAction function takes such an action and makes it happen. Because our state updates are so simple, state changes are handled in the same function.

+ +
function handleAction(state, action) {
+  if (action.type == "setUser") {
+    localStorage.setItem("userName", action.user);
+    return Object.assign({}, state, {user: action.user});
+  } else if (action.type == "setTalks") {
+    return Object.assign({}, state, {talks: action.talks});
+  } else if (action.type == "newTalk") {
+    fetchOK(talkURL(action.title), {
+      method: "PUT",
+      headers: {"Content-Type": "application/json"},
+      body: JSON.stringify({
+        presenter: state.user,
+        summary: action.summary
+      })
+    }).catch(reportError);
+  } else if (action.type == "deleteTalk") {
+    fetchOK(talkURL(action.talk), {method: "DELETE"})
+      .catch(reportError);
+  } else if (action.type == "newComment") {
+    fetchOK(talkURL(action.talk) + "/comments", {
+      method: "POST",
+      headers: {"Content-Type": "application/json"},
+      body: JSON.stringify({
+        author: state.user,
+        message: action.message
+      })
+    }).catch(reportError);
+  }
+  return state;
+}
+ +

We’ll store the user’s name in localStorage so that it can be restored when the page is loaded.

+ +

The actions that need to involve the server make network requests, using fetch, to the HTTP interface described earlier. We use a wrapper function, fetchOK, which makes sure the returned promise is rejected when the server returns an error code.

+ +
function fetchOK(url, options) {
+  return fetch(url, options).then(response => {
+    if (response.status < 400) return response;
+    else throw new Error(response.statusText);
+  });
+}
+ +

This helper function is used to build up a URL for a talk with a given title.

+ +
function talkURL(title) {
+  return "talks/" + encodeURIComponent(title);
+}
+ +

When the request fails, we don’t want to have our page just sit there, doing nothing without explanation. So we define a function called reportError, which at least shows the user a dialog that tells them something went wrong.

+ +
function reportError(error) {
+  alert(String(error));
+}
+ +

Rendering components

+ +

We’ll use an approach similar to the one we saw in Chapter 19, splitting the application into components. But since some of the components either never need to update or are always fully redrawn when updated, we’ll define those not as classes but as functions that directly return a DOM node. For example, here is a component that shows the field where the user can enter their name:

+ +
function renderUserField(name, dispatch) {
+  return elt("label", {}, "Your name: ", elt("input", {
+    type: "text",
+    value: name,
+    onchange(event) {
+      dispatch({type: "setUser", user: event.target.value});
+    }
+  }));
+}
+ +

The elt function used to construct DOM elements is the one we used in Chapter 19.

+ +

A similar function is used to render talks, which include a list of comments and a form for adding a new comment.

+ +
function renderTalk(talk, dispatch) {
+  return elt(
+    "section", {className: "talk"},
+    elt("h2", null, talk.title, " ", elt("button", {
+      type: "button",
+      onclick() {
+        dispatch({type: "deleteTalk", talk: talk.title});
+      }
+    }, "Delete")),
+    elt("div", null, "by ",
+        elt("strong", null, talk.presenter)),
+    elt("p", null, talk.summary),
+    ...talk.comments.map(renderComment),
+    elt("form", {
+      onsubmit(event) {
+        event.preventDefault();
+        let form = event.target;
+        dispatch({type: "newComment",
+                  talk: talk.title,
+                  message: form.elements.comment.value});
+        form.reset();
+      }
+    }, elt("input", {type: "text", name: "comment"}), " ",
+       elt("button", {type: "submit"}, "Add comment")));
+}
+ +

The "submit" event handler calls form.reset to clear the form’s content after creating a "newComment" action.

+ +

When creating moderately complex pieces of DOM, this style of programming starts to look rather messy. There’s a widely used (non-standard) JavaScript extension called JSX that lets you write HTML directly in your scripts, which can make such code prettier (depending on what you consider pretty). Before you can actually run such code, you have to run a program on your script to convert the pseudo-HTML into JavaScript function calls much like the ones we use here.

+ +

Comments are simpler to render.

+ +
function renderComment(comment) {
+  return elt("p", {className: "comment"},
+             elt("strong", null, comment.author),
+             ": ", comment.message);
+}
+ +

Finally, the form that the user can use to create a new talk is rendered like this:

+ +
function renderTalkForm(dispatch) {
+  let title = elt("input", {type: "text"});
+  let summary = elt("input", {type: "text"});
+  return elt("form", {
+    onsubmit(event) {
+      event.preventDefault();
+      dispatch({type: "newTalk",
+                title: title.value,
+                summary: summary.value});
+      event.target.reset();
+    }
+  }, elt("h3", null, "Submit a Talk"),
+     elt("label", null, "Title: ", title),
+     elt("label", null, "Summary: ", summary),
+     elt("button", {type: "submit"}, "Submit"));
+}
+ +

Polling

+ +

To start the app we need the current list of talks. Since the initial load is closely related to the long polling process—the ETag from the load must be used when polling—we’ll write a function that keeps polling the server for /talks and calls a callback function when a new set of talks is available.

+ +
async function pollTalks(update) {
+  let tag = undefined;
+  for (;;) {
+    let response;
+    try {
+      response = await fetchOK("/talks", {
+        headers: tag && {"If-None-Match": tag,
+                         "Prefer": "wait=90"}
+      });
+    } catch (e) {
+      console.log("Request failed: " + e);
+      await new Promise(resolve => setTimeout(resolve, 500));
+      continue;
+    }
+    if (response.status == 304) continue;
+    tag = response.headers.get("ETag");
+    update(await response.json());
+  }
+}
+ +

This is an async function so that looping and waiting for the request is easier. It runs an infinite loop that, on each iteration, retrieves the list of talks—either normally or, if this isn’t the first request, with the headers included that make it a long polling request.

+ +

When a request fails, the function waits a moment and then tries again. This way, if your network connection goes away for a while and then comes back, the application can recover and continue updating. The promise resolved via setTimeout is a way to force the async function to wait.

+ +

When the server gives back a 304 response, that means a long polling request timed out, so the function should just immediately start the next request. If the response is a normal 200 response, its body is read as JSON and passed to the callback, and its ETag header value is stored for the next iteration.

+ +

The application

+ +

The following component ties the whole user interface together:

+ +
class SkillShareApp {
+  constructor(state, dispatch) {
+    this.dispatch = dispatch;
+    this.talkDOM = elt("div", {className: "talks"});
+    this.dom = elt("div", null,
+                   renderUserField(state.user, dispatch),
+                   this.talkDOM,
+                   renderTalkForm(dispatch));
+    this.syncState(state);
+  }
+
+  syncState(state) {
+    if (state.talks != this.talks) {
+      this.talkDOM.textContent = "";
+      for (let talk of state.talks) {
+        this.talkDOM.appendChild(
+          renderTalk(talk, this.dispatch));
+      }
+      this.talks = state.talks;
+    }
+  }
+}
+ +

When the talks change, this component redraws all of them. This is simple but also wasteful. We’ll get back to that in the exercises.

+ +

We can start the application like this:

+ +
function runApp() {
+  let user = localStorage.getItem("userName") || "Anon";
+  let state, app;
+  function dispatch(action) {
+    state = handleAction(state, action);
+    app.syncState(state);
+  }
+
+  pollTalks(talks => {
+    if (!app) {
+      state = {user, talks};
+      app = new SkillShareApp(state, dispatch);
+      document.body.appendChild(app.dom);
+    } else {
+      dispatch({type: "setTalks", talks});
+    }
+  }).catch(reportError);
+}
+
+runApp();
+ +

If you run the server and open two browser windows for http://localhost:8000 next to each other, you can see that the actions you perform in one window are immediately visible in the other.

+ +

Exercises

+ +

The following exercises will involve modifying the system defined in this chapter. To work on them, make sure you download the code first (https://eloquentjavascript.net/code/skillsharing.zip), have Node installed https://nodejs.org, and have installed the project’s dependency with npm install.

+ +

Disk persistence

+ +

The skill-sharing server keeps its data purely in memory. This means that when it crashes or is restarted for any reason, all talks and comments are lost.

+ +

Extend the server so that it stores the talk data to disk and automatically reloads the data when it is restarted. Do not worry about efficiency—do the simplest thing that works.

+ +
+ +

The simplest solution I can come up with is to encode the whole talks object as JSON and dump it to a file with writeFile. There is already a method (updated) that is called every time the server’s data changes. It can be extended to write the new data to disk.

+ +

Pick a filename, for example ./talks.json. When the server starts, it can try to read that file with readFile, and if that succeeds, the server can use the file’s contents as its starting data.

+ +

Beware, though. The talks object started as a prototype-less object so that the in operator could reliably be used. JSON.parse will return regular objects with Object.prototype as their prototype. If you use JSON as your file format, you’ll have to copy the properties of the object returned by JSON.parse into a new, prototype-less object.

+ +
+ +

Comment field resets

+ +

The wholesale redrawing of talks works pretty well because you usually can’t tell the difference between a DOM node and its identical replacement. But there are exceptions. If you start typing something in the comment field for a talk in one browser window and then, in another, add a comment to that talk, the field in the first window will be redrawn, removing both its content and its focus.

+ +

In a heated discussion, where multiple people are adding comments at the same time, this would be annoying. Can you come up with a way to solve it?

+ +
+ +

The best way to do this is probably to make talks component objects, with a syncState method, so that they can be updated to show a modified version of the talk. During normal operation, the only way a talk can be changed is by adding more comments, so the syncState method can be relatively simple.

+ +

The difficult part is that, when a changed list of talks comes in, we have to reconcile the existing list of DOM components with the talks on the new list—deleting components whose talk was deleted and updating components whose talk changed.

+ +

To do this, it might be helpful to keep a data structure that stores the talk components under the talk titles so that you can easily figure out whether a component exists for a given talk. You can then loop over the new array of talks, and for each of them, either synchronize an existing component or create a new one. To delete components for deleted talks, you’ll have to also loop over the components and check whether the corresponding talks still exist.

+ +
+
diff --git a/docs/Eloquent_JavaScript.epub b/docs/Eloquent_JavaScript.epub new file mode 120000 index 000000000..a9098be1e --- /dev/null +++ b/docs/Eloquent_JavaScript.epub @@ -0,0 +1 @@ +../book.epub \ No newline at end of file diff --git a/docs/Eloquent_JavaScript.mobi b/docs/Eloquent_JavaScript.mobi new file mode 120000 index 000000000..b41e9cb51 --- /dev/null +++ b/docs/Eloquent_JavaScript.mobi @@ -0,0 +1 @@ +../book.mobi \ No newline at end of file diff --git a/docs/Eloquent_JavaScript.pdf b/docs/Eloquent_JavaScript.pdf new file mode 120000 index 000000000..4da1f1afc --- /dev/null +++ b/docs/Eloquent_JavaScript.pdf @@ -0,0 +1 @@ +../book.pdf \ No newline at end of file diff --git a/docs/Eloquent_JavaScript_small.pdf b/docs/Eloquent_JavaScript_small.pdf new file mode 120000 index 000000000..c28a0ae94 --- /dev/null +++ b/docs/Eloquent_JavaScript_small.pdf @@ -0,0 +1 @@ +../book_mobile.pdf \ No newline at end of file diff --git a/docs/author.html b/docs/author.html new file mode 100644 index 000000000..3e287ea46 --- /dev/null +++ b/docs/author.html @@ -0,0 +1,10 @@ + + +
+

Marijn Haverbeke, Programmer

+ +

You can reach me at marijn@haverbeke.nl, or visit my web page, marijnhaverbeke.nl.

+
diff --git a/docs/author.json b/docs/author.json new file mode 100644 index 000000000..24be3c131 --- /dev/null +++ b/docs/author.json @@ -0,0 +1,5 @@ +{ + "name": "Marijn Haverbeke", + "email": "marijn@haverbeke.nl", + "website": "https://marijnhaverbeke.nl/" +} diff --git a/docs/author.txt b/docs/author.txt new file mode 100644 index 000000000..6cf92433c --- /dev/null +++ b/docs/author.txt @@ -0,0 +1 @@ +My name is Marijn Haverbeke. You can email me at marijn@haverbeke.nl, or visit my website, https://marijnhaverbeke.nl/ . diff --git a/docs/backers.html b/docs/backers.html new file mode 100644 index 000000000..5fbc2bf40 --- /dev/null +++ b/docs/backers.html @@ -0,0 +1,3687 @@ + + + + + Eloquent JavaScript :: 2nd Edition Backers + + + + + +
+

List of Backers
Eloquent JavaScript, 2nd Edition

+ +

These are the kind souls who have contributed towards making the +second edition of Eloquent JavaScript +possible.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + 10,000 $
+ + 7000 €
+ Magnus Skog + 1000 €
+ + 1000 $Donation from dev team at Ghostery! Thanks for the awesome work!
+ Anonymous + 1000 $
+ erin lee + 200 $
+ Alexander Shendi + 100 €
+ Anonymous + 100 €
+ Anonymous + 100 €I love your book. Thank you very much!
+ Morgan Roderick + 100 €We need a second edition of the best, free JavaScript book
+ Stelian Ionescu + 100 €
+ Anonymous + 100 $
+ Anton Kovalyov + 100 $
+ david karapetyan + 100 $I fully support this effort. We need more decentralized sources of education for technology related matters.
+ Joshua Waheed + 100 $
+ Kevin Layer, Franz Inc. + 100 $
+ Nathan Youngman + 100 $
+ Raynos + 100 $
+ Tim Caswell + 100 $I especially liked the intro chapter in which you describe programming as a creative art more than a cold technical task.
+ Stan Yamane + 75 $
+ tiffon + 75 $
+ Anonymous + 56 €
+ [o]_O + 50 €I am a T-SQL monkey with the occasional C# app or python script. Much to my dismay, I was assigned the task of fixing some Javascript bugs in an internal deployment tool. All I knew at the time about Javascript was that it sucked, so I tried reading the 1st edition of Eloquent Javascript. Unfortunately, it worked too well: not only did I fix the problems, I now think Javascript is cool :-(
+ David Owens + 50 €
+ Dominique Poulain + 50 €
+ Jans Aasman + 50 €looking forward to see the new edition...
+ Kai Elvin + 50 €Not too interested in the book itself, donating to promote the funding model. I would advise to keep the bitcoins as Bitpay provides a very bad exchange rate.
+ Koen Steenbrink + 50 €First edition helped me a lot. Looking forward for the next one.
+ Mihai Bazon + 50 €
+ Siltaar + 50 €
+ tmk + 50 €
+ Tom Weber + 50 €
+ Vaidas Gaurilcikas + 50 €Thanks for the great work! I found the 1st edition of Eloquent Javascript to be one of the most pleasant to read and useful programming books I've ever come across. Eagerly awaiting the 2nd edition!
+ Vincent + 50 €
+ Iain Bryson + 40 €
+ Adam Hyland + 50 $
+ Amanda Brown + 50 $
+ Anonymous + 50 $
+ Anonymous + 50 $
+ Anonymous + 50 $
+ Anonymous + 50 $
+ Anonymous + 50 $
+ Mato Ilic + 50 $This is one of the best JavaScript books around. Would love to see an updated version-
+ Peter deHaan + 50 $
+ Pierce Lopez + 50 $
+ Trevor Blackwell + 50 $
+ Zach W + 50 $I loved the first edition. Looking forward to another :)
+ Anonymous + 40 $
+ Henry Allen-Tilford + 40 $
+ Kory Mathis + 40 $
+ Tom Kelley + 40 $Keep up the good work. I brag about your current book, and I can't wait to see the new one.
+ Anonymous + 30 €
+ Anonymous + 30 €
+ Anonymous + 30 €
+ Daniel Harrington + 30 €
+ Onno Schwanen + 30 €
+ Anonymous + 25 €
+ Chris Smith + 25 €Marijn's CodeMirror project is the foundation upon which I've been realizing my own vision of a better way to learn programming, and Eloquent JavaScript was truly a pioneering work. I feel it is extremely important to provide Marijn with the encouragement and financial support he needs.
+ Eric Allam + 25 €
+ fronx + 25 €
+ Octavian Nita + 25 €One of the best books on programming ever, if you ask me; even if you know your way around programming, it is still worth it to read even the beginner chapters. + +Can hardly wait to buy the new edition! Best of luck, man!
+ Orde Saunders + 25 €
+ Rob Crowther + 25 €
+ Tom + 25 €More of this sort of thing.
+ Alan Fothergill + 30 $
+ Ben Handzo + 30 $Eloquent JS was my intro to programming as a way of thinking. I've gone back to it a bunch of times and learned new things every time. A beautiful updated version is worth every penyn.
+ Carlos Iriarte + 30 $Your first book is amazing. This is my humble way of saying thanks for creating amazing things such as CodeMirror and the first edition of this book.
+ Dav Clark + 30 $
+ Isak Dalström + 30 $
+ Richard Yeh + 30 $
+ @fwg + 20 €Eloquent Javascript has been an invaluable resource both to my learning and teaching of JS over the years.
+ Anonymous + 20 €The only useful JavaScript Tutorial ;-)
+ Anonymous + 20 €
+ Anonymous + 20 €
+ Anonymous + 20 €The first edition is a wonderful book, something I recommend to my employees as a must-read. Thanks for all the hard work!
+ Anonymous + 20 €
+ Anonymous + 20 €
+ Anonymous + 20 €
+ Anonymous + 20 €
+ Anonymous + 20 €
+ Bundyo + 20 €
+ d4kris + 20 €
+ Florian Heinisch + 20 €
+ Frank Taillandier + 20 €The Web need this. Every good JS developer I have met said he had read your book. An updated edition will continue help everyone writing good Javascript.
+ Jason Smith + 20 €My most preferred task "author's discretion"; but a Node.js section will be great. Remember, the API documentation indicates the permanence ("stability") of the API.
+ Jonas Bredenfeldt + 20 €
+ Karl Westin + 20 €It's the #1 i recommend to people wanting to learn JS
+ larz + 20 €
+ Martin S. + 20 €
+ Massimiliano Filacchioni + 20 €
+ Michał Laskus + 20 €
+ nerdess + 20 €
+ Oliver Anan + 20 €
+ Owen Densmore + 20 €My preferred task really is "all"!
+ Panos Astithas + 20 €
+ Sonny Piers + 20 €Eloquent JavaScript is one of the best resource to learn programming and JavaScript. I'd love to read a new edition.
+ Tiago Rodrigues + 20 €I recommend Eloquent JavaScript to everyone wanting to learn JS as the first book they look at and people usually love it. Hopefully a new edition will get even more people to learn JS!
+ Tom Alterman + 20 €
+ Victor Hooi + 20 €Loved the first book. + +Go hard mate! =)
+ Vit Brunner + 20 €Whenever someone tells me they'd like to learn programming, I point them to Eloquent Javascript!
+ Adrian Schaedle + 25 $Eloquent JS is not only the greatest introduction to Javascript that exists, it's one of the most illuminating books about how to reason about your programs and how to start thinking functionally. I think it's the book responsible for most developers making the jump from curious HTML prodders to full-stop intelligent programmers.
+ Andrew de Andrade + 25 $Awesome work. I recommend your book to lots of people who are new to programming. I think it is easily one of the best books out there. Thank you and thank you on behalf of everyone I've recommended it to. + +Also, thank you for Tern.js, CodeMirror and the Haskell code I have perused. I love it when people like you Brian Lonsdorf, Substack, Fogus and others help build bridges between JavaScript and functional programming programming languages like Haskell and Clojure, and grow the body of work in JavaScript code that use functional paradigms and idioms. + +Best, +Andrew +engineer @ famo.us
+ Anonymous + 25 $
+ Anonymous + 25 $
+ Anonymous + 25 $
+ Anonymous + 25 $
+ Anonymous + 25 $
+ Brian Kung + 25 $
+ Dina Lamdany + 25 $
+ G. Jason Head + 25 $
+ Greg Lind + 25 $The original is such a wonderful resource. Thank you very much. Please take $20 for the project and spend the other $5 on a pint of something refreshing. You've earned it again and again.
+ Hartley + 25 $Despite its age, this book provides a wonderful introduction into Javascript. Would love to see it updated for a new generation of developers.
+ John Goodleaf + 25 $
+ Kashif Jabbar + 25 $EJS is by far the best JavaScript book I have come across. Really looking forward to the 2nd Edition.
+ Les Dougherty + 25 $I'm just past being a beginner to JavaScript, but do have older experience in Perl. I think this is a great project. Best wishes for success. I'm retired now, but will try to donate again later.
+ Matt Sahr + 25 $
+ Paul C + 25 $I've been exposed to Javascript for 10 years and thought I had a reasonable grasp of things but today your book confused me. This is a good thing because I realised there is an awful lot I don't know and am working through your book to start again with a better foundation.
+ Sean + 25 $
+ Thorin Messer + 25 $
+ Tom Gamble + 25 $
+ Trae + 25 $
+ Udi Wertheimer + 25 $
+ Yojan Shrestha + 25 $This is how education should happen to begin with. Also, love this book. Keep doing what you do.
+ Yusuf Abdi + 25 $
+ Anonymous + 18 €
+ Anonymous + 20 $When I use eloquent javascript people hardly notice my disfigurement.
+ Aaron Ackerman + 20 $
+ Aaron Moodie + 20 $
+ Alex Gill + 20 $
+ Alvin Ashcraft + 20 $Thanks! Love the first edition.
+ Andrey Fedorov + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $I'm going through the first edition right now. It's excellent. Can't wait for the second edition!
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $Thanks for your great works! +The practice-based introduction is what distinguishes this book from other JS books, so I think more practical chapters would be great.
+ Ariel Kirkwood + 20 $
+ Chris Kottom + 20 $
+ Cody Lindley + 20 $
+ David Sprague + 20 $
+ Dethe Elza + 20 $Hi Marijn, I can't tell you how many people I have steered to your book and online version. I still think it's the best introduction to JavaScript around, and am enthusiastic about it becoming even better.
+ Drew Bell + 20 $
+ Eric Baer + 20 $Reading this book helped me understand functional programming for the first time!
+ Erik Swan + 20 $
+ Gary Lucas + 20 $
+ Griffin Alberti + 20 $
+ Jeremy Schlatter + 20 $
+ Jerzy Batalinski + 20 $I love the work you have done, I continue to try to improve my knowledge of objects, loops as I rushed through the book to grasp the concepts as fast as possible. Now working with backbone.js I often refer to the Javascript fundamentals in your book. Plus the Aunt with Cats email is epic. Great work, continue doing awesome stuff :)
+ Jesus Alvarez + 20 $The first edition was amazing. Can't wait for the second edition!
+ John DeHope + 20 $I appreciate your work, Marijn, and the way you're funding and licensing it.
+ John M + 20 $Javascript is the world's most popular language -- I'd love to write it as gracefully and minimally as possible -- thanks!
+ Jorge L Garcia + 20 $Awesome book, awesome legacy, let's keep it alive.
+ Joseph Clay + 20 $
+ Justin Lowery + 20 $
+ Klemen Slavic + 20 $This guy.
+ Kyle Alexander Thompson + 20 $
+ LadyMartel + 20 $
+ Mauricio Mercado + 20 $
+ Millard Ellingsworth + 20 $I paid about $20 for the paper version and I'm happy to kick in another $20 to see an updated, generally available version. It's excellent work that I have referred others to multiple times.
+ Patrick Taylor + 20 $
+ Patrick Teglia + 20 $Loved the first book, really hope you make your goal!
+ Pierre-Francois Laquerre + 20 $
+ pmThompson + 20 $I initially wanted to mark task:None, but I was worried that un-targeted funds could become un-directed work. Besides, *EVERYONE* needs to hire an artist ;-) +
+ RicheTheBuddha + 20 $Thank you for working to update this excellent work and putting it out there in a free way. Thank you for the first edition!
+ Sanket Patel + 20 $I am always in a hurry and first time when I read this book very quick I felt kind of satisfaction and enlightenment with this book more than with any other book...I knew that I had to read this book again and I did...The title Eloquent is not an overstatement...Really the modern introduction on javascript...The definite way to go for writing a book...not only best javascript beginner book but best programming book for any programmer wanna be...Thanks for this book!
+ Scott Carpenter + 20 $Thank you!
+ Scott Lesser + 20 $
+ Sean Diamond + 20 $
+ Sergii + 20 $
+ TehShrike + 20 $Chapter 6 helped me wrap my head around functional programming. I am in your debt!
+ Timur + 20 $
+ Tom Keeler + 20 $
+ Tyler Cipriani + 20 $
+ Vish Jiawon + 20 $
+ Zev Averbach + 20 $
+ Alex Gyoshev + 15 €Rock on!
+ Alun Davey + 15 €
+ Anonymous + 15 €
+ Anonymous + 15 €
+ Anonymous + 15 €
+ Anonymous + 15 €
+ Anonymous + 15 €
+ Anonymous + 15 €It would be incredibly great to be able to not only read the book, but use the book sandbox on iPhone. +With v1 AFAIK it was not practical and/or not working. +An offline manifest would also be great for smartphones, in particular the (upcoming) Firefox OS based ones.
+ Anonymous + 15 €
+ Jérémy Ozog + 15 €
+ Karl Inglis + 15 €
+ manichord + 15 €
+ Pedro Figueiredo + 15 €
+ Peter Zuidhoek + 15 €
+ Tchesko + 15 €Great job. It's better than all the other books i have bought far away... Signed : A french reader.
+ Victor Cazacov + 15 €
+ Stefan Bauckmeier + 13 €
+ Ben Frank Lodge + 12 €Have fun writing the new book. +I look forward to the results!
+ @partyfists + 15 $The book that taught me how Javascript can be beautiful and well written needs this update and I am proud to support it.
+ Anonymous + 15 $I read a large part of your book online. It was awesome, I intended to buy a hardcopy but never did. Hope this compensates you adequately and get you running on the second edition. Looking forward to it. +
+ Anonymous + 15 $
+ Anonymous + 15 $
+ Anonymous + 15 $Like the first book, so would use the second book. Good luck on the rewrite!
+ Anthony Yu + 15 $Thanks bro. You're teaching this baby bird how to take flight. I guess, "Thanks Daddy!" would be more appropriate!
+ Jason Laster + 15 $thanks
+ Jim Hart + 15 $
+ Karl J. Smith + 15 $
+ Miroki + 15 $
+ Patrick + 15 $
+ Ramkumar + 15 $
+ Shawn Searcy + 15 $
+ Sheldon + 15 $
+ Alexander Dobbert + 10 €
+ Amr Malik + 10 €
+ Andrew Ducker + 10 €
+ Andrew Price + 10 €
+ Andrew Whitehouse + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €For beginners chapter 6-8 are where you are losing them, when it comes to functions, recursion, method chaining, oop and so on within a few pages.
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €yay i am so excited
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ bastian + 10 €
+ Bema + 10 €Thanks for doing this excellent work, I read the first edition online and leaned a lot. +I would like to help translating it to Portuguese, I am Brazilian, so if by any chance you are contacted by other Brazilians willing to do the same job please put us in contact so we can build a working group for this task. + +Best wishes, +Bema + +
+ blake johnson + 10 €this is one of the best programming books ever written regardless of language, thanks!
+ Bundyo + 10 €
+ Colm Heaney + 10 €I love this book! Still working my way through the first edition but it has been good enough to convince me to contribute to the 2nd. Great job man!
+ Daniel Beck + 10 €Eloquent Javascript is a really good book. Thanks for the hard work.
+ Dinis Correia + 10 €
+ Fabian Straubinger + 10 €
+ Ferdinand Salis-Samaden + 10 €
+ Garren Smith + 10 €Keep up the great work. I loved the first book.
+ gasper + 10 €yay for giving money to people to do cool stuff!
+ Greg McCarvell + 10 €
+ GZiolo + 10 €Good luck! Waiting for updated version.
+ Jag + 10 €Great work!
+ Jan Tiedemann + 10 €
+ Jiří Prokop + 10 €I hope this book will help to change world! :-)
+ jjjmmmhhh + 10 €
+ Kevin Dangoor + 10 €
+ Lech Rzedzicki + 10 €Hi. + +Thank you for all the work so far, looking forward to 2nd edition.
+ Loucas Papantoniou + 10 €
+ Lucas + 10 €Thank you!
+ Manuel Kiessling + 10 €Eloquent JavaScript is one of the very few books I did not sell before me and my family moved into another city last year. I really love having it.
+ Mark Robson + 10 €
+ Matthew Lancey + 10 €
+ Maurizio Mangione + 10 €
+ Paddy O'Hanlon + 10 €I learned so much from the first book. It broke down many JavaScript walls for me. Really looking forward to the new edition!
+ Patrick Te Tau + 10 €I'd prefer as much of a functional programming lean as possible in a rewrite. But, you know, it's your book ;)
+ Pedro Teixeira + 10 €Eloquent JavaScript has long been my go-to recommendation for people that want to have a good coverage of modern JavaScript programming. I'm really happy that the author is planning a second revision.
+ Piotr Migdał + 10 €A great starting point to learn JavaScript!
+ pixelkritzel + 10 €
+ PurplePilot + 10 €
+ qgi + 10 €
+ Richard + 10 €Great book! I'm a beginner but it helped me understand so much about Javascript and the fundamentals of the language.
+ Rob Campbell + 10 €It's been 6 whole years. JavaScript and the DOM have changed quite a bit since then. This new version should address that.
+ Roland Tanglao + 10 €
+ Rémi Gérard-Marchant + 10 €
+ Stefan Lodders + 10 €
+ Stuart Cuthbertson + 10 €
+ Thomas L. + 10 €Loved the first one. Keep up the good work!
+ Volodymyr Prokopyuk + 10 €
+ whostolemyhat + 10 €Great book, incredibly useful!
+ Juan Carlos Lopez B + 12 $I used your guide to learn JavaScript, and it was great! Thanks alot!
+ Anonymous + 11 $
+ Adam Khorshid + 10 $
+ Al Billings + 10 $
+ Alejandro Garcia + 10 $
+ Andriy G + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $You have written an excellent book on my favourite language... You havema made a difference to my life... I can afford only 10 $.. good luck
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $I used the original Eloquent Javascript to learn JS and I am grateful to it. It's a classic that was made available to everyone. I can't wait to see the next version and am happy to support its continued existence as a goto for learning JS especially as JS becomes more and more ubiquitous.
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $still going through the (very well-done) first version, excited to see the updates!
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $Thank you so much for your work. I've been working with javascript daily for about a year now, and your tutelage, especially in the way of data structures and programming paradigms, has been a huge boon to my abilities! Keep up the terrific work.
+ Anonymous + 10 $Thanks a lot for writing this.
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $Eloquent Javascript served as my intro to JS and it's been with me the whole way. Thanks a bunch.
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Audrey + 10 $Great work with 1st edition. Please keep up with the good work.
+ Brian FitzGerald + 10 $I think the current intro is beautiful, don't change it too much :) Thanks for your efforts!
+ C J Silverio + 10 $
+ Chris Bobek + 10 $
+ Collin Garvey + 10 $
+ Cory Gagliardi + 10 $Eloquent JavaScript is my favorite JS book. I'm looking forward to seeing an update.
+ Cris Noble + 10 $
+ CyberAP + 10 $First edition helped me a lot. Looking forward for a new one.
+ Dan Lourenco + 10 $
+ davewhat + 10 $
+ Don Burks + 10 $The first edition is how I get people excited about JS.
+ Eduardo Nieto + 10 $Your book has been invaluable to me. I recommend it anytime I can, especially to people learning how to program. Thanks a lot!
+ Eric Schiller + 10 $This book taught me Javascript.
+ erutan + 10 $
+ Ivan Shornikov + 10 $Great idea!
+ James + 10 $
+ James Herdman + 10 $
+ Jan + 10 $
+ Jason + 10 $
+ Jeb Schiefer + 10 $
+ John Caruso + 10 $
+ Leandro D'Onofrio + 10 $JavaScript FTW!
+ max borghino + 10 $
+ Mayuresh Kathe + 10 $
+ Megan Taylor + 10 $Eloquent JavaScript got me over a lot of my early hurdles, and is still a resource I return to. I've also recommended it to friends who were looking to learn programming.
+ Michael Allan + 10 $Heck, I'd have paid just to have one of the bugs speak my bubble! *So* much cooler than any Kickstarter bonus. Also, can't wait to see an updated version of this classic :-)
+ Michael Paulukonis + 10 $I loved the original web-version, and have recommended it to colleagues learning JS. + +For the record, I was always impressed with the in-page editor/executor environment. Being able to test ideas in the same location I was reading about kept me from jarring context-shifts. Making the sample code vanilla-js is a good idea, though.
+ mike maxwell + 10 $
+ Mohammed Ameen + 10 $
+ Nick Ketter + 10 $Have been wanting to learn Javascript for a while and something about this seems like a better use of any $ I'd spend on a finished book.
+ Rick Yentzer + 10 $
+ Rob Bartholme + 10 $
+ Robert Brimhall + 10 $
+ Rolf + 10 $I bought your first book and it was great :-)
+ Sachin Palewar + 10 $I took Derek Sivers's advice and started learning Javascript recently with your online book. I am onto 3rd chapter now and already feel that your book is useful not only for novice programmers but for experienced ones as well. + +I am mighty impressed with current version and can only imagine what can you do with a re-written version. All the best. Also your background animation rocks.
+ Sandy + 10 $I love the first edition. Your writing made me rediscover the joy of learning to code.
+ Scott + 10 $
+ Shaun Santa Cruz + 10 $
+ Steve Kinney + 10 $The first edition of the book really helped me wrap my head around JavaScript. I'm looking forward to the second version.
+ Tessa Thornton + 10 $
+ Tony Ching + 10 $Thank you for teaching programming via JavaScript. I look forward to buying the book again.
+ Varun Raj + 10 $Thank Tou Marijn... I would like to express my gratitude for the work you are doing. Great Job.
+ Zachary Freeman + 10 $Can't wait for the new version!
+ 谢彪 + 10 $Love your book and open source works <3 :-)
+ Anonymous + 8 $
+ Anonymous + 8 $
+ Anonymous + 8 $Congratulations for the initiative and the book. I home you get all the money you need for write this second version and make an ePub version too :)
+ Farid Neshat + 8 $
+ Patrick Stapleton + 8 $@gdi2290
+ vobi + 6 €
+ Robbie Edwards + 8 $
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Chris Mear + 5 €
+ Dima Samodurov + 5 €
+ Emilian Losneanu + 5 €
+ Francisco Fernández Castaño + 5 €
+ Harry Moreno + 5 €Loved the original. Consider including jquery, it's a pretty standard requirement these days for a JS programmer.
+ Ian Rose + 5 €Thanks and looking forward to the release!
+ Jacob + 5 €
+ Jan Aagaard + 5 €
+ Lasse + 5 €
+ Mario Estrada + 5 €
+ Peter Janotta + 5 €
+ Ralf Puchert + 5 €
+ Roberto Ferro + 5 €
+ Sergi + 5 €Awesome stuff, make it awesomer!
+ Slavo Ingilizov + 5 €Rock on dude. You're doing an awesome thing.
+ Staale Nataas + 5 €
+ Thomas Herzog + 5 €
+ Wojtek + 5 €
+ Wolfgang + 5 €
+ Anonymous + 4 €
+ Anonymous + 5 $
+ Ali Ukani + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Ben Boarder + 5 $The first edition of 'Eloquent JavaScript' helped me at the beginning of my development career so much, that I hope for the next generation of JavaScripters to gain the same solid skills.
+ Christopher Lamm + 5 $
+ Colin Gourlay + 5 $The original is a classic. Excited to share the new edition with new JS devs!
+ curtis gagliardi + 5 $
+ CYRIL DAVID + 5 $
+ Dhruv Chandna + 5 $
+ Eugene Bulkin + 5 $
+ Garth Johnson + 5 $
+ Jack Crish + 5 $Thank you for the first book. Glad to see you're working on an update. Good luck with your progress.
+ Loren Saele + 5 $Enjoyed the first edition and looking forward to an updated second edition.
+ Lucas D + 5 $Amazing contribution to the javascript community.
+ Luiz Americo Pereira Camara + 5 $
+ Paul Jaworski + 5 $Probably the best introduction to Javascript I have encountered!
+ Peter M + 5 $
+ Ron Hamenahem + 5 $
+ Stanislav + 5 $EloquentJS rocks !
+ Ted Young + 5 $
+ Thomas + 5 $Can't wait!
+ tucaz + 5 $
+ wannianchuan + 5 $Like your book, look forward to the second edition.
+ Wyatt + 5 $Yours is the best JS resource available. Keep up the good work.
+ Yorgos Kopanias + 5 $I am only half-way the 1st edition and I think you have done a great job! Thank you!
+ Anonymous + 4 $
+ Anonymous + 3 €
+ Guido Corradi + 3 €Good job! : )
+ Radek P + 3 €
+ Anthony Mastrean + 3 $
+ Anonymous + 2 €
+ Anonymous + 2 €
+ Anonymous + 2 $
+ Toby + 2 $
+ Tori Hamblin + 2 $
+ Anonymous + 1 €
+ Anonymous + 1 €
+ bzlm + 1 €hi guys
+ Krzysztof B. Wicher + 1 €
+ robalarcon + 1 €I have never read the first edition, but I have used a lot of your software and I'll looking forward for this edition
+ Anonymous + 1 $
+ Farid Neshat + 1 $
+ s5s5 + 1 $good job
+ Tapan Shah + 1 $Good luck & Thank you
+
+ diff --git a/docs/backers3.html b/docs/backers3.html new file mode 100644 index 000000000..2fd7a2a52 --- /dev/null +++ b/docs/backers3.html @@ -0,0 +1,802 @@ + + + + + Eloquent JavaScript :: 3rd Edition Backers + + + + + +
+

List of Backers
Eloquent JavaScript, 3rd Edition

+ +

These are the wonderful people and organizations who have +contributed towards making the third edition +of Eloquent JavaScript happen.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1000 €
1000 €
Belma Gaukrodger200 €
Christopher Bejnar150 €
Dennis van Leeuwen150 €
Tim Caswell111 €
Anonymous100 €
John W. Bruce100 €
Liam Newman100 €
Morgan Roderick100 €
Peter-Paul van Gemerden100 €
Christopher Astfalk75 €
Lord Omlette75 €
Edward Cheng66 €
t.m.k.65 €
Charles McMackin60 €
Alexander Bartolo50 €
Allie Jones50 €
Anonymous50 €×5
Anthony Lupinetti50 €
Ash Kumar50 €
Blaine Cook50 €
Brandon Yanofsky50 €
Brian Jones50 €
Chandler50 €
Chris Wright50 €
Chrissy Gonzalez50 €
Connie Kephart50 €
Daisuke Horie50 €
David Sommers50 €
Dwayne Gardner50 €
Eleanor Weigert50 €
Florencia Herra Vega50 €
G C Glaser50 €
Geoffrey Kidd50 €
jake.ro50 €
JS Cheerleader50 €
Jaime Silvela50 €
Jarren Bird50 €
Jeremy Whitbred50 €
Jiaxing Wang50 €
Joe Paris50 €
Johan Gunawan50 €
José de Leon50 €
Juri Leino50 €
Kei Ito50 €
Lewis Liu50 €
Louis Cheung50 €
Mathieu Jouhet50 €
Max Zhuravsky50 €
Mirco Strässle50 €
Nikita Skrebets50 €
Peter Persson50 €
Phil Aylesworth50 €
Pitch Arunsuwannakorn50 €
Raphael LANG50 €
Richard Curzon50 €
Scott Sauyet50 €
Stephen Band50 €
Techit U.50 €
Tim Scheer50 €
Tom MacWright50 €
Tommy Williams50 €
Vincent Sisk50 €
aravind mohan50 €
jsm50 €
Martin Bohgard45 €
Anonymous40 €
Don McCurdy40 €
Steve Albers33 €
Anonymous30 €
Charlie Orford30 €
Paul Jacobson30 €
Steven Mitts26 €
Aaron Williams25 €
Adi Dahiya25 €
Alex Melville25 €
Alex Moldovan25 €
Alexis La Porte25 €
Alfonso Higuera Gamboa25 €
Ali Smith25 €
Alper25 €
Amberley Romo25 €
Anonymous25 €×23
August G Dombrow25 €
Bas Hintemann25 €
Bastian Albers25 €
Biniam Bekele25 €
Bradley Ayers25 €
Bálint Kléri25 €
C.H.A.D.25 €
Camilo25 €
Charles Stanhope25 €
Chris Aves25 €
Christian Simonsen25 €
Christophe Pouliot25 €
Colby25 €
Constantinescu Nicolaie25 €
Coy Sanders25 €
Craig Maloney25 €
Cristina Suarez Corzo25 €
Cyril Pierron25 €
D C Jackson25 €
Daniel Kocoj25 €
Darren Torpey25 €
Dave King25 €
David Bremner25 €
David Lukeš25 €
David Owens25 €
David Shirey25 €
Dennis Martinez25 €
Donald Craig25 €
Dor Tzur25 €
Drew Bollinger25 €
Dumoulin Pierre25 €
ECAD Labs Inc.25 €
Eamonn Bell25 €
Elena Santana25 €
Elijah Dorman25 €
Emil Redzic25 €
Eric Haseltine25 €
Ernest D Weems25 €
Ethan Sherbondy25 €
Ferdinand Salis-Samaden25 €
Flaki25 €
Fran Iglesias25 €
Francesco Agosti25 €
Frank Siebenlist25 €
Frederik Eichler25 €
Henrik Saksela25 €
Ignacy25 €
James Taylor25 €
Jeff Whitfield25 €
Jerry Mao25 €
Jesse Printz25 €
Johan25 €
John Meredith25 €
Jonathan So25 €
Kashyap25 €
Kenji Rikitake25 €
Kenyasoweta Bowman25 €
Kovács László25 €
LIONEL E RAMOS25 €
Lev Izraelit25 €
Lev Izraelit25 €
Luciano Mammino25 €
Maarten25 €
Marc Farra25 €
Marcus Weiner25 €
Marko Bilal25 €
Marshall25 €
Mattie Kenny25 €
Michael Saxton25 €
Michael Terry25 €
Mikko Tolmunen25 €
Muhammad Abdusamad25 €
Niels Gregersen25 €
Niles Turner25 €
Nikolaus Klumpp25 €
Octavian Dobrescu25 €
Olivier Forget25 €
Omari Rose25 €
Paul Haddad25 €
Pedro Tavares25 €
Pelle Lundgren25 €
Peter Weber25 €
Phillip Bruk25 €
Pietro Menna25 €
Rocio Chongtay25 €
Ron Male25 €
Rosario Fusca25 €
Roy Grubb25 €
Ryan Paul25 €
SHI Wenhao25 €
Safa Yasin Yıldırım25 €
Samuel Durkin25 €
Sathya Gunasekaran25 €
Stephan Seidt25 €
Steve Ingram25 €
Steven Landman25 €
Stuart Kennedy25 €
SunnyByte25 €
Tim Richards25 €
Tyler Cipriani25 €
Will Schmidt25 €
William Harris25 €
YOU,ZONGYAN25 €
Yuya Saito25 €
Zhivko Siderov25 €
jenn schiffer25 €
theo bousquet25 €
www.actioncy.co.uk25 €
Anonymous< 25×107
+
+ diff --git a/docs/code b/docs/code new file mode 120000 index 000000000..2edff2610 --- /dev/null +++ b/docs/code @@ -0,0 +1 @@ +../code \ No newline at end of file diff --git a/docs/css/ejs.css b/docs/css/ejs.css new file mode 100644 index 000000000..7b3dfca8e --- /dev/null +++ b/docs/css/ejs.css @@ -0,0 +1,461 @@ +@font-face { + font-family: 'Cinzel'; + font-style: normal; + font-weight: 700; + src: local('Cinzel-Bold'), url(../font/cinzel_bold.woff) format('woff'); +} + +@font-face { + font-family: 'PT Mono'; + font-style: normal; + font-weight: 400; + src: local('PT Mono'), local('PTMono-Regular'), url(../font/pt_mono.woff) format('woff'); +} + +html, body { + padding: 0; + margin: 0; +} + +body { + font-family: Georgia, 'Nimbus Roman No9 L', 'Century Schoolbook L', serif; + font-size: 20px; + line-height: 1.45; + color: black; + background: white; +} + +article { + margin: 0 auto; + max-width: 35em; + padding: 2em 1em 5em; + position: relative; + overflow-wrap: break-word; +} + +nav { + display: block; + height: 0; + text-align: right; +} + +nav a { + font-size: 80%; + color: #aaa !important; + text-decoration: none !important; +} + +a.subtlelink { + color: black !important; + text-decoration: none !important; +} + +pre { + padding: 5px 0 5px 15px; + line-height: 1.35; + margin: 1rem 0; + max-width: 100%; + overflow-x: auto; +} + +pre[data-language=javascript] { + cursor: pointer; +} + +p:hover a.p_ident:after, pre:hover a.c_ident:after, h2:hover a.h_ident:after, h3:hover a.i_ident:after { + content: "¶"; + font-family: 'Cinzel', Georgia, serif; + color: #888; + font-size: 17px; + position: absolute; + right: -10px; +} + +@media screen and (max-width: 800px) { + p:hover a.p_ident:after, pre:hover a.c_ident:after, h2:hover a.h_ident:after, h3:hover a.i_ident:after { + right: 5px; + } + + blockquote p:hover a.p_ident:after { + right: -15px; + } +} + + +code, pre, .CodeMirror { + font-size: 18px; + font-family: 'PT Mono', monospace; +} + +code { + padding: 0 2px; +} + +h1, h2, h3 { + font-family: 'Cinzel', Georgia, serif; + font-weight: 700; + margin: 1rem 0; + letter-spacing: 2px; +} + +h1 { + font-size: 130%; +} +h2 { + font-size: 115%; +} +h3 { + font-size: 100%; +} + +pre.cm-s-default, p, h2, h3 { + margin-right: -30px; + padding-right: 30px; +} + +@media screen and (max-width: 800px) { + pre.cm-s-default, p, h2, h3 { + margin-right: 0; + padding-right: 0; + } +} + +a, a:visited, a:active { + text-decoration: none; + color: #467; +} + +a:hover { + text-decoration: underline; +} + +ol { + margin: 1em 0; + padding: 0; + counter-reset: li; +} + +ol li { + margin: 0 0 0 40px; + padding: 0; + list-style: none; + position: relative; +} + +ol li:before { + content: counter(li) "."; + counter-increment: li; + position: absolute; + width: 2em; + text-align: right; + left: -2.5em; top: 1px; + font-size: 90%; +} + +ol li p { + margin: 0; +} + +.chap_num { + font-size: 60%; + color: #aaa; + margin-top: -.7em; + display: block; +} + +blockquote { + margin: 0 0 0 3em; + padding: 0; + position: relative; + font-size: 85%; +} + +blockquote p { + color: #333; +} + +blockquote:before { + content: '“'; + position: absolute; + left: -.5em; +} + +blockquote p:last-of-type:after { + content: '”'; +} + +blockquote footer { + position: relative; + margin-left: 1em; +} + +p + footer { + margin-top: -.5em; +} + +blockquote footer cite { + font-style: italic; +} + +blockquote footer:before { + content: '—'; + position: absolute; + left: -1em; +} + +.editor-wrap { + margin: 1rem 0; + position: relative; + -moz-transition: margin-left .5s ease-out, margin-right .5s ease-out; + -webkit-transition: margin-left .5s ease-out, margin-right .5s ease-out; + -o-transition: margin-left .5s ease-out, margin-right .5s ease-out; + transition: margin-left .5s ease-out, margin-right .5s ease-out; + border-bottom: 1px solid #4ab; +} + +.sandbox-output { + border-top: 1px solid #4ab; + padding: 4px 0 4px 10px; + white-space: pre; + max-height: 25em; + overflow: auto; +} + +.sandbox-output:empty { + display: none; +} + +.editor-wrap iframe { + display: block; + border: 1px dotted #4ab; + border-top: 1px solid #4ab; + border-bottom-width: 0; + padding: 0; margin: 0; + width: 100%; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.sandbox-output pre { + margin: 0; + padding: 0; + white-space: pre-wrap; +} + +.sandbox-output-error { color: red; } +.sandbox-output-warn { color: orange; } + +.sandbox-output-etc { + color: #1a1; + background: #dfd; + cursor: pointer; + border-radius: 5px; + padding: 0 1px; +} + +.sandbox-output-prop { + color: #444; +} + +.sandbox-output-etc-block { + display: inline-block; + vertical-align: top; +} + +.sandbox-output-etc-block table { + border-collapse: collapse; +} +.sandbox-output-etc-block table td { + vertical-align: top; + white-space: pre-wrap; + font-family: 'PT Mono', monospace; +} +.sandbox-output-etc-block table td:first-child { + text-align: right; +} + +.sandbox-menu { + position: absolute; + z-index: 19; + right: -13px; top: -1px; + cursor: pointer; + font-size: 80%; + overflow: hidden; + width: 10px; + border-top: 2px solid #4ab; + height: 2px; + border-bottom: 6px double #4ab; +} + +.sandbox-open-menu { + font-family: tahoma, arial, sans-serif; + position: absolute; + background: white; + border: 1px solid #aaa; + box-shadow: 2px 2px 10px rgba(0, 0, 0, .2); + padding: 0; + font-size: 75%; + color: black; + line-height: 1.3; + right: -9px; top: 5px; + z-index: 20; +} + +.sandbox-open-menu div { + cursor: pointer; + padding: 5px 10px; +} + +.sandbox-open-menu div:hover { + background: #bdd; +} + +/* Toned-down CodeMirror style */ + +.cm-s-default .cm-keyword, .sandbox-output-null, .sandbox-output-fun {color: #506;} +.cm-s-default .cm-atom, .sandbox-output-bool {color: #106;} +.cm-s-default .cm-number, .sandbox-output-number {color: #042;} +.cm-s-default .cm-def {color: #009;} +.cm-s-default .cm-variable-2, .cm-s-default .cm-attribute {color: #027;} +.cm-s-default .cm-variable-3 {color: #072;} +.cm-s-default .cm-comment {color: #740;} +.cm-s-default .cm-string, .sandbox-output-string {color: #700;} +.cm-s-default .cm-string-2 {color: #740;} +.cm-s-default .cm-tag, .sandbox-output-symbol {color: #170;} + +.CodeMirror { + height: auto; + line-height: 1.35; + border-top: 1px solid #4ab; + overflow-wrap: normal; +} +.CodeMirror-scroll { + max-height: 700px; +} +.CodeMirror pre { + padding: 0 4px 0 10px; +} +.CodeMirror-gutters { + border: none; + background: white; +} +.CodeMirror-linenumber { + padding: .5em 3px 0 0; + min-width: 12px; + color: #4ab; + font-size: 60%; +} + +.sandboxhint { + position: absolute; + right: -15px; + font-family: tahoma, arial, sans-serif; + font-size: 70%; + padding: 4px 8px; + background: rgb(220, 220, 220); + color: white; + border-radius: 5px; +} + +@media screen and (max-width: 800px) { + .sandboxhint { + right: 5px; + } +} + +.sandboxhint:before { + position: absolute; + width: 0; height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 12px solid rgb(220, 220, 220); + top: 6px; + left: -11px; + content: ''; +} + + +figure { + max-width: 640px; + margin: 0 30px; +} + +figure.chapter { + text-align: center; + margin: 3em 0 2em; +} + +figure.chapter img { + max-width: 75%; +} + +figure.framed img { + border-radius: 50%; + border: 4px double #666; +} + +figure.square-framed img { + border-radius: 30px; + border: 4px double #666; +} + +span.keyname { font-variant: small-caps } + +@media screen and (max-width: 500px) { + figure { + margin: 0; + } +} + +figure img { + max-width: 100%; +} + +div.solution:before { + content: "» Display hints..."; +} + +div.solution { + color: #156; + cursor: pointer; +} + +div.solution-text { + display: none; +} + +div.solution.open:before { + content: ""; +} + +div.solution.open { + cursor: default; +} + +div.solution.open div.solution-text { + display: block; +} + +td { + vertical-align: top; +} + +td + td { + padding-left: 1em; +} + +table { + margin-left: 15px; +} + +sub, sup { + line-height: 1; +} + +sub { + font-size: 60%; +} + +sup { + font-size: 70%; +} diff --git a/docs/css/game.css b/docs/css/game.css new file mode 100644 index 000000000..bb777788a --- /dev/null +++ b/docs/css/game.css @@ -0,0 +1,24 @@ +.background { background: rgb(52, 166, 251); + table-layout: fixed; + border-spacing: 0; } +.background td { padding: 0; } +.lava { background: rgb(255, 100, 100); } +.wall { background: white; } + +.actor { position: absolute; } +.coin { background: rgb(241, 229, 89); } +.player { background: rgb(64, 64, 64); } + +.game { + overflow: hidden; + max-width: 600px; + max-height: 450px; + position: relative; +} + +.lost .player { + background: rgb(160, 64, 64); +} +.won .player { + box-shadow: -4px -7px 8px white, 4px -7px 8px white; +} diff --git a/docs/css/paint.css b/docs/css/paint.css new file mode 100644 index 000000000..159faddf4 --- /dev/null +++ b/docs/css/paint.css @@ -0,0 +1,13 @@ +.picturepanel { + width: -webkit-fit-content; + width: -moz-fit-content; + width: -ms-fit-content; + width: fit-content; + max-width: 500px; + max-height: 300px; + border: 2px solid silver; + overflow: auto; + position: relative; +} +.picturepanel canvas { display: block; } +.toolbar > * { margin-right: 5px; } diff --git a/docs/empty.html b/docs/empty.html new file mode 100644 index 000000000..c2a57ea02 --- /dev/null +++ b/docs/empty.html @@ -0,0 +1 @@ + diff --git a/docs/errata.html b/docs/errata.html new file mode 100644 index 000000000..6bb107bef --- /dev/null +++ b/docs/errata.html @@ -0,0 +1,98 @@ + + + + + Eloquent JavaScript :: Errata + + + + + +
+ +

Errata
Eloquent JavaScript, 3rd Edition

+ +

These are the known mistakes in the third edition +of the book. For errata in the first edition, +see this +page. For the second edition, +see this +page. To report a problem that is not listed +here, send me an email.

+ +

Issues whose page number is followed by an ordinal number are only +present up to the print denoted by that number. I.e. those followed by +“1st” were fixed in the second print.

+ +

Chapter 2

+ +

Page 34 (1st) Updating Bindings Succintly: Where it +says counter- it should be counter--.

+ +

Chapter 5

+ +

Page 91 Composability: Due to an initial +mistake in the script data set, the results of the computations on +this page differ from the ones with the current, corrected data. The +average year for living scripts should be 1165, the average for +non-living scripts should be 204.

+ +

Chapter 6

+ +

Page 111 (2nd) Inheritance: In the second +paragraph below the example code, instead of “content +method”, the text should say “element function”. + +

Chapter 8

+ +

Page 134 (2nd) Error Propagation: In the third +paragraph of the section, a function promptInteger is +referred to. The function is actually +called promptNumber, and the word “whole” should be +dropped from the sentence (it accepts non-whole numbers, too).

+ +

Chapter 10

+ +

Page 168 (1st) Modules as Building Blocks: In “each +needs it own private scope“, it should say “its own private +scope“.

+ +

Chapter 14

+ +

Page 234 (2nd) Creating Nodes: In the +code, “edition” is misspelled as “editon”.

+ +

Chapter 15

+ +

Page 258 Load Event: The description of +the beforeunload claims that you just need to return a +string from your event handler. For handlers registered +with addEventListener you, in fact, need to +call preventDefault and set a returnValue +property to get the warn-on-leave behavior.

+ +

Chapter 16

+ +

Page 285 (2nd) Pausing the Game: The text +refers to the arrow binding, where it should +say arrowKeys.

+ +

Chapter 20

+ +

Page 369 (1st) Directory +Creation: MKCOL stands for “make collection”, not “make +column” as the book claims.

+ +

Chapter 21

+ +

Page 373 HTTP Interface: There is a +superfluous closing brace at the end of the example JSON snippet.

+ +

Exercise Hints

+ +

Page 414 A Modular Robot: +The dijkstrajs package name is misspelled +as dijkstajs. + +

diff --git a/docs/example/bert.json b/docs/example/bert.json new file mode 100644 index 000000000..c943c0942 --- /dev/null +++ b/docs/example/bert.json @@ -0,0 +1,4 @@ +{ + "name": "Bert", + "spouse": "example/suzie.json" +} diff --git a/docs/example/data.txt b/docs/example/data.txt new file mode 100644 index 000000000..2a5a69639 --- /dev/null +++ b/docs/example/data.txt @@ -0,0 +1 @@ +This is the content of data.txt diff --git a/docs/example/fruit.json b/docs/example/fruit.json new file mode 100644 index 000000000..b54ea36b7 --- /dev/null +++ b/docs/example/fruit.json @@ -0,0 +1,3 @@ +{"banana": "yellow", + "lemon": "yellow", + "cherry": "red"} diff --git a/docs/example/fruit.xml b/docs/example/fruit.xml new file mode 100644 index 000000000..d932c647a --- /dev/null +++ b/docs/example/fruit.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/example/message.html b/docs/example/message.html new file mode 100644 index 000000000..cf92826f7 --- /dev/null +++ b/docs/example/message.html @@ -0,0 +1,35 @@ + +Example form target + + + + + + +

(Note that this page is just an illustration. No actual message was delivered anywhere.)

+ + + + diff --git a/docs/example/muriel.json b/docs/example/muriel.json new file mode 100644 index 000000000..921f40d45 --- /dev/null +++ b/docs/example/muriel.json @@ -0,0 +1,3 @@ +{ + "name": "Muriel" +} diff --git a/docs/example/submit.html b/docs/example/submit.html new file mode 100644 index 000000000..6607f2275 --- /dev/null +++ b/docs/example/submit.html @@ -0,0 +1,22 @@ + +Example form target + + + +

You submitted...

+ +

+
+
+
+
diff --git a/docs/example/suzie.json b/docs/example/suzie.json
new file mode 100644
index 000000000..1e47fac94
--- /dev/null
+++ b/docs/example/suzie.json
@@ -0,0 +1,5 @@
+{
+  "name": "Suzie",
+  "spouse": "example/bert.json",
+  "mother": "example/muriel.json"
+}
diff --git a/docs/favicon.ico b/docs/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..382b706926693fa007bfef3425deec7a3e2f265f
GIT binary patch
literal 1406
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
ioKjy@tG?8}^JEySv6`q>eW(rXdrN%-I
+
+  
+  
+  Eloquent JavaScript
+  
+  
+
+
+
+
+ +

+ + Cover image + +

+ +

Eloquent JavaScript
3rd edition

+ +

This is a book about JavaScript, programming, and the wonders of + the digital. You can read it online here, or get your own + paperback + copy.

+ +

Written by Marijn Haverbeke.

+ +
+

Licensed under + a Creative + Commons attribution-noncommercial license. All code in this book + may also be considered licensed under + an MIT license.

+ +

Illustrations by various artists: Cover and chapter illustrations + by Madalina Tantareanu. + Pixel art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular + expression diagrams in Chapter 9 generated + with regexper.com by Jeff + Avallone. Village photograph in Chapter 11 by Fabrice Creuzot. Game + concept for Chapter 15 by Thomas + Palef.

+ +

The third edition was made possible + by 325 financial backers, most + notably and . The second edition + was supported by 454 backers, with + significant contributions + from , , + and .

+
+ +
+ + + + +
diff --git a/docs/js/.tern-project b/docs/js/.tern-project new file mode 100644 index 000000000..3a218cbc1 --- /dev/null +++ b/docs/js/.tern-project @@ -0,0 +1,3 @@ +{ + "libs": ["browser"] +} \ No newline at end of file diff --git a/docs/js/acorn_codemirror.js b/docs/js/acorn_codemirror.js new file mode 100644 index 000000000..729457568 --- /dev/null +++ b/docs/js/acorn_codemirror.js @@ -0,0 +1,11 @@ +(function(e,t){typeof exports==="object"&&typeof module!=="undefined"?module.exports=t():typeof define==="function"&&define.amd?define(t):e.CodeMirror=t()})(this,function(){"use strict";var e=navigator.userAgent;var t=navigator.platform;var r=/gecko\/\d/i.test(e);var i=/MSIE \d/.test(e);var n=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e);var a=/Edge\/(\d+)/.exec(e);var s=i||n||a;var o=s&&(i?document.documentMode||6:+(a||n)[1]);var l=!a&&/WebKit\//.test(e);var u=l&&/Qt\/\d+\.\d+/.test(e);var c=!a&&/Chrome\//.test(e);var f=/Opera\//.test(e);var h=/Apple Computer/.test(navigator.vendor);var p=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e);var d=/PhantomJS/.test(e);var m=!a&&/AppleWebKit/.test(e)&&/Mobile\/\w+/.test(e);var v=/Android/.test(e);var g=m||v||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(e);var y=m||/Mac/.test(t);var x=/\bCrOS\b/.test(e);var b=/win/i.test(t);var w=f&&e.match(/Version\/(\d*\.\d*)/);if(w){w=Number(w[1])}if(w&&w>=15){f=false;l=true}var k=y&&(u||f&&(w==null||w<12.11));var C=r||s&&o>=9;function S(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}var L=function(e,t){var r=e.className;var i=S(t).exec(r);if(i){var n=r.slice(i.index+i[0].length);e.className=r.slice(0,i.index)+(n?i[1]+n:"")}};function T(e){for(var t=e.childNodes.length;t>0;--t){e.removeChild(e.firstChild)}return e}function A(e,t){return T(e).appendChild(t)}function E(e,t,r,i){var n=document.createElement(e);if(r){n.className=r}if(i){n.style.cssText=i}if(typeof t=="string"){n.appendChild(document.createTextNode(t))}else if(t){for(var a=0;a=t){return s+(t-a)}s+=o-a;s+=r-s%r;a=o+1}}var W=function(){this.id=null;this.f=null;this.time=0;this.handler=R(this.onTimeout,this)};W.prototype.onTimeout=function(e){e.id=0;if(e.time<=+new Date){e.f()}else{setTimeout(e.handler,e.time-+new Date)}};W.prototype.set=function(e,t){this.f=t;var r=+new Date+e;if(!this.id||r=t){return i+Math.min(s,t-n)}n+=a-i;n+=r-n%r;i=a+1;if(n>=t){return i}}}var K=[""];function $(e){while(K.length<=e){K.push(X(K)+" ")}return K[e]}function X(e){return e[e.length-1]}function Y(e,t){var r=[];for(var i=0;i"€"&&(e.toUpperCase()!=e.toLowerCase()||ee.test(e))}function re(e,t){if(!t){return te(e)}if(t.source.indexOf("\\w")>-1&&te(e)){return true}return t.test(e)}function ie(e){for(var t in e){if(e.hasOwnProperty(t)&&e[t]){return false}}return true}var ne=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;function ae(e){return e.charCodeAt(0)>=768&&ne.test(e)}function se(e,t,r){while((r<0?t>0:tr?-1:1;for(;;){if(t==r){return t}var n=(t+r)/2,a=i<0?Math.ceil(n):Math.floor(n);if(a==t){return e(a)?t:r}if(e(a)){r=a}else{t=a+i}}}function le(e,t,r,i){if(!e){return i(t,r,"ltr",0)}var n=false;for(var a=0;at||t==r&&s.to==t){i(Math.max(s.from,t),Math.min(s.to,r),s.level==1?"rtl":"ltr",a);n=true}}if(!n){i(t,r,"ltr")}}var ue=null;function ce(e,t,r){var i;ue=null;for(var n=0;nt){return n}if(a.to==t){if(a.from!=a.to&&r=="before"){i=n}else{ue=n}}if(a.from==t){if(a.from!=a.to&&r!="before"){i=n}else{ue=n}}}return i!=null?i:ue}var fe=function(){var e="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";var t="nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111";function r(r){if(r<=247){return e.charAt(r)}else if(1424<=r&&r<=1524){return"R"}else if(1536<=r&&r<=1785){return t.charAt(r-1536)}else if(1774<=r&&r<=2220){return"r"}else if(8192<=r&&r<=8203){return"w"}else if(r==8204){return"b"}else{return"L"}}var i=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;var n=/[stwN]/,a=/[LRr]/,s=/[Lb1n]/,o=/[1n]/;function l(e,t,r){this.level=e;this.from=t;this.to=r}return function(e,t){var u=t=="ltr"?"L":"R";if(e.length==0||t=="ltr"&&!i.test(e)){return false}var c=e.length,f=[];for(var h=0;h-1){i[t]=n.slice(0,a).concat(n.slice(a+1))}}}}function ge(e,t){var r=me(e,t);if(!r.length){return}var i=Array.prototype.slice.call(arguments,2);for(var n=0;n0}function we(e){e.prototype.on=function(e,t){de(this,e,t)};e.prototype.off=function(e,t){ve(this,e,t)}}function ke(e){if(e.preventDefault){e.preventDefault()}else{e.returnValue=false}}function Ce(e){if(e.stopPropagation){e.stopPropagation()}else{e.cancelBubble=true}}function Se(e){return e.defaultPrevented!=null?e.defaultPrevented:e.returnValue==false}function Le(e){ke(e);Ce(e)}function Te(e){return e.target||e.srcElement}function Ae(e){var t=e.which;if(t==null){if(e.button&1){t=1}else if(e.button&2){t=3}else if(e.button&4){t=2}}if(y&&e.ctrlKey&&t==1){t=3}return t}var Ee=function(){if(s&&o<9){return false}var e=E("div");return"draggable"in e||"dragDrop"in e}();var Me;function Ne(e){if(Me==null){var t=E("span","​");A(e,E("span",[t,document.createTextNode("x")]));if(e.firstChild.offsetHeight!=0){Me=t.offsetWidth<=1&&t.offsetHeight>2&&!(s&&o<8)}}var r=Me?E("span","​"):E("span"," ",null,"display: inline-block; width: 1px; margin-right: -1px");r.setAttribute("cm-text","");return r}var _e;function Pe(e){if(_e!=null){return _e}var t=A(e,document.createTextNode("AخA"));var r=N(t,0,1).getBoundingClientRect();var i=N(t,1,2).getBoundingClientRect();T(e);if(!r||r.left==r.right){return false}return _e=i.right-r.right<3}var Ie="\n\nb".split(/\n/).length!=3?function(e){var t=0,r=[],i=e.length;while(t<=i){var n=e.indexOf("\n",t);if(n==-1){n=e.length}var a=e.slice(t,e.charAt(n-1)=="\r"?n-1:n);var s=a.indexOf("\r");if(s!=-1){r.push(a.slice(0,s));t+=s+1}else{r.push(a);t=n+1}}return r}:function(e){return e.split(/\r\n?|\n/)};var Oe=window.getSelection?function(e){try{return e.selectionStart!=e.selectionEnd}catch(e){return false}}:function(e){var t;try{t=e.ownerDocument.selection.createRange()}catch(e){}if(!t||t.parentElement()!=e){return false}return t.compareEndPoints("StartToEnd",t)!=0};var De=function(){var e=E("div");if("oncopy"in e){return true}e.setAttribute("oncopy","return;");return typeof e.oncopy=="function"}();var Re=null;function Ve(e){if(Re!=null){return Re}var t=A(e,E("span","x"));var r=t.getBoundingClientRect();var i=N(t,0,1).getBoundingClientRect();return Re=Math.abs(r.left-i.left)>1}var Fe={},We={};function ze(e,t){if(arguments.length>2){t.dependencies=Array.prototype.slice.call(arguments,2)}Fe[e]=t}function Be(e,t){We[e]=t}function He(e){if(typeof e=="string"&&We.hasOwnProperty(e)){e=We[e]}else if(e&&typeof e.name=="string"&&We.hasOwnProperty(e.name)){var t=We[e.name];if(typeof t=="string"){t={name:t}}e=J(t,e);e.name=t.name}else if(typeof e=="string"&&/^[\w\-]+\/[\w\-]+\+xml$/.test(e)){return He("application/xml")}else if(typeof e=="string"&&/^[\w\-]+\/[\w\-]+\+json$/.test(e)){return He("application/json")}if(typeof e=="string"){return{name:e}}else{return e||{name:"null"}}}function je(e,t){t=He(t);var r=Fe[t.name];if(!r){return je(e,"text/plain")}var i=r(e,t);if(Ue.hasOwnProperty(t.name)){var n=Ue[t.name];for(var a in n){if(!n.hasOwnProperty(a)){continue}if(i.hasOwnProperty(a)){i["_"+a]=i[a]}i[a]=n[a]}}i.name=t.name;if(t.helperType){i.helperType=t.helperType}if(t.modeProps){for(var s in t.modeProps){i[s]=t.modeProps[s]}}return i}var Ue={};function Ge(e,t){var r=Ue.hasOwnProperty(e)?Ue[e]:Ue[e]={};V(t,r)}function qe(e,t){if(t===true){return t}if(e.copyState){return e.copyState(t)}var r={};for(var i in t){var n=t[i];if(n instanceof Array){n=n.concat([])}r[i]=n}return r}function Ke(e,t){var r;while(e.innerMode){r=e.innerMode(t);if(!r||r.mode==e){break}t=r.state;e=r.mode}return r||{mode:e,state:t}}function $e(e,t,r){return e.startState?e.startState(t,r):true}var Xe=function(e,t,r){this.pos=this.start=0;this.string=e;this.tabSize=t||8;this.lastColumnPos=this.lastColumnValue=0;this.lineStart=0;this.lineOracle=r};Xe.prototype.eol=function(){return this.pos>=this.string.length};Xe.prototype.sol=function(){return this.pos==this.lineStart};Xe.prototype.peek=function(){return this.string.charAt(this.pos)||undefined};Xe.prototype.next=function(){if(this.post};Xe.prototype.eatSpace=function(){var e=this;var t=this.pos;while(/[\s\u00a0]/.test(this.string.charAt(this.pos))){++e.pos}return this.pos>t};Xe.prototype.skipToEnd=function(){this.pos=this.string.length};Xe.prototype.skipTo=function(e){var t=this.string.indexOf(e,this.pos);if(t>-1){this.pos=t;return true}};Xe.prototype.backUp=function(e){this.pos-=e};Xe.prototype.column=function(){if(this.lastColumnPos0){return null}if(a&&t!==false){this.pos+=a[0].length}return a}};Xe.prototype.current=function(){return this.string.slice(this.start,this.pos)};Xe.prototype.hideFirstChars=function(e,t){this.lineStart+=e;try{return t()}finally{this.lineStart-=e}};Xe.prototype.lookAhead=function(e){var t=this.lineOracle;return t&&t.lookAhead(e)};Xe.prototype.baseToken=function(){var e=this.lineOracle;return e&&e.baseToken(this.pos)};function Ye(e,t){t-=e.first;if(t<0||t>=e.size){throw new Error("There is no line "+(t+e.first)+" in the document.")}var r=e;while(!r.lines){for(var i=0;;++i){var n=r.children[i],a=n.chunkSize();if(t=e.first&&tr){return nt(r,Ye(e,r).text.length)}return ht(t,Ye(e,t.line).text.length)}function ht(e,t){var r=e.ch;if(r==null||r>t){return nt(e.line,t)}else if(r<0){return nt(e.line,0)}else{return e}}function pt(e,t){var r=[];for(var i=0;ithis.maxLookAhead){this.maxLookAhead=e}return t};mt.prototype.baseToken=function(e){var t=this;if(!this.baseTokens){return null}while(this.baseTokens[this.baseTokenPos]<=e){t.baseTokenPos+=2}var r=this.baseTokens[this.baseTokenPos+1];return{type:r&&r.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}};mt.prototype.nextLine=function(){this.line++;if(this.maxLookAhead>0){this.maxLookAhead--}};mt.fromSaved=function(e,t,r){if(t instanceof dt){return new mt(e,qe(e.mode,t.state),r,t.lookAhead)}else{return new mt(e,qe(e.mode,t),r)}};mt.prototype.save=function(e){var t=e!==false?qe(this.doc.mode,this.state):this.state;return this.maxLookAhead>0?new dt(t,this.maxLookAhead):t};function vt(e,t,r,i){var n=[e.state.modeGen],a={};Lt(e,t.text,e.doc.mode,r,function(e,t){return n.push(e,t)},a,i);var s=r.state;var o=function(i){r.baseTokens=n;var o=e.state.overlays[i],l=1,u=0;r.state=true;Lt(e,t.text,o.mode,r,function(e,t){var r=l;while(ue){n.splice(l,1,e,n[l+1],i)}l+=2;u=Math.min(e,i)}if(!t){return}if(o.opaque){n.splice(r,l-r,e,"overlay "+t);l=r+2}else{for(;re.options.maxHighlightLength&&qe(e.doc.mode,i.state);var a=vt(e,t,i);if(n){i.state=n}t.stateAfter=i.save(!n);t.styles=a.styles;if(a.classes){t.styleClasses=a.classes}else if(t.styleClasses){t.styleClasses=null}if(r===e.doc.highlightFrontier){e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier)}}return t.styles}function yt(e,t,r){var i=e.doc,n=e.display;if(!i.mode.startState){return new mt(i,true,t)}var a=Tt(e,t,r);var s=a>i.first&&Ye(i,a-1).stateAfter;var o=s?mt.fromSaved(i,s,a):new mt(i,$e(i.mode),a);i.iter(a,t,function(r){xt(e,r.text,o);var i=o.line;r.stateAfter=i==t-1||i%5==0||i>=n.viewFrom&&it.start){return a}}throw new Error("Mode "+e.name+" failed to advance stream.")}var kt=function(e,t,r){this.start=e.start;this.end=e.pos;this.string=e.current();this.type=t||null;this.state=r};function Ct(e,t,r,i){var n=e.doc,a=n.mode,s;t=ft(n,t);var o=Ye(n,t.line),l=yt(e,t.line,r);var u=new Xe(o.text,e.options.tabSize,l),c;if(i){c=[]}while((i||u.pose.options.maxHighlightLength){o=false;if(s){xt(e,t,i,c.pos)}c.pos=t.length;f=null}else{f=St(wt(r,c,i.state,h),a)}if(h){var p=h[0].name;if(p){f="m-"+(f?p+" "+f:p)}}if(!o||u!=f){while(ls;--o){if(o<=a.first){return a.first}var l=Ye(a,o-1),u=l.stateAfter;if(u&&(!r||o+(u instanceof dt?u.lookAhead:0)<=a.modeFrontier)){return o}var c=F(l.text,null,e.options.tabSize);if(n==null||i>c){n=o-1;i=c}}return n}function At(e,t){e.modeFrontier=Math.min(e.modeFrontier,t);if(e.highlightFrontierr;i--){var n=Ye(e,i).stateAfter;if(n&&(!(n instanceof dt)||i+n.lookAhead=t:a.to>t);(i||(i=[])).push(new Pt(s,a.from,l?null:a.to))}}}return i}function Vt(e,t,r){var i;if(e){for(var n=0;n=t:a.to>t);if(o||a.from==t&&s.type=="bookmark"&&(!r||a.marker.insertLeft)){var l=a.from==null||(s.inclusiveLeft?a.from<=t:a.from0&&o){for(var b=0;b0){continue}var c=[l,1],f=at(u.from,o.from),h=at(u.to,o.to);if(f<0||!s.inclusiveLeft&&!f){c.push({from:u.from,to:o.from})}if(h>0||!s.inclusiveRight&&!h){c.push({from:o.to,to:u.to})}n.splice.apply(n,c);l+=c.length-3}}return n}function Bt(e){var t=e.markedSpans;if(!t){return}for(var r=0;rt)&&(!i||Gt(i,a.marker)<0)){i=a.marker}}}return i}function Yt(e,t,r,i,n){var a=Ye(e,t);var s=Mt&&a.markedSpans;if(s){for(var o=0;o=0&&f<=0||c<=0&&f>=0){continue}if(c<=0&&(l.marker.inclusiveRight&&n.inclusiveLeft?at(u.to,r)>=0:at(u.to,r)>0)||c>=0&&(l.marker.inclusiveRight&&n.inclusiveLeft?at(u.from,i)<=0:at(u.from,i)<0)){return true}}}}function Qt(e){var t;while(t=Kt(e)){e=t.find(-1,true).line}return e}function Zt(e){var t;while(t=$t(e)){e=t.find(1,true).line}return e}function Jt(e){var t,r;while(t=$t(e)){e=t.find(1,true).line;(r||(r=[])).push(e)}return r}function er(e,t){var r=Ye(e,t),i=Qt(r);if(r==i){return t}return et(i)}function tr(e,t){if(t>e.lastLine()){return t}var r=Ye(e,t),i;if(!rr(e,r)){return t}while(i=$t(r)){r=i.find(1,true).line}return et(r)+1}function rr(e,t){var r=Mt&&t.markedSpans;if(r){for(var i=void 0,n=0;nt.maxLineLength){t.maxLineLength=r;t.maxLine=e}})}var or=function(e,t,r){this.text=e;Ht(this,t);this.height=r?r(this):1};or.prototype.lineNo=function(){return et(this)};we(or);function lr(e,t,r,i){e.text=t;if(e.stateAfter){e.stateAfter=null}if(e.styles){e.styles=null}if(e.order!=null){e.order=null}Bt(e);Ht(e,r);var n=i?i(e):1;if(n!=e.height){Je(e,n)}}function ur(e){e.parent=null;Bt(e)}var cr={},fr={};function hr(e,t){if(!e||/^\s*$/.test(e)){return null}var r=t.addModeClass?fr:cr;return r[e]||(r[e]=e.replace(/\S+/g,"cm-$&"))}function pr(e,t){var r=M("span",null,null,l?"padding-right: .1px":null);var i={pre:M("pre",[r],"CodeMirror-line"),content:r,col:0,pos:0,cm:e,trailingSpace:false,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var n=0;n<=(t.rest?t.rest.length:0);n++){var a=n?t.rest[n-1]:t.line,s=void 0;i.pos=0;i.addToken=mr;if(Pe(e.display.measure)&&(s=he(a,e.doc.direction))){i.addToken=gr(i.addToken,s)}i.map=[];var o=t!=e.display.externalMeasured&&et(a);xr(a,i,gt(e,a,o));if(a.styleClasses){if(a.styleClasses.bgClass){i.bgClass=O(a.styleClasses.bgClass,i.bgClass||"")}if(a.styleClasses.textClass){i.textClass=O(a.styleClasses.textClass,i.textClass||"")}}if(i.map.length==0){i.map.push(0,0,i.content.appendChild(Ne(e.display.measure)))}if(n==0){t.measure.map=i.map;t.measure.cache={}}else{(t.measure.maps||(t.measure.maps=[])).push(i.map);(t.measure.caches||(t.measure.caches=[])).push({})}}if(l){var u=i.content.lastChild;if(/\bcm-tab\b/.test(u.className)||u.querySelector&&u.querySelector(".cm-tab")){i.content.className="cm-tab-wrap-hack"}}ge(e,"renderLine",e,t.line,i.pre);if(i.pre.className){i.textClass=O(i.pre.className,i.textClass||"")}return i}function dr(e){var t=E("span","•","cm-invalidchar");t.title="\\u"+e.charCodeAt(0).toString(16);t.setAttribute("aria-label",t.title);return t}function mr(e,t,r,i,n,a,l){if(!t){return}var u=e.splitSpaces?vr(t,e.trailingSpace):t;var c=e.cm.state.specialChars,f=false;var h;if(!c.test(t)){e.col+=t.length;h=document.createTextNode(u);e.map.push(e.pos,e.pos+t.length,h);if(s&&o<9){f=true}e.pos+=t.length}else{h=document.createDocumentFragment();var p=0;while(true){c.lastIndex=p;var d=c.exec(t);var m=d?d.index-p:t.length-p;if(m){var v=document.createTextNode(u.slice(p,p+m));if(s&&o<9){h.appendChild(E("span",[v]))}else{h.appendChild(v)}e.map.push(e.pos,e.pos+m,v);e.col+=m;e.pos+=m}if(!d){break}p+=m+1;var g=void 0;if(d[0]=="\t"){var y=e.cm.options.tabSize,x=y-e.col%y;g=h.appendChild(E("span",$(x),"cm-tab"));g.setAttribute("role","presentation");g.setAttribute("cm-text","\t");e.col+=x}else if(d[0]=="\r"||d[0]=="\n"){g=h.appendChild(E("span",d[0]=="\r"?"␍":"␤","cm-invalidchar"));g.setAttribute("cm-text",d[0]);e.col+=1}else{g=e.cm.options.specialCharPlaceholder(d[0]);g.setAttribute("cm-text",d[0]);if(s&&o<9){h.appendChild(E("span",[g]))}else{h.appendChild(g)}e.col+=1}e.map.push(e.pos,e.pos+1,g);e.pos++}}e.trailingSpace=u.charCodeAt(t.length-1)==32;if(r||i||n||f||a){var b=r||"";if(i){ +b+=i}if(n){b+=n}var w=E("span",[h],b,a);if(l){for(var k in l){if(l.hasOwnProperty(k)&&k!="style"&&k!="class"){w.setAttribute(k,l[k])}}}return e.content.appendChild(w)}e.content.appendChild(h)}function vr(e,t){if(e.length>1&&!/ /.test(e)){return e}var r=t,i="";for(var n=0;nu&&f.from<=u){break}}if(f.to>=c){return e(r,i,n,a,s,o,l)}e(r,i.slice(0,f.to-u),n,a,null,o,l);a=null;i=i.slice(f.to-u);u=f.to}}}function yr(e,t,r,i){var n=!i&&r.widgetNode;if(n){e.map.push(e.pos,e.pos+t,n)}if(!i&&e.cm.display.input.needsContentAttribute){if(!n){n=e.content.appendChild(document.createElement("span"))}n.setAttribute("cm-marker",r.id)}if(n){e.cm.display.input.setUneditable(n);e.content.appendChild(n)}e.pos+=t;e.trailingSpace=false}function xr(e,t,r){var i=e.markedSpans,n=e.text,a=0;if(!i){for(var s=1;sl||C.collapsed&&k.to==l&&k.from==l)){if(k.to!=null&&k.to!=l&&p>k.to){p=k.to;m=""}if(C.className){d+=" "+C.className}if(C.css){h=(h?h+";":"")+C.css}if(C.startStyle&&k.from==l){v+=" "+C.startStyle}if(C.endStyle&&k.to==p){(b||(b=[])).push(C.endStyle,k.to)}if(C.title){(y||(y={})).title=C.title}if(C.attributes){for(var S in C.attributes){(y||(y={}))[S]=C.attributes[S]}}if(C.collapsed&&(!g||Gt(g.marker,C)<0)){g=k}}else if(k.from>l&&p>k.from){p=k.from}}if(b){for(var L=0;L=o){break}var A=Math.min(o,p);while(true){if(c){var E=l+c.length;if(!g){var M=E>A?c.slice(0,A-l):c;t.addToken(t,M,f?f+d:d,v,l+M.length==p?m:"",h,y)}if(E>=A){c=c.slice(A-l);l=A;break}l=E;v=""}c=n.slice(a,a=r[u++]);f=hr(r[u++],t.cm.options)}}}function br(e,t,r){this.line=t;this.rest=Jt(t);this.size=this.rest?et(X(this.rest))-r+1:1;this.node=this.text=null;this.hidden=rr(e,t)}function wr(e,t,r){var i=[],n;for(var a=t;a2){a.push((l.bottom+u.top)/2-r.top)}}}a.push(r.bottom-r.top)}}function Yr(e,t,r){if(e.line==t){return{map:e.measure.map,cache:e.measure.cache}}for(var i=0;ir){return{map:e.measure.maps[n],cache:e.measure.caches[n],before:true}}}}function Qr(e,t){t=Qt(t);var r=et(t);var i=e.display.externalMeasured=new br(e.doc,t,r);i.lineN=r;var n=i.built=pr(e,i);i.text=n.pre;A(e.display.lineMeasure,n.pre);return i}function Zr(e,t,r,i){return ti(e,ei(e,t),r,i)}function Jr(e,t){if(t>=e.display.viewFrom&&t=r.lineN&&tt){a=l-o;n=a-1;if(t>=l){s="right"}}if(n!=null){i=e[u+2];if(o==l&&r==(i.insertLeft?"left":"right")){s=r}if(r=="left"&&n==0){while(u&&e[u-2]==e[u-3]&&e[u-1].insertLeft){i=e[(u-=3)+2];s="left"}}if(r=="right"&&n==l-o){while(u=0;n--){if((r=e[n]).left!=r.right){break}}}return r}function ai(e,t,r,i){var n=ii(t.map,r,i);var a=n.node,l=n.start,u=n.end,c=n.collapse;var f;if(a.nodeType==3){for(var h=0;h<4;h++){while(l&&ae(t.line.text.charAt(n.coverStart+l))){--l}while(n.coverStart+u0){c=i="right"}var p;if(e.options.lineWrapping&&(p=a.getClientRects()).length>1){f=p[i=="right"?p.length-1:0]}else{f=a.getBoundingClientRect()}}if(s&&o<9&&!l&&(!f||!f.left&&!f.right)){var d=a.parentNode.getClientRects()[0];if(d){f={left:d.left,right:d.left+Ei(e.display),top:d.top,bottom:d.bottom}}else{f=ri}}var m=f.top-t.rect.top,v=f.bottom-t.rect.top;var g=(m+v)/2;var y=t.view.measure.heights;var x=0;for(;x=i.text.length){l=i.text.length;u="before"}else if(l<=0){l=0;u="after"}if(!o){return s(u=="before"?l-1:l,u=="before")}function c(e,t,r){var i=o[t],n=i.level==1;return s(r?e-1:e,n!=r)}var f=ce(o,l,u);var h=ue;var p=c(l,f,u=="before");if(h!=null){p.other=c(l,h,u!="before")}return p}function gi(e,t){var r=0;t=ft(e.doc,t);if(!e.options.lineWrapping){r=Ei(e.display)*t.ch}var i=Ye(e.doc,t.line);var n=nr(i)+jr(e.display);return{left:r,right:r,top:n,bottom:n+i.height}}function yi(e,t,r,i,n){var a=nt(e,t,r);a.xRel=n;if(i){a.outside=i}return a}function xi(e,t,r){var i=e.doc;r+=e.display.viewOffset;if(r<0){return yi(i.first,0,null,-1,-1)}var n=tt(i,r),a=i.first+i.size-1;if(n>a){return yi(i.first+i.size-1,Ye(i,a).text.length,null,1,1)}if(t<0){t=0}var s=Ye(i,n);for(;;){var o=Ci(e,s,n,t,r);var l=Xt(s,o.ch+(o.xRel>0||o.outside>0?1:0));if(!l){return o}var u=l.find(1);if(u.line==n){return u}s=Ye(i,n=u.line)}}function bi(e,t,r,i){i-=hi(t);var n=t.text.length;var a=oe(function(t){return ti(e,r,t-1).bottom<=i},n,0);n=oe(function(t){return ti(e,r,t).top>i},a,n);return{begin:a,end:n}}function wi(e,t,r,i){if(!r){r=ei(e,t)}var n=pi(e,t,ti(e,r,i),"line").top;return bi(e,t,r,n)}function ki(e,t,r,i){return e.bottom<=r?false:e.top>r?true:(i?e.left:e.right)>t}function Ci(e,t,r,i,n){n-=nr(t);var a=ei(e,t);var s=hi(t);var o=0,l=t.text.length,u=true;var c=he(t,e.doc.direction);if(c){var f=(e.options.lineWrapping?Li:Si)(e,t,r,a,c,i,n);u=f.level!=1;o=u?f.from:f.to-1;l=u?f.to:f.from-1}var h=null,p=null;var d=oe(function(t){var r=ti(e,a,t);r.top+=s;r.bottom+=s;if(!ki(r,i,n,false)){return false}if(r.top<=n&&r.left<=i){h=t;p=r}return true},o,l);var m,v,g=false;if(p){var y=i-p.left=b.bottom?1:0}d=se(t.text,d,1);return yi(r,d,v,g,i-m)}function Si(e,t,r,i,n,a,s){var o=oe(function(o){var l=n[o],u=l.level!=1;return ki(vi(e,nt(r,u?l.to:l.from,u?"before":"after"),"line",t,i),a,s,true)},0,n.length-1);var l=n[o];if(o>0){var u=l.level!=1;var c=vi(e,nt(r,u?l.from:l.to,u?"after":"before"),"line",t,i);if(ki(c,a,s,true)&&c.top>s){l=n[o-1]}}return l}function Li(e,t,r,i,n,a,s){var o=bi(e,t,i,s);var l=o.begin;var u=o.end;if(/\s/.test(t.text.charAt(u-1))){u--}var c=null,f=null;for(var h=0;h=u||p.to<=l){continue}var d=p.level!=1;var m=ti(e,i,d?Math.min(u,p.to)-1:Math.max(l,p.from)).right;var v=mv){c=p;f=v}}if(!c){c=n[n.length-1]}if(c.fromu){c={from:c.from,to:u,level:c.level}}return c}var Ti;function Ai(e){if(e.cachedTextHeight!=null){return e.cachedTextHeight}if(Ti==null){Ti=E("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t){Ti.appendChild(document.createTextNode("x"));Ti.appendChild(E("br"))}Ti.appendChild(document.createTextNode("x"))}A(e.measure,Ti);var r=Ti.offsetHeight/50;if(r>3){e.cachedTextHeight=r}T(e.measure);return r||1}function Ei(e){if(e.cachedCharWidth!=null){return e.cachedCharWidth}var t=E("span","xxxxxxxxxx");var r=E("pre",[t],"CodeMirror-line-like");A(e.measure,r);var i=t.getBoundingClientRect(),n=(i.right-i.left)/10;if(n>2){e.cachedCharWidth=n}return n||10}function Mi(e){var t=e.display,r={},i={};var n=t.gutters.clientLeft;for(var a=t.gutters.firstChild,s=0;a;a=a.nextSibling,++s){var o=e.display.gutterSpecs[s].className;r[o]=a.offsetLeft+a.clientLeft+n;i[o]=a.clientWidth}return{fixedPos:Ni(t),gutterTotalWidth:t.gutters.offsetWidth,gutterLeft:r,gutterWidth:i,wrapperWidth:t.wrapper.clientWidth}}function Ni(e){return e.scroller.getBoundingClientRect().left-e.sizer.getBoundingClientRect().left}function _i(e){var t=Ai(e.display),r=e.options.lineWrapping;var i=r&&Math.max(5,e.display.scroller.clientWidth/Ei(e.display)-3);return function(n){if(rr(e.doc,n)){return 0}var a=0;if(n.widgets){for(var s=0;s=e.display.viewTo){return null}t-=e.display.viewFrom;if(t<0){return null}var r=e.display.view;for(var i=0;it)){n.updateLineNumbers=t}e.curOp.viewChanged=true;if(t>=n.viewTo){if(Mt&&er(e.doc,t)n.viewFrom){Vi(e)}else{n.viewFrom+=i;n.viewTo+=i}}else if(t<=n.viewFrom&&r>=n.viewTo){Vi(e)}else if(t<=n.viewFrom){var a=Fi(e,r,r+i,1);if(a){n.view=n.view.slice(a.index);n.viewFrom=a.lineN;n.viewTo+=i}else{Vi(e)}}else if(r>=n.viewTo){var s=Fi(e,t,t,-1);if(s){n.view=n.view.slice(0,s.index);n.viewTo=s.lineN}else{Vi(e)}}else{var o=Fi(e,t,t,-1);var l=Fi(e,r,r+i,1);if(o&&l){n.view=n.view.slice(0,o.index).concat(wr(e,o.lineN,l.lineN)).concat(n.view.slice(l.index));n.viewTo+=i}else{Vi(e)}}var u=n.externalMeasured;if(u){if(r=n.lineN&&t=i.viewTo){return}var a=i.view[Oi(e,t)];if(a.node==null){return}var s=a.changes||(a.changes=[]);if(z(s,r)==-1){s.push(r)}}function Vi(e){e.display.viewFrom=e.display.viewTo=e.doc.first;e.display.view=[];e.display.viewOffset=0}function Fi(e,t,r,i){var n=Oi(e,t),a,s=e.display.view;if(!Mt||r==e.doc.first+e.doc.size){return{index:n,lineN:r}}var o=e.display.viewFrom;for(var l=0;l0){if(n==s.length-1){return null}a=o+s[n].size-t;n++}else{a=o-t}t+=a;r+=a}while(er(e.doc,r)!=r){if(n==(i<0?0:s.length-1)){return null}r+=i*s[n-(i<0?1:0)].size;n+=i}return{index:n,lineN:r}}function Wi(e,t,r){var i=e.display,n=i.view;if(n.length==0||t>=i.viewTo||r<=i.viewFrom){i.view=wr(e,t,r);i.viewFrom=t}else{if(i.viewFrom>t){i.view=wr(e,t,i.viewFrom).concat(i.view)}else if(i.viewFromr){i.view=i.view.slice(0,Oi(e,r))}}i.viewTo=r}function zi(e){var t=e.display.view,r=0;for(var i=0;i=e.display.viewTo||o.to().line0){t.blinker=setInterval(function(){return t.cursorDiv.style.visibility=(r=!r)?"":"hidden"},e.options.cursorBlinkRate)}else if(e.options.cursorBlinkRate<0){t.cursorDiv.style.visibility="hidden"}}function Ki(e){if(!e.state.focused){e.display.input.focus();Xi(e)}}function $i(e){e.state.delayingBlurEvent=true;setTimeout(function(){if(e.state.delayingBlurEvent){e.state.delayingBlurEvent=false;Yi(e)}},100)}function Xi(e,t){if(e.state.delayingBlurEvent){e.state.delayingBlurEvent=false}if(e.options.readOnly=="nocursor"){return}if(!e.state.focused){ge(e,"focus",e,t);e.state.focused=true;I(e.display.wrapper,"CodeMirror-focused");if(!e.curOp&&e.display.selForContextMenu!=e.doc.sel){e.display.input.reset();if(l){setTimeout(function(){return e.display.input.reset(true)},20)}}e.display.input.receivedFocus()}qi(e)}function Yi(e,t){if(e.state.delayingBlurEvent){return}if(e.state.focused){ge(e,"blur",e,t);e.state.focused=false;L(e.display.wrapper,"CodeMirror-focused")}clearInterval(e.display.blinker);setTimeout(function(){if(!e.state.focused){e.display.shift=false}},150)}function Qi(e){var t=e.display;var r=t.lineDiv.offsetTop;for(var i=0;i.005||h<-.005){Je(n.line,l);Zi(n.line);if(n.rest){for(var p=0;pe.display.sizerWidth){var d=Math.ceil(u/Ei(e.display));if(d>e.display.maxLineLength){e.display.maxLineLength=d;e.display.maxLine=n.line;e.display.maxLineChanged=true}}}}function Zi(e){if(e.widgets){for(var t=0;t=s){a=tt(t,nr(Ye(t,l))-e.wrapper.clientHeight);s=l}}return{from:a,to:Math.max(s,a+1)}}function en(e,t){if(ye(e,"scrollCursorIntoView")){return}var r=e.display,i=r.sizer.getBoundingClientRect(),n=null;if(t.top+i.top<0){n=true}else if(t.bottom+i.top>(window.innerHeight||document.documentElement.clientHeight)){n=false}if(n!=null&&!d){var a=E("div","​",null,"position: absolute;\n top: "+(t.top-r.viewOffset-jr(e.display))+"px;\n height: "+(t.bottom-t.top+qr(e)+r.barHeight)+"px;\n left: "+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;");e.display.lineSpace.appendChild(a);a.scrollIntoView(n);e.display.lineSpace.removeChild(a)}}function tn(e,t,r,i){if(i==null){i=0}var n;if(!e.options.lineWrapping&&t==r){t=t.ch?nt(t.line,t.sticky=="before"?t.ch-1:t.ch,"after"):t;r=t.sticky=="before"?nt(t.line,t.ch+1,"before"):t}for(var a=0;a<5;a++){var s=false;var o=vi(e,t);var l=!r||r==t?o:vi(e,r);n={left:Math.min(o.left,l.left),top:Math.min(o.top,l.top)-i,right:Math.max(o.left,l.left),bottom:Math.max(o.bottom,l.bottom)+i};var u=nn(e,n);var c=e.doc.scrollTop,f=e.doc.scrollLeft;if(u.scrollTop!=null){fn(e,u.scrollTop);if(Math.abs(e.doc.scrollTop-c)>1){s=true}}if(u.scrollLeft!=null){pn(e,u.scrollLeft);if(Math.abs(e.doc.scrollLeft-f)>1){s=true}}if(!s){break}}return n}function rn(e,t){var r=nn(e,t);if(r.scrollTop!=null){fn(e,r.scrollTop)}if(r.scrollLeft!=null){pn(e,r.scrollLeft)}}function nn(e,t){var r=e.display,i=Ai(e.display);if(t.top<0){t.top=0}var n=e.curOp&&e.curOp.scrollTop!=null?e.curOp.scrollTop:r.scroller.scrollTop;var a=$r(e),s={};if(t.bottom-t.top>a){t.bottom=t.top+a}var o=e.doc.height+Ur(r);var l=t.topo-i;if(t.topn+a){var c=Math.min(t.top,(u?o:t.bottom)-a);if(c!=n){s.scrollTop=c}}var f=e.curOp&&e.curOp.scrollLeft!=null?e.curOp.scrollLeft:r.scroller.scrollLeft;var h=Kr(e)-(e.options.fixedGutter?r.gutters.offsetWidth:0);var p=t.right-t.left>h;if(p){t.right=t.left+h}if(t.left<10){s.scrollLeft=0}else if(t.lefth+f-3){s.scrollLeft=t.right+(p?0:10)-h}return s}function an(e,t){if(t==null){return}un(e);e.curOp.scrollTop=(e.curOp.scrollTop==null?e.doc.scrollTop:e.curOp.scrollTop)+t}function sn(e){un(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function on(e,t,r){if(t!=null||r!=null){un(e)}if(t!=null){e.curOp.scrollLeft=t}if(r!=null){e.curOp.scrollTop=r}}function ln(e,t){un(e);e.curOp.scrollToPos=t}function un(e){var t=e.curOp.scrollToPos;if(t){e.curOp.scrollToPos=null;var r=gi(e,t.from),i=gi(e,t.to);cn(e,r,i,t.margin)}}function cn(e,t,r,i){var n=nn(e,{left:Math.min(t.left,r.left),top:Math.min(t.top,r.top)-i,right:Math.max(t.right,r.right),bottom:Math.max(t.bottom,r.bottom)+i});on(e,n.scrollLeft,n.scrollTop)}function fn(e,t){if(Math.abs(e.doc.scrollTop-t)<2){return}if(!r){Hn(e,{top:t})}hn(e,t,true);if(r){Hn(e)}On(e,100)}function hn(e,t,r){t=Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t);if(e.display.scroller.scrollTop==t&&!r){return}e.doc.scrollTop=t;e.display.scrollbars.setScrollTop(t);if(e.display.scroller.scrollTop!=t){e.display.scroller.scrollTop=t}}function pn(e,t,r,i){t=Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth);if((r?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!i){return}e.doc.scrollLeft=t;qn(e);if(e.display.scroller.scrollLeft!=t){e.display.scroller.scrollLeft=t}e.display.scrollbars.setScrollLeft(t)}function dn(e){var t=e.display,r=t.gutters.offsetWidth;var i=Math.round(e.doc.height+Ur(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?r:0,docHeight:i,scrollHeight:i+qr(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:r}}var mn=function(e,t,r){this.cm=r;var i=this.vert=E("div",[E("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar");var n=this.horiz=E("div",[E("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");i.tabIndex=n.tabIndex=-1;e(i);e(n);de(i,"scroll",function(){if(i.clientHeight){t(i.scrollTop,"vertical")}});de(n,"scroll",function(){if(n.clientWidth){t(n.scrollLeft,"horizontal")}});this.checkedZeroWidth=false;if(s&&o<8){this.horiz.style.minHeight=this.vert.style.minWidth="18px"}};mn.prototype.update=function(e){var t=e.scrollWidth>e.clientWidth+1;var r=e.scrollHeight>e.clientHeight+1;var i=e.nativeBarWidth;if(r){this.vert.style.display="block";this.vert.style.bottom=t?i+"px":"0";var n=e.viewHeight-(t?i:0);this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+n)+"px"}else{this.vert.style.display="";this.vert.firstChild.style.height="0"}if(t){this.horiz.style.display="block";this.horiz.style.right=r?i+"px":"0";this.horiz.style.left=e.barLeft+"px";var a=e.viewWidth-e.barLeft-(r?i:0);this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+a)+"px"}else{this.horiz.style.display="";this.horiz.firstChild.style.width="0"}if(!this.checkedZeroWidth&&e.clientHeight>0){if(i==0){this.zeroWidthHack()}this.checkedZeroWidth=true}return{right:r?i:0,bottom:t?i:0}};mn.prototype.setScrollLeft=function(e){if(this.horiz.scrollLeft!=e){ +this.horiz.scrollLeft=e}if(this.disableHoriz){this.enableZeroWidthBar(this.horiz,this.disableHoriz,"horiz")}};mn.prototype.setScrollTop=function(e){if(this.vert.scrollTop!=e){this.vert.scrollTop=e}if(this.disableVert){this.enableZeroWidthBar(this.vert,this.disableVert,"vert")}};mn.prototype.zeroWidthHack=function(){var e=y&&!p?"12px":"18px";this.horiz.style.height=this.vert.style.width=e;this.horiz.style.pointerEvents=this.vert.style.pointerEvents="none";this.disableHoriz=new W;this.disableVert=new W};mn.prototype.enableZeroWidthBar=function(e,t,r){e.style.pointerEvents="auto";function i(){var n=e.getBoundingClientRect();var a=r=="vert"?document.elementFromPoint(n.right-1,(n.top+n.bottom)/2):document.elementFromPoint((n.right+n.left)/2,n.bottom-1);if(a!=e){e.style.pointerEvents="none"}else{t.set(1e3,i)}}t.set(1e3,i)};mn.prototype.clear=function(){var e=this.horiz.parentNode;e.removeChild(this.horiz);e.removeChild(this.vert)};var vn=function(){};vn.prototype.update=function(){return{bottom:0,right:0}};vn.prototype.setScrollLeft=function(){};vn.prototype.setScrollTop=function(){};vn.prototype.clear=function(){};function gn(e,t){if(!t){t=dn(e)}var r=e.display.barWidth,i=e.display.barHeight;yn(e,t);for(var n=0;n<4&&r!=e.display.barWidth||i!=e.display.barHeight;n++){if(r!=e.display.barWidth&&e.options.lineWrapping){Qi(e)}yn(e,dn(e));r=e.display.barWidth;i=e.display.barHeight}}function yn(e,t){var r=e.display;var i=r.scrollbars.update(t);r.sizer.style.paddingRight=(r.barWidth=i.right)+"px";r.sizer.style.paddingBottom=(r.barHeight=i.bottom)+"px";r.heightForcer.style.borderBottom=i.bottom+"px solid transparent";if(i.right&&i.bottom){r.scrollbarFiller.style.display="block";r.scrollbarFiller.style.height=i.bottom+"px";r.scrollbarFiller.style.width=i.right+"px"}else{r.scrollbarFiller.style.display=""}if(i.bottom&&e.options.coverGutterNextToScrollbar&&e.options.fixedGutter){r.gutterFiller.style.display="block";r.gutterFiller.style.height=i.bottom+"px";r.gutterFiller.style.width=t.gutterWidth+"px"}else{r.gutterFiller.style.display=""}}var xn={native:mn,null:vn};function bn(e){if(e.display.scrollbars){e.display.scrollbars.clear();if(e.display.scrollbars.addClass){L(e.display.wrapper,e.display.scrollbars.addClass)}}e.display.scrollbars=new xn[e.options.scrollbarStyle](function(t){e.display.wrapper.insertBefore(t,e.display.scrollbarFiller);de(t,"mousedown",function(){if(e.state.focused){setTimeout(function(){return e.display.input.focus()},0)}});t.setAttribute("cm-not-content","true")},function(t,r){if(r=="horizontal"){pn(e,t)}else{fn(e,t)}},e);if(e.display.scrollbars.addClass){I(e.display.wrapper,e.display.scrollbars.addClass)}}var wn=0;function kn(e){e.curOp={cm:e,viewChanged:false,startHeight:e.doc.height,forceUpdate:false,updateInput:0,typing:false,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:false,updateMaxLine:false,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:false,id:++wn};Cr(e.curOp)}function Cn(e){var t=e.curOp;if(t){Lr(t,function(e){for(var t=0;t=r.viewTo)||r.maxLineChanged&&t.options.lineWrapping;e.update=e.mustUpdate&&new Rn(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}function Tn(e){e.updatedDisplay=e.mustUpdate&&zn(e.cm,e.update)}function An(e){var t=e.cm,r=t.display;if(e.updatedDisplay){Qi(t)}e.barMeasure=dn(t);if(r.maxLineChanged&&!t.options.lineWrapping){e.adjustWidthTo=Zr(t,r.maxLine,r.maxLine.text.length).left+3;t.display.sizerWidth=e.adjustWidthTo;e.barMeasure.scrollWidth=Math.max(r.scroller.clientWidth,r.sizer.offsetLeft+e.adjustWidthTo+qr(t)+t.display.barWidth);e.maxScrollLeft=Math.max(0,r.sizer.offsetLeft+e.adjustWidthTo-Kr(t))}if(e.updatedDisplay||e.selectionChanged){e.preparedSelection=r.input.prepareSelection()}}function En(e){var t=e.cm;if(e.adjustWidthTo!=null){t.display.sizer.style.minWidth=e.adjustWidthTo+"px";if(e.maxScrollLeft=e.display.viewTo){return}var r=+new Date+e.options.workTime;var i=yt(e,t.highlightFrontier);var n=[];t.iter(i.line,Math.min(t.first+t.size,e.display.viewTo+500),function(a){if(i.line>=e.display.viewFrom){var s=a.styles;var o=a.text.length>e.options.maxHighlightLength?qe(t.mode,i.state):null;var l=vt(e,a,i,true);if(o){i.state=o}a.styles=l.styles;var u=a.styleClasses,c=l.classes;if(c){a.styleClasses=c}else if(u){a.styleClasses=null}var f=!s||s.length!=a.styles.length||u!=c&&(!u||!c||u.bgClass!=c.bgClass||u.textClass!=c.textClass);for(var h=0;!f&&hr){On(e,e.options.workDelay);return true}});t.highlightFrontier=i.line;t.modeFrontier=Math.max(t.modeFrontier,i.line);if(n.length){Nn(e,function(){for(var t=0;t=r.viewFrom&&t.visible.to<=r.viewTo&&(r.updateLineNumbers==null||r.updateLineNumbers>=r.viewTo)&&r.renderedView==r.view&&zi(e)==0){return false}if(Kn(e)){Vi(e);t.dims=Mi(e)}var n=i.first+i.size;var a=Math.max(t.visible.from-e.options.viewportMargin,i.first);var s=Math.min(n,t.visible.to+e.options.viewportMargin);if(r.viewFroms&&r.viewTo-s<20){s=Math.min(n,r.viewTo)}if(Mt){a=er(e.doc,a);s=tr(e.doc,s)}var o=a!=r.viewFrom||s!=r.viewTo||r.lastWrapHeight!=t.wrapperHeight||r.lastWrapWidth!=t.wrapperWidth;Wi(e,a,s);r.viewOffset=nr(Ye(e.doc,r.viewFrom));e.display.mover.style.top=r.viewOffset+"px";var l=zi(e);if(!o&&l==0&&!t.force&&r.renderedView==r.view&&(r.updateLineNumbers==null||r.updateLineNumbers>=r.viewTo)){return false}var u=Fn(e);if(l>4){r.lineDiv.style.display="none"}jn(e,r.updateLineNumbers,t.dims);if(l>4){r.lineDiv.style.display=""}r.renderedView=r.view;Wn(u);T(r.cursorDiv);T(r.selectionDiv);r.gutters.style.height=r.sizer.style.minHeight=0;if(o){r.lastWrapHeight=t.wrapperHeight;r.lastWrapWidth=t.wrapperWidth;On(e,400)}r.updateLineNumbers=null;return true}function Bn(e,t){var r=t.viewport;for(var i=true;;i=false){if(!i||!e.options.lineWrapping||t.oldDisplayWidth==Kr(e)){if(r&&r.top!=null){r={top:Math.min(e.doc.height+Ur(e.display)-$r(e),r.top)}}t.visible=Ji(e.display,e.doc,r);if(t.visible.from>=e.display.viewFrom&&t.visible.to<=e.display.viewTo){break}}if(!zn(e,t)){break}Qi(e);var n=dn(e);Bi(e);gn(e,n);Gn(e,n);t.force=false}t.signal(e,"update",e);if(e.display.viewFrom!=e.display.reportedViewFrom||e.display.viewTo!=e.display.reportedViewTo){t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo);e.display.reportedViewFrom=e.display.viewFrom;e.display.reportedViewTo=e.display.viewTo}}function Hn(e,t){var r=new Rn(e,t);if(zn(e,r)){Qi(e);Bn(e,r);var i=dn(e);Bi(e);gn(e,i);Gn(e,i);r.finish()}}function jn(e,t,r){var i=e.display,n=e.options.lineNumbers;var a=i.lineDiv,s=a.firstChild;function o(t){var r=t.nextSibling;if(l&&y&&e.display.currentWheelTarget==t){t.style.display="none"}else{t.parentNode.removeChild(t)}return r}var u=i.view,c=i.viewFrom;for(var f=0;f-1){d=false}Mr(e,h,c,r)}if(d){T(h.lineNumber);h.lineNumber.appendChild(document.createTextNode(it(e.options,c)))}s=h.node.nextSibling}c+=h.size}while(s){s=o(s)}}function Un(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px"}function Gn(e,t){e.display.sizer.style.minHeight=t.docHeight+"px";e.display.heightForcer.style.top=t.docHeight+"px";e.display.gutters.style.height=t.docHeight+e.display.barHeight+qr(e)+"px"}function qn(e){var t=e.display,r=t.view;if(!t.alignWidgets&&(!t.gutters.firstChild||!e.options.fixedGutter)){return}var i=Ni(t)-t.scroller.scrollLeft+e.doc.scrollLeft;var n=t.gutters.offsetWidth,a=i+"px";for(var s=0;so.clientWidth;var c=o.scrollHeight>o.clientHeight;if(!(n&&u||a&&c)){return}if(a&&y&&l){e:for(var h=t.target,p=s.view;h!=o;h=h.parentNode){for(var d=0;d=0&&at(e,n.to())<=0){return i}}return-1};var na=function(e,t){this.anchor=e;this.head=t};na.prototype.from=function(){return ut(this.anchor,this.head)};na.prototype.to=function(){return lt(this.anchor,this.head)};na.prototype.empty=function(){return this.head.line==this.anchor.line&&this.head.ch==this.anchor.ch};function aa(e,t,r){var i=e&&e.options.selectionsMayTouch;var n=t[r];t.sort(function(e,t){return at(e.from(),t.from())});r=z(t,n);for(var a=1;a0:l>=0){var u=ut(o.from(),s.from()),c=lt(o.to(),s.to());var f=o.empty()?s.from()==s.head:o.from()==o.head;if(a<=r){--r}t.splice(--a,2,new na(f?c:u,f?u:c))}}return new ia(t,r)}function sa(e,t){return new ia([new na(e,t||e)],0)}function oa(e){if(!e.text){return e.to}return nt(e.from.line+e.text.length-1,X(e.text).length+(e.text.length==1?e.from.ch:0))}function la(e,t){if(at(e,t.from)<0){return e}if(at(e,t.to)<=0){return oa(t)}var r=e.line+t.text.length-(t.to.line-t.from.line)-1,i=e.ch;if(e.line==t.to.line){i+=oa(t).ch-t.to.ch}return nt(r,i)}function ua(e,t){var r=[];for(var i=0;i1){e.remove(o.line+1,d-1)}e.insert(o.line+1,g)}Ar(e,"change",e,t)}function va(e,t,r){function i(e,n,a){if(e.linked){for(var s=0;s1&&!e.done[e.done.length-2].ranges){e.done.pop();return X(e.done)}}function Sa(e,t,r,i){var n=e.history;n.undone.length=0;var a=+new Date,s;var o;if((n.lastOp==i||n.lastOrigin==t.origin&&t.origin&&(t.origin.charAt(0)=="+"&&n.lastModTime>a-(e.cm?e.cm.options.historyEventDelay:500)||t.origin.charAt(0)=="*"))&&(s=Ca(n,n.lastOp==i))){o=X(s.changes);if(at(t.from,t.to)==0&&at(t.from,o.to)==0){o.to=oa(t)}else{s.changes.push(wa(e,t))}}else{var l=X(n.done);if(!l||!l.ranges){Aa(e.sel,n.done)}s={changes:[wa(e,t)],generation:n.generation};n.done.push(s);while(n.done.length>n.undoDepth){n.done.shift();if(!n.done[0].ranges){n.done.shift()}}}n.done.push(r);n.generation=++n.maxGeneration;n.lastModTime=n.lastSelTime=a;n.lastOp=n.lastSelOp=i;n.lastOrigin=n.lastSelOrigin=t.origin;if(!o){ge(e,"historyAdded")}}function La(e,t,r,i){var n=t.charAt(0);return n=="*"||n=="+"&&r.ranges.length==i.ranges.length&&r.somethingSelected()==i.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500)}function Ta(e,t,r,i){var n=e.history,a=i&&i.origin;if(r==n.lastSelOp||a&&n.lastSelOrigin==a&&(n.lastModTime==n.lastSelTime&&n.lastOrigin==a||La(e,a,X(n.done),t))){n.done[n.done.length-1]=t}else{Aa(t,n.done)}n.lastSelTime=+new Date;n.lastSelOrigin=a;n.lastSelOp=r;if(i&&i.clearRedo!==false){ka(n.undone)}}function Aa(e,t){var r=X(t);if(!(r&&r.ranges&&r.equals(e))){t.push(e)}}function Ea(e,t,r,i){var n=t["spans_"+e.id],a=0;e.iter(Math.max(e.first,r),Math.min(e.first+e.size,i),function(r){if(r.markedSpans){(n||(n=t["spans_"+e.id]={}))[a]=r.markedSpans}++a})}function Ma(e){if(!e){return null}var t;for(var r=0;r-1){X(o)[f]=u[f];delete u[f]}}}}}}return i}function Ia(e,t,r,i){if(i){var n=e.anchor;if(r){var a=at(t,n)<0;if(a!=at(r,n)<0){n=t;t=r}else if(a!=at(t,r)<0){t=r}}return new na(n,t)}else{return new na(r||t,t)}}function Oa(e,t,r,i,n){if(n==null){n=e.cm&&(e.cm.display.shift||e.extend)}za(e,new ia([Ia(e.sel.primary(),t,r,n)],0),i)}function Da(e,t,r){var i=[];var n=e.cm&&(e.cm.display.shift||e.extend);for(var a=0;a=t.ch:o.to>t.ch))){if(n){ge(l,"beforeCursorEnter");if(l.explicitlyCleared){if(!a.markedSpans){break}else{--s;continue}}}if(!l.atomic){continue}if(r){var f=l.find(i<0?1:-1),h=void 0;if(i<0?c:u){f=Ka(e,f,-i,f&&f.line==t.line?a:null)}if(f&&f.line==t.line&&(h=at(f,r))&&(i<0?h<0:h>0)){return Ga(e,f,t,i,n)}}var p=l.find(i<0?-1:1);if(i<0?u:c){p=Ka(e,p,i,p.line==t.line?a:null)}return p?Ga(e,p,t,i,n):null}}}return t}function qa(e,t,r,i,n){var a=i||1;var s=Ga(e,t,r,a,n)||!n&&Ga(e,t,r,a,true)||Ga(e,t,r,-a,n)||!n&&Ga(e,t,r,-a,true);if(!s){e.cantEdit=true;return nt(e.first,0)}return s}function Ka(e,t,r,i){if(r<0&&t.ch==0){if(t.line>e.first){return ft(e,nt(t.line-1))}else{return null}}else if(r>0&&t.ch==(i||Ye(e,t.line)).text.length){if(t.line=0;--n){Qa(e,{from:i[n].from,to:i[n].to,text:n?[""]:t.text,origin:t.origin})}}else{Qa(e,t)}}function Qa(e,t){if(t.text.length==1&&t.text[0]==""&&at(t.from,t.to)==0){return}var r=ua(e,t);Sa(e,t,r,e.cm?e.cm.curOp.id:NaN);es(e,t,r,Ft(e,t));var i=[];va(e,function(e,r){if(!r&&z(i,e.history)==-1){as(e.history,t);i.push(e.history)}es(e,t,null,Ft(e,t))})}function Za(e,t,r){var i=e.cm&&e.cm.state.suppressEdits;if(i&&!r){return}var n=e.history,a,s=e.sel;var o=t=="undo"?n.done:n.undone,l=t=="undo"?n.undone:n.done;var u=0;for(;u=0;--p){var d=h(p);if(d)return d.v}}function Ja(e,t){if(t==0){return}e.first+=t;e.sel=new ia(Y(e.sel.ranges,function(e){return new na(nt(e.anchor.line+t,e.anchor.ch),nt(e.head.line+t,e.head.ch))}),e.sel.primIndex);if(e.cm){Di(e.cm,e.first,e.first-t,t);for(var r=e.cm.display,i=r.viewFrom;ie.lastLine()){return}if(t.from.linea){t={from:t.from,to:nt(a,Ye(e,a).text.length),text:[t.text[0]],origin:t.origin}}t.removed=Qe(e,t.from,t.to);if(!r){r=ua(e,t)}if(e.cm){ts(e.cm,t,i)}else{ma(e,t,i)}Ba(e,r,j);if(e.cantEdit&&qa(e,nt(e.firstLine(),0))){e.cantEdit=false}}function ts(e,t,r){var i=e.doc,n=e.display,a=t.from,s=t.to;var o=false,l=a.line;if(!e.options.lineWrapping){l=et(Qt(Ye(i,a.line)));i.iter(l,s.line+1,function(e){if(e==n.maxLine){o=true;return true}})}if(i.sel.contains(t.from,t.to)>-1){xe(e)}ma(i,t,r,_i(e));if(!e.options.lineWrapping){i.iter(l,a.line+t.text.length,function(e){var t=ar(e);if(t>n.maxLineLength){n.maxLine=e;n.maxLineLength=t;n.maxLineChanged=true;o=false}});if(o){e.curOp.updateMaxLine=true}}At(i,a.line);On(e,400);var u=t.text.length-(s.line-a.line)-1;if(t.full){Di(e)}else if(a.line==s.line&&t.text.length==1&&!da(e.doc,t)){Ri(e,a.line,"text")}else{Di(e,a.line,s.line+1,u)}var c=be(e,"changes"),f=be(e,"change");if(f||c){var h={from:a,to:s,text:t.text,removed:t.removed,origin:t.origin};if(f){Ar(e,"change",e,h)}if(c){(e.curOp.changeObjs||(e.curOp.changeObjs=[])).push(h)}}e.display.selForContextMenu=null}function rs(e,t,r,i,n){var a;if(!i){i=r}if(at(i,r)<0){a=[i,r],r=a[0],i=a[1]}if(typeof t=="string"){t=e.splitLines(t)}Ya(e,{from:r,to:i,text:t,origin:n})}function is(e,t,r,i){if(r1||!(this.children[0]instanceof os))){var l=[];this.collapse(l);this.children=[new os(l)];this.children[0].parent=this}},collapse:function(e){var t=this;for(var r=0;r50){var o=a.lines.length%25+25;for(var l=o;l10);e.parent.maybeSpill()},iterN:function(e,t,r){var i=this;for(var n=0;nt.display.maxLineLength){t.display.maxLine=c;t.display.maxLineLength=f;t.display.maxLineChanged=true}}}if(n!=null&&t&&this.collapsed){Di(t,n,a+1)}this.lines.length=0;this.explicitlyCleared=true;if(this.atomic&&this.doc.cantEdit){this.doc.cantEdit=false;if(t){ja(t.doc)}}if(t){Ar(t,"markerCleared",t,this,n,a)}if(r){Cn(t)}if(this.parent){this.parent.clear()}};ps.prototype.find=function(e,t){var r=this;if(e==null&&this.type=="bookmark"){e=1}var i,n;for(var a=0;a0||s==0&&a.clearWhenEmpty!==false){return a}if(a.replacedWith){a.collapsed=true;a.widgetNode=M("span",[a.replacedWith],"CodeMirror-widget");if(!i.handleMouseEvents){a.widgetNode.setAttribute("cm-ignore-events","true")}if(i.insertLeft){a.widgetNode.insertLeft=true}}if(a.collapsed){if(Yt(e,t.line,t,r,a)||t.line!=r.line&&Yt(e,r.line,t,r,a)){throw new Error("Inserting collapsed marker partially overlapping an existing one")}_t()}if(a.addToHistory){Sa(e,{from:t,to:r,origin:"markText"},e.sel,NaN)}var o=t.line,l=e.cm,u;e.iter(o,r.line+1,function(e){if(l&&a.collapsed&&!l.options.lineWrapping&&Qt(e)==l.display.maxLine){u=true}if(a.collapsed&&o!=t.line){Je(e,0)}Dt(e,new Pt(a,o==t.line?t.ch:null,o==r.line?r.ch:null));++o});if(a.collapsed){e.iter(t.line,r.line+1,function(t){if(rr(e,t)){Je(t,0)}})}if(a.clearOnEnter){de(a,"beforeCursorEnter",function(){return a.clear()})}if(a.readOnly){Nt();if(e.history.done.length||e.history.undone.length){e.clearHistory()}}if(a.collapsed){a.id=++hs;a.atomic=true}if(l){if(u){l.curOp.updateMaxLine=true}if(a.collapsed){Di(l,t.line,r.line+1)}else if(a.className||a.startStyle||a.endStyle||a.css||a.attributes||a.title){for(var c=t.line;c<=r.line;c++){Ri(l,c,"text")}}if(a.atomic){ja(l.doc)}Ar(l,"markerAdded",l,a)}return a}var ms=function(e,t){var r=this;this.markers=e;this.primary=t;for(var i=0;i=0;u--){Ya(i,n[u])}if(l){Wa(this,l)}else if(this.cm){sn(this.cm)}}),undo:In(function(){Za(this,"undo")}),redo:In(function(){Za(this,"redo")}),undoSelection:In(function(){Za(this,"undo",true)}),redoSelection:In(function(){Za(this,"redo",true)}),setExtending:function(e){this.extend=e},getExtending:function(){return this.extend},historySize:function(){var e=this.history,t=0,r=0;for(var i=0;i=e.ch)){t.push(n.marker.parent||n.marker)}}}return t},findMarks:function(e,t,r){e=ft(this,e);t=ft(this,t);var i=[],n=e.line;this.iter(e.line,t.line+1,function(a){var s=a.markedSpans;if(s){for(var o=0;o=l.to||l.from==null&&n!=e.line||l.from!=null&&n==t.line&&l.from>=t.ch)&&(!r||r(l.marker))){i.push(l.marker.parent||l.marker)}}}++n});return i},getAllMarks:function(){var e=[];this.iter(function(t){var r=t.markedSpans;if(r){for(var i=0;ie){t=e;return true}e-=a;++r});return ft(this,nt(r,t))},indexFromPos:function(e){e=ft(this,e);var t=e.ch;if(e.linet){t=e.from}if(e.to!=null&&e.to-1){t.state.draggingText(e);setTimeout(function(){return t.display.input.focus()},20);return}try{var c=e.dataTransfer.getData("Text");if(c){var f;if(t.state.draggingText&&!t.state.draggingText.copy){f=t.listSelections()}Ba(t.doc,sa(r,r));if(f){for(var h=0;h=0;t--){rs(e.doc,"",i[t].from,i[t].to,"+delete")}sn(e)})}function Gs(e,t,r){var i=se(e.text,t+r,r);return i<0||i>e.text.length?null:i}function qs(e,t,r){var i=Gs(e,t.ch,r);return i==null?null:new nt(t.line,i,r<0?"after":"before")}function Ks(e,t,r,i,n){if(e){var a=he(r,t.doc.direction);if(a){var s=n<0?X(a):a[0];var o=n<0==(s.level==1);var l=o?"after":"before";var u;if(s.level>0||t.doc.direction=="rtl"){var c=ei(t,r);u=n<0?r.text.length-1:0;var f=ti(t,c,u).top;u=oe(function(e){return ti(t,c,e).top==f},n<0==(s.level==1)?s.from:s.to-1,u);if(l=="before"){u=Gs(r,u,1)}}else{u=n<0?s.to:s.from}return new nt(i,u,l)}}return new nt(i,n<0?r.text.length:0,n<0?"before":"after")}function $s(e,t,r,i){var n=he(t,e.doc.direction);if(!n){return qs(t,r,i)}if(r.ch>=t.text.length){r.ch=t.text.length;r.sticky="before"}else if(r.ch<=0){r.ch=0;r.sticky="after"}var a=ce(n,r.ch,r.sticky),s=n[a];if(e.doc.direction=="ltr"&&s.level%2==0&&(i>0?s.to>r.ch:s.from=s.from&&h>=c.begin:h<=s.to&&h<=c.end)){var p=f?"before":"after";return new nt(r.line,h,p)}}var d=function(e,t,i){var a=function(e,t){return t?new nt(r.line,o(e,1),"before"):new nt(r.line,e,"after")};for(;e>=0&&e0==(s.level!=1);var u=l?i.begin:o(i.end,-1);if(s.from<=u&&u0?c.end:o(c.begin,-1);if(v!=null&&!(i>0&&v==t.text.length)){m=d(i>0?0:n.length-1,i,u(v));if(m){return m}}return null}var Xs={selectAll:$a,singleSelection:function(e){return e.setSelection(e.getCursor("anchor"),e.getCursor("head"),j)},killLine:function(e){return Us(e,function(t){if(t.empty()){var r=Ye(e.doc,t.head.line).text.length;if(t.head.ch==r&&t.head.line0){n=new nt(n.line,n.ch+1);e.replaceRange(a.charAt(n.ch-1)+a.charAt(n.ch-2),nt(n.line,n.ch-2),n,"+transpose")}else if(n.line>e.doc.first){var s=Ye(e.doc,n.line-1).text;if(s){n=new nt(n.line,1);e.replaceRange(a.charAt(0)+e.doc.lineSeparator()+s.charAt(s.length-1),nt(n.line-1,s.length-1),n,"+transpose")}}}r.push(new na(n,n))}e.setSelections(r)})},newlineAndIndent:function(e){return Nn(e,function(){var t=e.listSelections();for(var r=t.length-1;r>=0;r--){e.replaceRange(e.doc.lineSeparator(),t[r].anchor,t[r].head,"+input")}t=e.listSelections();for(var i=0;ie&&at(t,this.pos)==0&&r==this.button};var po,mo;function vo(e,t){var r=+new Date;if(mo&&mo.compare(r,e,t)){po=mo=null;return"triple"}else if(po&&po.compare(r,e,t)){mo=new ho(r,e,t);po=null;return"double"}else{po=new ho(r,e,t);mo=null;return"single"}}function go(e){var t=this,r=t.display;if(ye(t,e)||r.activeTouch&&r.input.supportsTouch()){return}r.input.ensurePolled();r.shift=e.shiftKey;if(Hr(r,e)){if(!l){r.scroller.draggable=false;setTimeout(function(){return r.scroller.draggable=true},100)}return}if(To(t,e)){return}var i=Ii(t,e),n=Ae(e),a=i?vo(i,n):"single";window.focus();if(n==1&&t.state.selectingText){t.state.selectingText(e)}if(i&&yo(t,n,i,a,e)){return}if(n==1){if(i){bo(t,i,a,e)}else if(Te(e)==r.scroller){ke(e)}}else if(n==2){if(i){Oa(t.doc,i)}setTimeout(function(){return r.input.focus()},20)}else if(n==3){if(C){t.display.input.onContextMenu(e)}else{$i(t)}}}function yo(e,t,r,i,n){var a="Click";if(i=="double"){a="Double"+a}else if(i=="triple"){a="Triple"+a}a=(t==1?"Left":t==2?"Middle":"Right")+a;return ro(e,Bs(a,n),n,function(t){if(typeof t=="string"){t=Xs[t]}if(!t){return false}var i=false;try{if(e.isReadOnly()){e.state.suppressEdits=true}i=t(e,r)!=H}finally{e.state.suppressEdits=false}return i})}function xo(e,t,r){var i=e.getOption("configureMouse");var n=i?i(e,t,r):{};if(n.unit==null){var a=x?r.shiftKey&&r.metaKey:r.altKey;n.unit=a?"rectangle":t=="single"?"char":t=="double"?"word":"line"}if(n.extend==null||e.doc.extend){n.extend=e.doc.extend||r.shiftKey}if(n.addNew==null){n.addNew=y?r.metaKey:r.ctrlKey}if(n.moveOnDrag==null){n.moveOnDrag=!(y?r.altKey:r.ctrlKey)}return n}function bo(e,t,r,i){if(s){setTimeout(R(Ki,e),0)}else{e.curOp.focus=P()}var n=xo(e,r,i);var a=e.doc.sel,o;if(e.options.dragDrop&&Ee&&!e.isReadOnly()&&r=="single"&&(o=a.contains(t))>-1&&(at((o=a.ranges[o]).from(),t)<0||t.xRel>0)&&(at(o.to(),t)>0||t.xRel<0)){wo(e,i,t,n)}else{Co(e,i,t,n)}}function wo(e,t,r,i){var n=e.display,a=false;var u=_n(e,function(t){if(l){n.scroller.draggable=false}e.state.draggingText=false;ve(n.wrapper.ownerDocument,"mouseup",u);ve(n.wrapper.ownerDocument,"mousemove",c);ve(n.scroller,"dragstart",f);ve(n.scroller,"drop",u);if(!a){ke(t);if(!i.addNew){Oa(e.doc,r,null,null,i.extend)}if(l||s&&o==9){setTimeout(function(){n.wrapper.ownerDocument.body.focus();n.input.focus()},20)}else{n.input.focus()}}});var c=function(e){a=a||Math.abs(t.clientX-e.clientX)+Math.abs(t.clientY-e.clientY)>=10};var f=function(){return a=true};if(l){n.scroller.draggable=true}e.state.draggingText=u;u.copy=!i.moveOnDrag;if(n.scroller.dragDrop){n.scroller.dragDrop()}de(n.wrapper.ownerDocument,"mouseup",u);de(n.wrapper.ownerDocument,"mousemove",c);de(n.scroller,"dragstart",f);de(n.scroller,"drop",u);$i(e);setTimeout(function(){return n.input.focus()},20)}function ko(e,t,r){if(r=="char"){return new na(t,t)}if(r=="word"){return e.findWordAt(t)}if(r=="line"){return new na(nt(t.line,0),ft(e.doc,nt(t.line+1,0)))}var i=r(e,t);return new na(i.from,i.to)}function Co(e,t,r,i){var n=e.display,a=e.doc;ke(t);var s,o,l=a.sel,u=l.ranges;if(i.addNew&&!i.extend){o=a.sel.contains(r);if(o>-1){s=u[o]}else{s=new na(r,r)}}else{s=a.sel.primary();o=a.sel.primIndex}if(i.unit=="rectangle"){if(!i.addNew){s=new na(r,r)}r=Ii(e,t,true,true);o=-1}else{var c=ko(e,r,i.unit);if(i.extend){s=Ia(s,c.anchor,c.head,i.extend)}else{s=c}}if(!i.addNew){o=0;za(a,new ia([s],0),U);l=a.sel}else if(o==-1){o=u.length;za(a,aa(e,u.concat([s]),o),{scroll:false,origin:"*mouse"})}else if(u.length>1&&u[o].empty()&&i.unit=="char"&&!i.extend){za(a,aa(e,u.slice(0,o).concat(u.slice(o+1)),0),{scroll:false,origin:"*mouse"});l=a.sel}else{Ra(a,o,s,U)}var f=r;function h(t){if(at(f,t)==0){return}f=t;if(i.unit=="rectangle"){var n=[],u=e.options.tabSize;var c=F(Ye(a,r.line).text,r.ch,u);var h=F(Ye(a,t.line).text,t.ch,u);var p=Math.min(c,h),d=Math.max(c,h);for(var m=Math.min(r.line,t.line),v=Math.min(e.lastLine(),Math.max(r.line,t.line));m<=v;m++){var g=Ye(a,m).text,y=q(g,p,u);if(p==d){n.push(new na(nt(m,y),nt(m,y)))}else if(g.length>y){n.push(new na(nt(m,y),nt(m,q(g,d,u))))}}if(!n.length){n.push(new na(r,r))}za(a,aa(e,l.ranges.slice(0,o).concat(n),o),{origin:"*mouse",scroll:false});e.scrollIntoView(t)}else{var x=s;var b=ko(e,t,i.unit);var w=x.anchor,k;if(at(b.anchor,w)>0){k=b.head;w=ut(x.from(),b.anchor)}else{k=b.anchor;w=lt(x.to(),b.head)}var C=l.ranges.slice(0);C[o]=So(e,new na(ft(a,w),k));za(a,aa(e,C,o),U)}}var p=n.wrapper.getBoundingClientRect();var d=0;function m(t){var r=++d;var s=Ii(e,t,true,i.unit=="rectangle");if(!s){return}if(at(s,f)!=0){e.curOp.focus=P();h(s);var o=Ji(n,a);if(s.line>=o.to||s.linep.bottom?20:0;if(l){setTimeout(_n(e,function(){if(d!=r){return}n.scroller.scrollTop+=l;m(t)}),50)}}}function v(t){e.state.selectingText=false;d=Infinity;if(t){ke(t);n.input.focus()}ve(n.wrapper.ownerDocument,"mousemove",g);ve(n.wrapper.ownerDocument,"mouseup",y);a.history.lastSelOrigin=null}var g=_n(e,function(e){if(e.buttons===0||!Ae(e)){v(e)}else{m(e)}});var y=_n(e,v);e.state.selectingText=y;de(n.wrapper.ownerDocument,"mousemove",g);de(n.wrapper.ownerDocument,"mouseup",y)}function So(e,t){var r=t.anchor;var i=t.head;var n=Ye(e.doc,r.line);if(at(r,i)==0&&r.sticky==i.sticky){return t}var a=he(n);if(!a){return t}var s=ce(a,r.ch,r.sticky),o=a[s];if(o.from!=r.ch&&o.to!=r.ch){return t}var l=s+(o.from==r.ch==(o.level!=1)?0:1);if(l==0||l==a.length){return t}var u;if(i.line!=r.line){u=(i.line-r.line)*(e.doc.direction=="ltr"?1:-1)>0}else{var c=ce(a,i.ch,i.sticky);var f=c-s||(i.ch-r.ch)*(o.level==1?-1:1);if(c==l-1||c==l){u=f<0}else{u=f>0}}var h=a[l+(u?-1:0)];var p=u==(h.level==1);var d=p?h.from:h.to,m=p?"after":"before";return r.ch==d&&r.sticky==m?t:new na(new nt(r.line,d,m),i)}function Lo(e,t,r,i){var n,a;if(t.touches){n=t.touches[0].clientX;a=t.touches[0].clientY}else{try{n=t.clientX;a=t.clientY}catch(t){return false}}if(n>=Math.floor(e.display.gutters.getBoundingClientRect().right)){return false}if(i){ke(t)}var s=e.display;var o=s.lineDiv.getBoundingClientRect();if(a>o.bottom||!be(e,r)){return Se(t)}a-=o.top-s.viewOffset;for(var l=0;l=n){var c=tt(e.doc,a);var f=e.display.gutterSpecs[l];ge(e,r,e,c,f.className,t);return Se(t)}}}function To(e,t){return Lo(e,t,"gutterClick",true)}function Ao(e,t){if(Hr(e.display,t)||Eo(e,t)){return}if(ye(e,t,"contextmenu")){return}if(!C){e.display.input.onContextMenu(t)}}function Eo(e,t){if(!be(e,"gutterContextMenu")){return false}return Lo(e,t,"gutterContextMenu",false)}function Mo(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-");ui(e)}var No={toString:function(){return"CodeMirror.Init"}};var _o={};var Po={};function Io(e){var t=e.optionHandlers;function r(r,i,n,a){e.defaults[r]=i;if(n){t[r]=a?function(e,t,r){if(r!=No){n(e,t,r)}}:n}}e.defineOption=r;e.Init=No;r("value","",function(e,t){return e.setValue(t)},true);r("mode",null,function(e,t){e.doc.modeOption=t;ha(e)},true);r("indentUnit",2,ha,true);r("indentWithTabs",false);r("smartIndent",true);r("tabSize",4,function(e){pa(e);ui(e);Di(e)},true);r("lineSeparator",null,function(e,t){e.doc.lineSep=t;if(!t){return}var r=[],i=e.doc.first;e.doc.iter(function(e){for(var n=0;;){var a=e.text.indexOf(t,n);if(a==-1){break}n=a+t.length;r.push(nt(i,a))}i++});for(var n=r.length-1;n>=0;n--){rs(e.doc,t,r[n],nt(r[n].line,r[n].ch+t.length))}});r("specialChars",/[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g,function(e,t,r){e.state.specialChars=new RegExp(t.source+(t.test("\t")?"":"|\t"),"g");if(r!=No){e.refresh()}});r("specialCharPlaceholder",dr,function(e){return e.refresh()},true);r("electricChars",true);r("inputStyle",g?"contenteditable":"textarea",function(){throw new Error("inputStyle can not (yet) be changed in a running editor")},true);r("spellcheck",false,function(e,t){return e.getInputField().spellcheck=t},true);r("autocorrect",false,function(e,t){return e.getInputField().autocorrect=t},true);r("autocapitalize",false,function(e,t){return e.getInputField().autocapitalize=t},true);r("rtlMoveVisually",!b);r("wholeLineUpdateBefore",true);r("theme","default",function(e){Mo(e);Yn(e)},true);r("keyMap","default",function(e,t,r){var i=js(t);var n=r!=No&&js(r);if(n&&n.detach){n.detach(e,i)}if(i.attach){i.attach(e,n||null)}});r("extraKeys",null);r("configureMouse",null);r("lineWrapping",false,Do,true);r("gutters",[],function(e,t){e.display.gutterSpecs=$n(t,e.options.lineNumbers);Yn(e)},true);r("fixedGutter",true,function(e,t){e.display.gutters.style.left=t?Ni(e.display)+"px":"0";e.refresh()},true);r("coverGutterNextToScrollbar",false,function(e){return gn(e)},true);r("scrollbarStyle","native",function(e){bn(e);gn(e);e.display.scrollbars.setScrollTop(e.doc.scrollTop);e.display.scrollbars.setScrollLeft(e.doc.scrollLeft)},true);r("lineNumbers",false,function(e,t){e.display.gutterSpecs=$n(e.options.gutters,t);Yn(e)},true);r("firstLineNumber",1,Yn,true);r("lineNumberFormatter",function(e){return e},Yn,true);r("showCursorWhenSelecting",false,Bi,true);r("resetSelectionOnContextMenu",true);r("lineWiseCopyCut",true);r("pasteLinesPerSelection",true);r("selectionsMayTouch",false);r("readOnly",false,function(e,t){if(t=="nocursor"){Yi(e);e.display.input.blur()}e.display.input.readOnlyChanged(t)});r("disableInput",false,function(e,t){if(!t){e.display.input.reset()}},true);r("dragDrop",true,Oo);r("allowDropFileTypes",null);r("cursorBlinkRate",530);r("cursorScrollMargin",0);r("cursorHeight",1,Bi,true);r("singleCursorHeightPerLine",true,Bi,true);r("workTime",100);r("workDelay",100);r("flattenSpans",true,pa,true);r("addModeClass",false,pa,true);r("pollInterval",100);r("undoDepth",200,function(e,t){return e.doc.history.undoDepth=t});r("historyEventDelay",1250);r("viewportMargin",10,function(e){return e.refresh()},true);r("maxHighlightLength",1e4,pa,true);r("moveInputWithCursor",true,function(e,t){if(!t){e.display.input.resetPosition()}});r("tabindex",null,function(e,t){return e.display.input.getField().tabIndex=t||""});r("autofocus",null);r("direction","ltr",function(e,t){return e.doc.setDirection(t)},true);r("phrases",null)}function Oo(e,t,r){var i=r&&r!=No;if(!t!=!i){var n=e.display.dragFunctions;var a=t?de:ve;a(e.display.scroller,"dragstart",n.start);a(e.display.scroller,"dragenter",n.enter);a(e.display.scroller,"dragover",n.over);a(e.display.scroller,"dragleave",n.leave);a(e.display.scroller,"drop",n.drop)}}function Do(e){if(e.options.lineWrapping){I(e.display.wrapper,"CodeMirror-wrap");e.display.sizer.style.minWidth="";e.display.sizerWidth=null}else{L(e.display.wrapper,"CodeMirror-wrap");sr(e)}Pi(e);Di(e);ui(e);setTimeout(function(){return gn(e)},100)}function Ro(e,t){var r=this;if(!(this instanceof Ro)){return new Ro(e,t)}this.options=t=t?V(t):{};V(_o,t,false);var i=t.value;if(typeof i=="string"){i=new ws(i,t.mode,null,t.lineSeparator,t.direction)}else if(t.mode){i.modeOption=t.mode}this.doc=i;var n=new Ro.inputStyles[t.inputStyle](this);var a=this.display=new Qn(e,i,n,t);a.wrapper.CodeMirror=this;Mo(this);if(t.lineWrapping){this.display.wrapper.className+=" CodeMirror-wrap"}bn(this);this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:false,delayingBlurEvent:false,focused:false,suppressEdits:false,pasteIncoming:-1,cutIncoming:-1,selectingText:false,draggingText:false,highlight:new W,keySeq:null,specialChars:null};if(t.autofocus&&!g){a.input.focus()}if(s&&o<11){setTimeout(function(){return r.display.input.reset(true)},20)}Vo(this);Ms();kn(this);this.curOp.forceUpdate=true;ga(this,i);if(t.autofocus&&!g||this.hasFocus()){setTimeout(R(Xi,this),20)}else{Yi(this)}for(var u in Po){if(Po.hasOwnProperty(u)){Po[u](r,t[u],No)}}Kn(this);if(t.finishInit){t.finishInit(this)}for(var c=0;c20*20}de(t.scroller,"touchstart",function(n){if(!ye(e,n)&&!a(n)&&!To(e,n)){t.input.ensurePolled();clearTimeout(r);var s=+new Date;t.activeTouch={start:s,moved:false,prev:s-i.end<=300?i:null};if(n.touches.length==1){t.activeTouch.left=n.touches[0].pageX;t.activeTouch.top=n.touches[0].pageY}}});de(t.scroller,"touchmove",function(){if(t.activeTouch){t.activeTouch.moved=true}});de(t.scroller,"touchend",function(r){var i=t.activeTouch;if(i&&!Hr(t,r)&&i.left!=null&&!i.moved&&new Date-i.start<300){var a=e.coordsChar(t.activeTouch,"page"),s;if(!i.prev||l(i,i.prev)){s=new na(a,a)}else if(!i.prev.prev||l(i,i.prev.prev)){s=e.findWordAt(a)}else{s=new na(nt(a.line,0),ft(e.doc,nt(a.line+1,0)))}e.setSelection(s.anchor,s.head);e.focus();ke(r)}n()});de(t.scroller,"touchcancel",n);de(t.scroller,"scroll",function(){if(t.scroller.clientHeight){fn(e,t.scroller.scrollTop);pn(e,t.scroller.scrollLeft,true);ge(e,"scroll",e)}});de(t.scroller,"mousewheel",function(t){return ra(e,t)});de(t.scroller,"DOMMouseScroll",function(t){return ra(e,t)});de(t.wrapper,"scroll",function(){return t.wrapper.scrollTop=t.wrapper.scrollLeft=0});t.dragFunctions={enter:function(t){if(!ye(e,t)){Le(t)}},over:function(t){if(!ye(e,t)){Ls(e,t);Le(t)}},start:function(t){return Ss(e,t)},drop:_n(e,Cs),leave:function(t){if(!ye(e,t)){Ts(e)}}};var u=t.input.getField();de(u,"keyup",function(t){return uo.call(e,t)});de(u,"keydown",_n(e,oo));de(u,"keypress",_n(e,co));de(u,"focus",function(t){return Xi(e,t)});de(u,"blur",function(t){return Yi(e,t)})}var Fo=[];Ro.defineInitHook=function(e){return Fo.push(e)};function Wo(e,t,r,i){var n=e.doc,a;if(r==null){r="add"}if(r=="smart"){if(!n.mode.indent){r="prev"}else{a=yt(e,t).state}}var s=e.options.tabSize;var o=Ye(n,t),l=F(o.text,null,s);if(o.stateAfter){o.stateAfter=null}var u=o.text.match(/^\s*/)[0],c;if(!i&&!/\S/.test(o.text)){c=0;r="not"}else if(r=="smart"){c=n.mode.indent(a,o.text.slice(u.length),o.text);if(c==H||c>150){if(!i){return}r="prev"}}if(r=="prev"){if(t>n.first){c=F(Ye(n,t-1).text,null,s)}else{c=0}}else if(r=="add"){c=l+e.options.indentUnit}else if(r=="subtract"){c=l-e.options.indentUnit}else if(typeof r=="number"){c=l+r}c=Math.max(0,c);var f="",h=0;if(e.options.indentWithTabs){for(var p=Math.floor(c/s);p;--p){h+=s;f+="\t"}}if(hs;var l=Ie(t),u=null;if(o&&i.ranges.length>1){if(zo&&zo.text.join("\n")==t){if(i.ranges.length%zo.text.length==0){u=[];for(var c=0;c=0;h--){var p=i.ranges[h];var d=p.from(),m=p.to();if(p.empty()){if(r&&r>0){d=nt(d.line,d.ch-r)}else if(e.state.overwrite&&!o){m=nt(m.line,Math.min(Ye(a,m.line).text.length,m.ch+X(l).length))}else if(o&&zo&&zo.lineWise&&zo.text.join("\n")==t){d=m=nt(d.line,0)}}var v={from:d,to:m,text:u?u[h%u.length]:l,origin:n||(o?"paste":e.state.cutIncoming>s?"cut":"+input")};Ya(e.doc,v);Ar(e,"inputRead",e,v)}if(t&&!o){Uo(e,t)}sn(e);if(e.curOp.updateInput<2){e.curOp.updateInput=f}e.curOp.typing=true;e.state.pasteIncoming=e.state.cutIncoming=-1}function jo(e,t){var r=e.clipboardData&&e.clipboardData.getData("Text");if(r){e.preventDefault();if(!t.isReadOnly()&&!t.options.disableInput){Nn(t,function(){return Ho(t,r,0,null,"paste")})}return true}}function Uo(e,t){if(!e.options.electricChars||!e.options.smartIndent){return}var r=e.doc.sel;for(var i=r.ranges.length-1;i>=0;i--){var n=r.ranges[i];if(n.head.ch>100||i&&r.ranges[i-1].head.line==n.head.line){continue}var a=e.getModeAt(n.head);var s=false;if(a.electricChars){for(var o=0;o-1){s=Wo(e,n.head.line,"smart");break}}}else if(a.electricInput){if(a.electricInput.test(Ye(e.doc,n.head.line).text.slice(0,n.head.ch))){s=Wo(e,n.head.line,"smart")}}if(s){Ar(e,"electricInput",e,n.head.line)}}}function Go(e){var t=[],r=[];for(var i=0;i0){Ra(t.doc,n,new na(s,c[n].to()),j)}}else if(a.head.line>i){Wo(t,a.head.line,e,true);i=a.head.line;if(n==t.doc.sel.primIndex){sn(t)}}}}),getTokenAt:function(e,t){return Ct(this,e,t)},getLineTokens:function(e,t){return Ct(this,nt(e),t,true)},getTokenTypeAt:function(e){e=ft(this.doc,e);var t=gt(this,Ye(this.doc,e.line));var r=0,i=(t.length-1)/2,n=e.ch;var a;if(n==0){a=t[2]}else{for(;;){var s=r+i>>1;if((s?t[s*2-1]:0)>=n){i=s}else if(t[s*2+1]a){e=a;i=true}n=Ye(this.doc,e)}else{n=e}return pi(this,n,{top:0,left:0},t||"page",r||i).top+(i?this.doc.height-nr(n):0)},defaultTextHeight:function(){return Ai(this.display)},defaultCharWidth:function(){return Ei(this.display)},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(e,t,r,i,n){var a=this.display;e=vi(this,ft(this.doc,e));var s=e.bottom,o=e.left;t.style.position="absolute";t.setAttribute("cm-ignore-events","true");this.display.input.setUneditable(t);a.sizer.appendChild(t);if(i=="over"){s=e.top}else if(i=="above"||i=="near"){var l=Math.max(a.wrapper.clientHeight,this.doc.height),u=Math.max(a.sizer.clientWidth,a.lineSpace.clientWidth);if((i=="above"||e.bottom+t.offsetHeight>l)&&e.top>t.offsetHeight){s=e.top-t.offsetHeight}else if(e.bottom+t.offsetHeight<=l){s=e.bottom}if(o+t.offsetWidth>u){o=u-t.offsetWidth}}t.style.top=s+"px";t.style.left=t.style.right="";if(n=="right"){o=a.sizer.clientWidth-t.offsetWidth;t.style.right="0px"}else{if(n=="left"){o=0}else if(n=="middle"){o=(a.sizer.clientWidth-t.offsetWidth)/2}t.style.left=o+"px"}if(r){rn(this,{left:o,top:s,right:o+t.offsetWidth,bottom:s+t.offsetHeight})}},triggerOnKeyDown:Pn(oo),triggerOnKeyPress:Pn(co),triggerOnKeyUp:uo,triggerOnMouseDown:Pn(go),execCommand:function(e){if(Xs.hasOwnProperty(e)){return Xs[e].call(null,this)}},triggerElectric:Pn(function(e){Uo(this,e)}),findPosH:function(e,t,r,i){var n=this;var a=1;if(t<0){a=-1;t=-t}var s=ft(this.doc,e);for(var o=0;o0&&o(r.charAt(i-1))){--i}while(n.5){Pi(this)}ge(this,"refresh",this)}),swapDoc:Pn(function(e){var t=this.doc;t.cm=null;if(this.state.selectingText){this.state.selectingText()}ga(this,e);ui(this);this.display.input.reset();on(this,e.scrollLeft,e.scrollTop);this.curOp.forceScroll=true;Ar(this,"swapDoc",this,t);return t}),phrase:function(e){var t=this.options.phrases;return t&&Object.prototype.hasOwnProperty.call(t,e)?t[e]:e},getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}};we(e);e.registerHelper=function(t,i,n){if(!r.hasOwnProperty(t)){r[t]=e[t]={_global:[]}}r[t][i]=n};e.registerGlobalHelper=function(t,i,n,a){e.registerHelper(t,i,a);r[t]._global.push({pred:n,val:a})}}function Xo(e,t,r,i,n){var a=t;var s=r;var o=Ye(e,t.line);function l(){var i=t.line+r;if(i=e.first+e.size){return false}t=new nt(i,t.ch,t.sticky);return o=Ye(e,i)}function u(i){var a;if(n){a=$s(e.cm,o,t,r)}else{a=qs(o,t,r)}if(a==null){if(!i&&l()){t=Ks(n,e.cm,o,t.line,r)}else{return false}}else{t=a}return true}if(i=="char"){u()}else if(i=="column"){u(true)}else if(i=="word"||i=="group"){var c=null,f=i=="group";var h=e.cm&&e.cm.getHelper(t,"wordChars");for(var p=true;;p=false){if(r<0&&!u(!p)){break}var d=o.text.charAt(t.ch)||"\n";var m=re(d,h)?"w":f&&d=="\n"?"n":!f||/\s/.test(d)?null:"p";if(f&&!p&&!m){m="s"}if(c&&c!=m){if(r<0){r=1;u();t.sticky="after"}break}if(m){c=m}if(r>0&&!u(!p)){break}}}var v=qa(e,t,a,s,true);if(st(a,v)){v.hitSide=true}return v}function Yo(e,t,r,i){var n=e.doc,a=t.left,s;if(i=="page"){var o=Math.min(e.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight);var l=Math.max(o-.5*Ai(e.display),3);s=(r>0?t.bottom:t.top)+r*l}else if(i=="line"){s=r>0?t.bottom+3:t.top-3}var u;for(;;){u=xi(e,a,s);if(!u.outside){break}if(r<0?s<=0:s>=n.height){u.hitSide=true;break}s+=r*5}return u}var Qo=function(e){this.cm=e;this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null;this.polling=new W;this.composing=null;this.gracePeriod=false;this.readDOMTimeout=null};Qo.prototype.init=function(e){var t=this;var r=this,i=r.cm;var n=r.div=e.lineDiv;qo(n,i.options.spellcheck,i.options.autocorrect,i.options.autocapitalize);de(n,"paste",function(e){if(ye(i,e)||jo(e,i)){return}if(o<=11){setTimeout(_n(i,function(){return t.updateFromDOM()}),20)}});de(n,"compositionstart",function(e){t.composing={data:e.data,done:false}});de(n,"compositionupdate",function(e){if(!t.composing){t.composing={data:e.data,done:false}}});de(n,"compositionend",function(e){if(t.composing){if(e.data!=t.composing.data){t.readFromDOMSoon()}t.composing.done=true}});de(n,"touchstart",function(){return r.forceCompositionEnd()});de(n,"input",function(){if(!t.composing){t.readFromDOMSoon()}});function a(e){if(ye(i,e)){return}if(i.somethingSelected()){Bo({lineWise:false,text:i.getSelections()});if(e.type=="cut"){i.replaceSelection("",null,"cut")}}else if(!i.options.lineWiseCopyCut){return}else{var t=Go(i);Bo({lineWise:true,text:t.text});if(e.type=="cut"){i.operation(function(){i.setSelections(t.ranges,0,j) +;i.replaceSelection("",null,"cut")})}}if(e.clipboardData){e.clipboardData.clearData();var a=zo.text.join("\n");e.clipboardData.setData("Text",a);if(e.clipboardData.getData("Text")==a){e.preventDefault();return}}var s=Ko(),o=s.firstChild;i.display.lineSpace.insertBefore(s,i.display.lineSpace.firstChild);o.value=zo.text.join("\n");var l=document.activeElement;D(o);setTimeout(function(){i.display.lineSpace.removeChild(s);l.focus();if(l==n){r.showPrimarySelection()}},50)}de(n,"copy",a);de(n,"cut",a)};Qo.prototype.prepareSelection=function(){var e=Hi(this.cm,false);e.focus=this.cm.state.focused;return e};Qo.prototype.showSelection=function(e,t){if(!e||!this.cm.display.view.length){return}if(e.focus||t){this.showPrimarySelection()}this.showMultipleSelections(e)};Qo.prototype.getSelection=function(){return this.cm.display.wrapper.ownerDocument.getSelection()};Qo.prototype.showPrimarySelection=function(){var e=this.getSelection(),t=this.cm,i=t.doc.sel.primary();var n=i.from(),a=i.to();if(t.display.viewTo==t.display.viewFrom||n.line>=t.display.viewTo||a.line=t.display.viewFrom&&Zo(t,n)||{node:l[0].measure.map[2],offset:0};var c=a.linee.firstLine()){i=nt(i.line-1,Ye(e.doc,i.line-1).length)}if(n.ch==Ye(e.doc,n.line).text.length&&n.linet.viewTo-1){return false}var a,s,o;if(i.line==t.viewFrom||(a=Oi(e,i.line))==0){s=et(t.view[0].line);o=t.view[0].node}else{s=et(t.view[a].line);o=t.view[a-1].node.nextSibling}var l=Oi(e,n.line);var u,c;if(l==t.view.length-1){u=t.viewTo-1;c=t.lineDiv.lastChild}else{u=et(t.view[l+1].line)-1;c=t.view[l+1].node.previousSibling}if(!o){return false}var f=e.doc.splitLines(tl(e,o,c,s,u));var h=Qe(e.doc,nt(s,0),nt(u,Ye(e.doc,u).text.length));while(f.length>1&&h.length>1){if(X(f)==X(h)){f.pop();h.pop();u--}else if(f[0]==h[0]){f.shift();h.shift();s++}else{break}}var p=0,d=0;var m=f[0],v=h[0],g=Math.min(m.length,v.length);while(pi.ch&&y.charCodeAt(y.length-d-1)==x.charCodeAt(x.length-d-1)){p--;d++}}f[f.length-1]=y.slice(0,y.length-d).replace(/^\u200b+/,"");f[0]=f[0].slice(p).replace(/\u200b+$/,"");var w=nt(s,p);var k=nt(u,h.length?X(h).length-d:0);if(f.length>1||f[0]||at(w,k)){rs(e.doc,f,w,k,"+input");return true}};Qo.prototype.ensurePolled=function(){this.forceCompositionEnd()};Qo.prototype.reset=function(){this.forceCompositionEnd()};Qo.prototype.forceCompositionEnd=function(){if(!this.composing){return}clearTimeout(this.readDOMTimeout);this.composing=null;this.updateFromDOM();this.div.blur();this.div.focus()};Qo.prototype.readFromDOMSoon=function(){var e=this;if(this.readDOMTimeout!=null){return}this.readDOMTimeout=setTimeout(function(){e.readDOMTimeout=null;if(e.composing){if(e.composing.done){e.composing=null}else{return}}e.updateFromDOM()},80)};Qo.prototype.updateFromDOM=function(){var e=this;if(this.cm.isReadOnly()||!this.pollContent()){Nn(this.cm,function(){return Di(e.cm)})}};Qo.prototype.setUneditable=function(e){e.contentEditable="false"};Qo.prototype.onKeyPress=function(e){if(e.charCode==0||this.composing){return}e.preventDefault();if(!this.cm.isReadOnly()){_n(this.cm,Ho)(this.cm,String.fromCharCode(e.charCode==null?e.keyCode:e.charCode),0)}};Qo.prototype.readOnlyChanged=function(e){this.div.contentEditable=String(e!="nocursor")};Qo.prototype.onContextMenu=function(){};Qo.prototype.resetPosition=function(){};Qo.prototype.needsContentAttribute=true;function Zo(e,t){var r=Jr(e,t.line);if(!r||r.hidden){return null}var i=Ye(e.doc,t.line);var n=Yr(r,i,t.line);var a=he(i,e.doc.direction),s="left";if(a){var o=ce(a,t.ch);s=o%2?"right":"left"}var l=ii(n.map,t.ch,s);l.offset=l.collapse=="right"?l.end:l.start;return l}function Jo(e){for(var t=e;t;t=t.parentNode){if(/CodeMirror-gutter-wrapper/.test(t.className)){return true}}return false}function el(e,t){if(t){e.bad=true}return e}function tl(e,t,r,i,n){var a="",s=false,o=e.doc.lineSeparator(),l=false;function u(e){return function(t){return t.id==e}}function c(){if(s){a+=o;if(l){a+=o}s=l=false}}function f(e){if(e){c();a+=e}}function h(t){if(t.nodeType==1){var r=t.getAttribute("cm-text");if(r){f(r);return}var a=t.getAttribute("cm-marker"),p;if(a){var d=e.findMarks(nt(i,0),nt(n+1,0),u(+a));if(d.length&&(p=d[0].find(0))){f(Qe(e.doc,p.from,p.to).join(o))}return}if(t.getAttribute("contenteditable")=="false"){return}var m=/^(pre|div|p|li|table|br)$/i.test(t.nodeName);if(!/^br$/i.test(t.nodeName)&&t.textContent.length==0){return}if(m){c()}for(var v=0;v=9&&t.hasSelection){t.hasSelection=null}r.poll()});de(n,"paste",function(e){if(ye(i,e)||jo(e,i)){return}i.state.pasteIncoming=+new Date;r.fastPoll()});function a(e){if(ye(i,e)){return}if(i.somethingSelected()){Bo({lineWise:false,text:i.getSelections()})}else if(!i.options.lineWiseCopyCut){return}else{var t=Go(i);Bo({lineWise:true,text:t.text});if(e.type=="cut"){i.setSelections(t.ranges,null,j)}else{r.prevInput="";n.value=t.text.join("\n");D(n)}}if(e.type=="cut"){i.state.cutIncoming=+new Date}}de(n,"cut",a);de(n,"copy",a);de(e.scroller,"paste",function(t){if(Hr(e,t)||ye(i,t)){return}if(!n.dispatchEvent){i.state.pasteIncoming=+new Date;r.focus();return}var a=new Event("paste");a.clipboardData=t.clipboardData;n.dispatchEvent(a)});de(e.lineSpace,"selectstart",function(t){if(!Hr(e,t)){ke(t)}});de(n,"compositionstart",function(){var e=i.getCursor("from");if(r.composing){r.composing.range.clear()}r.composing={start:e,range:i.markText(e,i.getCursor("to"),{className:"CodeMirror-composing"})}});de(n,"compositionend",function(){if(r.composing){r.poll();r.composing.range.clear();r.composing=null}})};nl.prototype.createField=function(e){this.wrapper=Ko();this.textarea=this.wrapper.firstChild};nl.prototype.prepareSelection=function(){var e=this.cm,t=e.display,r=e.doc;var i=Hi(e);if(e.options.moveInputWithCursor){var n=vi(e,r.sel.primary().head,"div");var a=t.wrapper.getBoundingClientRect(),s=t.lineDiv.getBoundingClientRect();i.teTop=Math.max(0,Math.min(t.wrapper.clientHeight-10,n.top+s.top-a.top));i.teLeft=Math.max(0,Math.min(t.wrapper.clientWidth-10,n.left+s.left-a.left))}return i};nl.prototype.showSelection=function(e){var t=this.cm,r=t.display;A(r.cursorDiv,e.cursors);A(r.selectionDiv,e.selection);if(e.teTop!=null){this.wrapper.style.top=e.teTop+"px";this.wrapper.style.left=e.teLeft+"px"}};nl.prototype.reset=function(e){if(this.contextMenuPending||this.composing){return}var t=this.cm;if(t.somethingSelected()){this.prevInput="";var r=t.getSelection();this.textarea.value=r;if(t.state.focused){D(this.textarea)}if(s&&o>=9){this.hasSelection=r}}else if(!e){this.prevInput=this.textarea.value="";if(s&&o>=9){this.hasSelection=null}}};nl.prototype.getField=function(){return this.textarea};nl.prototype.supportsTouch=function(){return false};nl.prototype.focus=function(){if(this.cm.options.readOnly!="nocursor"&&(!g||P()!=this.textarea)){try{this.textarea.focus()}catch(e){}}};nl.prototype.blur=function(){this.textarea.blur()};nl.prototype.resetPosition=function(){this.wrapper.style.top=this.wrapper.style.left=0};nl.prototype.receivedFocus=function(){this.slowPoll()};nl.prototype.slowPoll=function(){var e=this;if(this.pollingFast){return}this.polling.set(this.cm.options.pollInterval,function(){e.poll();if(e.cm.state.focused){e.slowPoll()}})};nl.prototype.fastPoll=function(){var e=false,t=this;t.pollingFast=true;function r(){var i=t.poll();if(!i&&!e){e=true;t.polling.set(60,r)}else{t.pollingFast=false;t.slowPoll()}}t.polling.set(20,r)};nl.prototype.poll=function(){var e=this;var t=this.cm,r=this.textarea,i=this.prevInput;if(this.contextMenuPending||!t.state.focused||Oe(r)&&!i&&!this.composing||t.isReadOnly()||t.options.disableInput||t.state.keySeq){return false}var n=r.value;if(n==i&&!t.somethingSelected()){return false}if(s&&o>=9&&this.hasSelection===n||y&&/[\uf700-\uf7ff]/.test(n)){t.display.input.reset();return false}if(t.doc.sel==t.display.selForContextMenu){var a=n.charCodeAt(0);if(a==8203&&!i){i="​"}if(a==8666){this.reset();return this.cm.execCommand("undo")}}var l=0,u=Math.min(i.length,n.length);while(l1e3||n.indexOf("\n")>-1){r.value=e.prevInput=""}else{e.prevInput=n}if(e.composing){e.composing.range.clear();e.composing.range=t.markText(e.composing.start,t.getCursor("to"),{className:"CodeMirror-composing"})}});return true};nl.prototype.ensurePolled=function(){if(this.pollingFast&&this.poll()){this.pollingFast=false}};nl.prototype.onKeyPress=function(){if(s&&o>=9){this.hasSelection=null}this.fastPoll()};nl.prototype.onContextMenu=function(e){var t=this,r=t.cm,i=r.display,n=t.textarea;if(t.contextMenuPending){t.contextMenuPending()}var a=Ii(r,e),u=i.scroller.scrollTop;if(!a||f){return}var c=r.options.resetSelectionOnContextMenu;if(c&&r.doc.sel.contains(a)==-1){_n(r,za)(r.doc,sa(a),j)}var h=n.style.cssText,p=t.wrapper.style.cssText;var d=t.wrapper.offsetParent.getBoundingClientRect();t.wrapper.style.cssText="position: static";n.style.cssText="position: absolute; width: 30px; height: 30px;\n top: "+(e.clientY-d.top-5)+"px; left: "+(e.clientX-d.left-5)+"px;\n z-index: 1000; background: "+(s?"rgba(255, 255, 255, .05)":"transparent")+";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";var m;if(l){m=window.scrollY}i.input.focus();if(l){window.scrollTo(null,m)}i.input.reset();if(!r.somethingSelected()){n.value=t.prevInput=" "}t.contextMenuPending=g;i.selForContextMenu=r.doc.sel;clearTimeout(i.detectingSelectAll);function v(){if(n.selectionStart!=null){var e=r.somethingSelected();var a="​"+(e?n.value:"");n.value="⇚";n.value=a;t.prevInput=e?"":"​";n.selectionStart=1;n.selectionEnd=a.length;i.selForContextMenu=r.doc.sel}}function g(){if(t.contextMenuPending!=g){return}t.contextMenuPending=false;t.wrapper.style.cssText=p;n.style.cssText=h;if(s&&o<9){i.scrollbars.setScrollTop(i.scroller.scrollTop=u)}if(n.selectionStart!=null){if(!s||s&&o<9){v()}var e=0,a=function(){if(i.selForContextMenu==r.doc.sel&&n.selectionStart==0&&n.selectionEnd>0&&t.prevInput=="​"){_n(r,$a)(r)}else if(e++<10){i.detectingSelectAll=setTimeout(a,500)}else{i.selForContextMenu=null;i.input.reset()}};i.detectingSelectAll=setTimeout(a,200)}}if(s&&o>=9){v()}if(C){Le(e);var y=function(){ve(window,"mouseup",y);setTimeout(g,20)};de(window,"mouseup",y)}else{setTimeout(g,50)}};nl.prototype.readOnlyChanged=function(e){if(!e){this.reset()}this.textarea.disabled=e=="nocursor"};nl.prototype.setUneditable=function(){};nl.prototype.needsContentAttribute=false;function al(e,t){t=t?V(t):{};t.value=e.value;if(!t.tabindex&&e.tabIndex){t.tabindex=e.tabIndex}if(!t.placeholder&&e.placeholder){t.placeholder=e.placeholder}if(t.autofocus==null){var r=P();t.autofocus=r==e||e.getAttribute("autofocus")!=null&&r==document.body}function i(){e.value=o.getValue()}var n;if(e.form){de(e.form,"submit",i);if(!t.leaveSubmitMethodAlone){var a=e.form;n=a.submit;try{var s=a.submit=function(){i();a.submit=n;a.submit();a.submit=s}}catch(e){}}}t.finishInit=function(r){r.save=i;r.getTextArea=function(){return e};r.toTextArea=function(){r.toTextArea=isNaN;i();e.parentNode.removeChild(r.getWrapperElement());e.style.display="";if(e.form){ve(e.form,"submit",i);if(!t.leaveSubmitMethodAlone&&typeof e.form.submit=="function"){e.form.submit=n}}}};e.style.display="none";var o=Ro(function(t){return e.parentNode.insertBefore(t,e.nextSibling)},t);return o}function sl(e){e.off=ve;e.on=de;e.wheelEventPixels=ta;e.Doc=ws;e.splitLines=Ie;e.countColumn=F;e.findColumn=q;e.isWordChar=te;e.Pass=H;e.signal=ge;e.Line=or;e.changeEnd=oa;e.scrollbarModel=xn;e.Pos=nt;e.cmpPos=at;e.modes=Fe;e.mimeModes=We;e.resolveMode=He;e.getMode=je;e.modeExtensions=Ue;e.extendMode=Ge;e.copyState=qe;e.startState=$e;e.innerMode=Ke;e.commands=Xs;e.keyMap=Rs;e.keyName=Hs;e.isModifierKey=zs;e.lookupKey=Ws;e.normalizeKeyMap=Fs;e.StringStream=Xe;e.SharedTextMarker=ms;e.TextMarker=ps;e.LineWidget=us;e.e_preventDefault=ke;e.e_stopPropagation=Ce;e.e_stop=Le;e.addClass=I;e.contains=_;e.rmClass=L;e.keyNames=Ps}Io(Ro);$o(Ro);var ol="iter insert remove copy getEditor constructor".split(" ");for(var ll in ws.prototype){if(ws.prototype.hasOwnProperty(ll)&&z(ol,ll)<0){Ro.prototype[ll]=function(e){return function(){return e.apply(this.doc,arguments)}}(ws.prototype[ll])}}we(ws);Ro.inputStyles={textarea:nl,contenteditable:Qo};Ro.defineMode=function(e){if(!Ro.defaults.mode&&e!="null"){Ro.defaults.mode=e}ze.apply(this,arguments)};Ro.defineMIME=Be;Ro.defineMode("null",function(){return{token:function(e){return e.skipToEnd()}}});Ro.defineMIME("text/plain","null");Ro.defineExtension=function(e,t){Ro.prototype[e]=t};Ro.defineDocExtension=function(e,t){ws.prototype[e]=t};Ro.fromTextArea=al;sl(Ro);Ro.version="5.49.2";return Ro});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror"],e);else e(CodeMirror)})(function(e){"use strict";e.defineMode("javascript",function(t,r){var i=t.indentUnit;var n=r.statementIndent;var a=r.jsonld;var s=r.json||a;var o=r.typescript;var l=r.wordCharacters||/[\w$\xa1-\uffff]/;var u=function(){function e(e){return{type:e,style:"keyword"}}var t=e("keyword a"),r=e("keyword b"),i=e("keyword c"),n=e("keyword d");var a=e("operator"),s={type:"atom",style:"atom"};return{if:e("if"),while:t,with:t,else:r,do:r,try:r,finally:r,return:n,break:n,continue:n,new:e("new"),delete:i,void:i,throw:i,debugger:e("debugger"),var:e("var"),const:e("var"),let:e("var"),function:e("function"),catch:e("catch"),for:e("for"),switch:e("switch"),case:e("case"),default:e("default"),in:a,typeof:a,instanceof:a,true:s,false:s,null:s,undefined:s,NaN:s,Infinity:s,this:e("this"),class:e("class"),super:e("atom"),yield:i,export:e("export"),import:e("import"),extends:i,await:i}}();var c=/[+\-*&%=<>!?|~^@]/;var f=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;function h(e){var t=false,r,i=false;while((r=e.next())!=null){if(!t){if(r=="/"&&!i)return;if(r=="[")i=true;else if(i&&r=="]")i=false}t=!t&&r=="\\"}}var p,d;function m(e,t,r){p=e;d=r;return t}function v(e,t){var r=e.next();if(r=='"'||r=="'"){t.tokenize=g(r);return t.tokenize(e,t)}else if(r=="."&&e.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)){return m("number","number")}else if(r=="."&&e.match("..")){return m("spread","meta")}else if(/[\[\]{}\(\),;\:\.]/.test(r)){return m(r)}else if(r=="="&&e.eat(">")){return m("=>","operator")}else if(r=="0"&&e.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)){return m("number","number")}else if(/\d/.test(r)){e.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);return m("number","number")}else if(r=="/"){if(e.eat("*")){t.tokenize=y;return y(e,t)}else if(e.eat("/")){e.skipToEnd();return m("comment","comment")}else if(et(e,t,1)){h(e);e.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);return m("regexp","string-2")}else{e.eat("=");return m("operator","operator",e.current())}}else if(r=="`"){t.tokenize=x;return x(e,t)}else if(r=="#"){e.skipToEnd();return m("error","error")}else if(r=="<"&&e.match("!--")||r=="-"&&e.match("->")){e.skipToEnd();return m("comment","comment")}else if(c.test(r)){if(r!=">"||!t.lexical||t.lexical.type!=">"){if(e.eat("=")){if(r=="!"||r=="=")e.eat("=")}else if(/[<>*+\-]/.test(r)){e.eat(r);if(r==">")e.eat(r)}}return m("operator","operator",e.current())}else if(l.test(r)){e.eatWhile(l);var i=e.current();if(t.lastType!="."){if(u.propertyIsEnumerable(i)){var n=u[i];return m(n.type,n.style,i)}if(i=="async"&&e.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/,false))return m("async","keyword",i)}return m("variable","variable",i)}}function g(e){return function(t,r){var i=false,n;if(a&&t.peek()=="@"&&t.match(f)){r.tokenize=v;return m("jsonld-keyword","meta")}while((n=t.next())!=null){if(n==e&&!i)break;i=!i&&n=="\\"}if(!i)r.tokenize=v;return m("string","string")}}function y(e,t){var r=false,i;while(i=e.next()){if(i=="/"&&r){t.tokenize=v;break}r=i=="*"}return m("comment","comment")}function x(e,t){var r=false,i;while((i=e.next())!=null){if(!r&&(i=="`"||i=="$"&&e.eat("{"))){t.tokenize=v;break}r=!r&&i=="\\"}return m("quasi","string-2",e.current())}var b="([{}])";function w(e,t){if(t.fatArrowAt)t.fatArrowAt=null;var r=e.string.indexOf("=>",e.start);if(r<0)return;if(o){var i=/:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(e.string.slice(e.start,r));if(i)r=i.index}var n=0,a=false;for(var s=r-1;s>=0;--s){var u=e.string.charAt(s);var c=b.indexOf(u);if(c>=0&&c<3){if(!n){++s;break}if(--n==0){if(u=="(")a=true;break}}else if(c>=3&&c<6){++n}else if(l.test(u)){a=true}else if(/["'\/`]/.test(u)){for(;;--s){if(s==0)return;var f=e.string.charAt(s-1);if(f==u&&e.string.charAt(s-2)!="\\"){s--;break}}}else if(a&&!n){++s;break}}if(a&&!n)t.fatArrowAt=s}var k={atom:true,number:true,variable:true,string:true,regexp:true,this:true,"jsonld-keyword":true};function C(e,t,r,i,n,a){this.indented=e;this.column=t;this.type=r;this.prev=n;this.info=a;if(i!=null)this.align=i}function S(e,t){for(var r=e.localVars;r;r=r.next)if(r.name==t)return true;for(var i=e.context;i;i=i.prev){for(var r=i.vars;r;r=r.next)if(r.name==t)return true}}function L(e,t,r,i,n){var a=e.cc;T.state=e;T.stream=n;T.marked=null,T.cc=a;T.style=t;if(!e.lexical.hasOwnProperty("align"))e.lexical.align=true;while(true){var o=a.length?a.pop():s?U:H;if(o(r,i)){while(a.length&&a[a.length-1].lex)a.pop()();if(T.marked)return T.marked;if(r=="variable"&&S(e,i))return"variable-2";return t}}}var T={state:null,column:null,marked:null,cc:null};function A(){for(var e=arguments.length-1;e>=0;e--)T.cc.push(arguments[e])}function E(){A.apply(null,arguments);return true}function M(e,t){for(var r=t;r;r=r.next)if(r.name==e)return true;return false}function N(e){var t=T.state;T.marked="def";if(t.context){if(t.lexical.info=="var"&&t.context&&t.context.block){var i=_(e,t.context);if(i!=null){t.context=i;return}}else if(!M(e,t.localVars)){t.localVars=new O(e,t.localVars);return}}if(r.globalVars&&!M(e,t.globalVars))t.globalVars=new O(e,t.globalVars)}function _(e,t){if(!t){return null}else if(t.block){var r=_(e,t.prev);if(!r)return null;if(r==t.prev)return t;return new I(r,t.vars,true)}else if(M(e,t.vars)){return t}else{return new I(t.prev,new O(e,t.vars),false)}}function P(e){return e=="public"||e=="private"||e=="protected"||e=="abstract"||e=="readonly"}function I(e,t,r){this.prev=e;this.vars=t;this.block=r}function O(e,t){this.name=e;this.next=t}var D=new O("this",new O("arguments",null));function R(){T.state.context=new I(T.state.context,T.state.localVars,false);T.state.localVars=D}function V(){T.state.context=new I(T.state.context,T.state.localVars,true);T.state.localVars=null}function F(){T.state.localVars=T.state.context.vars;T.state.context=T.state.context.prev}F.lex=true;function W(e,t){var r=function(){var r=T.state,i=r.indented;if(r.lexical.type=="stat")i=r.lexical.indented;else for(var n=r.lexical;n&&n.type==")"&&n.align;n=n.prev)i=n.indented;r.lexical=new C(i,T.stream.column(),e,null,r.lexical,t)};r.lex=true;return r}function z(){var e=T.state;if(e.lexical.prev){if(e.lexical.type==")")e.indented=e.lexical.indented;e.lexical=e.lexical.prev}}z.lex=true;function B(e){function t(r){if(r==e)return E();else if(e==";"||r=="}"||r==")"||r=="]")return A();else return E(t)}return t}function H(e,t){if(e=="var")return E(W("vardef",t),Se,B(";"),z);if(e=="keyword a")return E(W("form"),q,H,z);if(e=="keyword b")return E(W("form"),H,z);if(e=="keyword d")return T.stream.match(/^\s*$/,false)?E():E(W("stat"),$,B(";"),z);if(e=="debugger")return E(B(";"));if(e=="{")return E(W("}"),V,fe,z,F);if(e==";")return E();if(e=="if"){if(T.state.lexical.info=="else"&&T.state.cc[T.state.cc.length-1]==z)T.state.cc.pop()();return E(W("form"),q,H,z,Ne)}if(e=="function")return E(Oe);if(e=="for")return E(W("form"),_e,H,z);if(e=="class"||o&&t=="interface"){T.marked="keyword";return E(W("form",e=="class"?e:t),We,z)}if(e=="variable"){if(o&&t=="declare"){T.marked="keyword";return E(H)}else if(o&&(t=="module"||t=="enum"||t=="type")&&T.stream.match(/^\s*\w/,false)){T.marked="keyword";if(t=="enum")return E(Qe);else if(t=="type")return E(Re,B("operator"),ve,B(";"));else return E(W("form"),Le,B("{"),W("}"),fe,z,z)}else if(o&&t=="namespace"){T.marked="keyword";return E(W("form"),U,H,z)}else if(o&&t=="abstract"){T.marked="keyword";return E(H)}else{return E(W("stat"),ne)}}if(e=="switch")return E(W("form"),q,B("{"),W("}","switch"),V,fe,z,z,F);if(e=="case")return E(U,B(":"));if(e=="default")return E(B(":"));if(e=="catch")return E(W("form"),R,j,H,z,F);if(e=="export")return E(W("stat"),je,z);if(e=="import")return E(W("stat"),Ge,z);if(e=="async")return E(H);if(t=="@")return E(U,H);return A(W("stat"),U,B(";"),z)}function j(e){if(e=="(")return E(Ve,B(")"))}function U(e,t){return K(e,t,false)}function G(e,t){return K(e,t,true)}function q(e){if(e!="(")return A();return E(W(")"),U,B(")"),z)}function K(e,t,r){if(T.state.fatArrowAt==T.stream.start){var i=r?ee:J;if(e=="(")return E(R,W(")"),ue(Ve,")"),z,B("=>"),i,F);else if(e=="variable")return A(R,Le,B("=>"),i,F)}var n=r?Y:X;if(k.hasOwnProperty(e))return E(n);if(e=="function")return E(Oe,n);if(e=="class"||o&&t=="interface"){T.marked="keyword";return E(W("form"),Fe,z)}if(e=="keyword c"||e=="async")return E(r?G:U);if(e=="(")return E(W(")"),$,B(")"),z,n);if(e=="operator"||e=="spread")return E(r?G:U);if(e=="[")return E(W("]"),Ye,z,n);if(e=="{")return ce(se,"}",null,n);if(e=="quasi")return A(Q,n);if(e=="new")return E(te(r));if(e=="import")return E(U);return E()}function $(e){if(e.match(/[;\}\)\],]/))return A();return A(U)}function X(e,t){if(e==",")return E(U);return Y(e,t,false)}function Y(e,t,r){var i=r==false?X:Y;var n=r==false?U:G;if(e=="=>")return E(R,r?ee:J,F);if(e=="operator"){if(/\+\+|--/.test(t)||o&&t=="!")return E(i);if(o&&t=="<"&&T.stream.match(/^([^>]|<.*?>)*>\s*\(/,false))return E(W(">"),ue(ve,">"),z,i);if(t=="?")return E(U,B(":"),n);return E(n)}if(e=="quasi"){return A(Q,i)}if(e==";")return;if(e=="(")return ce(G,")","call",i);if(e==".")return E(ae,i);if(e=="[")return E(W("]"),$,B("]"),z,i);if(o&&t=="as"){T.marked="keyword";return E(ve,i)}if(e=="regexp"){T.state.lastType=T.marked="operator";T.stream.backUp(T.stream.pos-T.stream.start-1);return E(n)}}function Q(e,t){if(e!="quasi")return A();if(t.slice(t.length-2)!="${")return E(Q);return E(U,Z)}function Z(e){if(e=="}"){T.marked="string-2";T.state.tokenize=x;return E(Q)}}function J(e){w(T.stream,T.state);return A(e=="{"?H:U)}function ee(e){w(T.stream,T.state);return A(e=="{"?H:G)}function te(e){return function(t){if(t==".")return E(e?ie:re);else if(t=="variable"&&o)return E(we,e?Y:X);else return A(e?G:U)}}function re(e,t){if(t=="target"){T.marked="keyword";return E(X)}}function ie(e,t){if(t=="target"){T.marked="keyword";return E(Y)}}function ne(e){if(e==":")return E(z,H);return A(X,B(";"),z)}function ae(e){if(e=="variable"){T.marked="property";return E()}}function se(e,t){if(e=="async"){T.marked="property";return E(se)}else if(e=="variable"||T.style=="keyword"){T.marked="property";if(t=="get"||t=="set")return E(oe);var r;if(o&&T.state.fatArrowAt==T.stream.start&&(r=T.stream.match(/^\s*:\s*/,false)))T.state.fatArrowAt=T.stream.pos+r[0].length;return E(le)}else if(e=="number"||e=="string"){T.marked=a?"property":T.style+" property";return E(le)}else if(e=="jsonld-keyword"){return E(le)}else if(o&&P(t)){T.marked="keyword";return E(se)}else if(e=="["){return E(U,he,B("]"),le)}else if(e=="spread"){return E(G,le)}else if(t=="*"){T.marked="keyword";return E(se)}else if(e==":"){return A(le)}}function oe(e){if(e!="variable")return A(le);T.marked="property";return E(Oe)}function le(e){if(e==":")return E(G);if(e=="(")return A(Oe)}function ue(e,t,r){function i(n,a){if(r?r.indexOf(n)>-1:n==","){var s=T.state.lexical;if(s.info=="call")s.pos=(s.pos||0)+1;return E(function(r,i){if(r==t||i==t)return A();return A(e)},i)}if(n==t||a==t)return E();if(r&&r.indexOf(";")>-1)return A(e);return E(B(t))}return function(r,n){if(r==t||n==t)return E();return A(e,i)}}function ce(e,t,r){for(var i=3;i"),ve)}function ge(e){if(e=="=>")return E(ve)}function ye(e,t){if(e=="variable"||T.style=="keyword"){T.marked="property";return E(ye)}else if(t=="?"||e=="number"||e=="string"){return E(ye)}else if(e==":"){return E(ve)}else if(e=="["){return E(B("variable"),pe,B("]"),ye)}else if(e=="("){return A(De,ye)}}function xe(e,t){if(e=="variable"&&T.stream.match(/^\s*[?:]/,false)||t=="?")return E(xe);if(e==":")return E(ve);if(e=="spread")return E(xe);return A(ve)}function be(e,t){if(t=="<")return E(W(">"),ue(ve,">"),z,be);if(t=="|"||e=="."||t=="&")return E(ve);if(e=="[")return E(ve,B("]"),be);if(t=="extends"||t=="implements"){T.marked="keyword";return E(ve)}if(t=="?")return E(ve,B(":"),ve)}function we(e,t){if(t=="<")return E(W(">"),ue(ve,">"),z,be)}function ke(){return A(ve,Ce)}function Ce(e,t){if(t=="=")return E(ve)}function Se(e,t){if(t=="enum"){T.marked="keyword";return E(Qe)}return A(Le,he,Ee,Me)}function Le(e,t){if(o&&P(t)){T.marked="keyword";return E(Le)}if(e=="variable"){N(t);return E()}if(e=="spread")return E(Le);if(e=="[")return ce(Ae,"]");if(e=="{")return ce(Te,"}")}function Te(e,t){if(e=="variable"&&!T.stream.match(/^\s*:/,false)){N(t);return E(Ee)}if(e=="variable")T.marked="property";if(e=="spread")return E(Le);if(e=="}")return A();if(e=="[")return E(U,B("]"),B(":"),Te);return E(B(":"),Le,Ee)}function Ae(){return A(Le,Ee)}function Ee(e,t){if(t=="=")return E(G)}function Me(e){if(e==",")return E(Se)}function Ne(e,t){if(e=="keyword b"&&t=="else")return E(W("form","else"),H,z)}function _e(e,t){if(t=="await")return E(_e);if(e=="(")return E(W(")"),Pe,z)}function Pe(e){if(e=="var")return E(Se,Ie);if(e=="variable")return E(Ie);return A(Ie)}function Ie(e,t){if(e==")")return E();if(e==";")return E(Ie);if(t=="in"||t=="of"){T.marked="keyword";return E(U,Ie)}return A(U,Ie)}function Oe(e,t){if(t=="*"){T.marked="keyword";return E(Oe) +}if(e=="variable"){N(t);return E(Oe)}if(e=="(")return E(R,W(")"),ue(Ve,")"),z,de,H,F);if(o&&t=="<")return E(W(">"),ue(ke,">"),z,Oe)}function De(e,t){if(t=="*"){T.marked="keyword";return E(De)}if(e=="variable"){N(t);return E(De)}if(e=="(")return E(R,W(")"),ue(Ve,")"),z,de,F);if(o&&t=="<")return E(W(">"),ue(ke,">"),z,De)}function Re(e,t){if(e=="keyword"||e=="variable"){T.marked="type";return E(Re)}else if(t=="<"){return E(W(">"),ue(ke,">"),z)}}function Ve(e,t){if(t=="@")E(U,Ve);if(e=="spread")return E(Ve);if(o&&P(t)){T.marked="keyword";return E(Ve)}if(o&&e=="this")return E(he,Ee);return A(Le,he,Ee)}function Fe(e,t){if(e=="variable")return We(e,t);return ze(e,t)}function We(e,t){if(e=="variable"){N(t);return E(ze)}}function ze(e,t){if(t=="<")return E(W(">"),ue(ke,">"),z,ze);if(t=="extends"||t=="implements"||o&&e==","){if(t=="implements")T.marked="keyword";return E(o?ve:U,ze)}if(e=="{")return E(W("}"),Be,z)}function Be(e,t){if(e=="async"||e=="variable"&&(t=="static"||t=="get"||t=="set"||o&&P(t))&&T.stream.match(/^\s+[\w$\xa1-\uffff]/,false)){T.marked="keyword";return E(Be)}if(e=="variable"||T.style=="keyword"){T.marked="property";return E(o?He:Oe,Be)}if(e=="number"||e=="string")return E(o?He:Oe,Be);if(e=="[")return E(U,he,B("]"),o?He:Oe,Be);if(t=="*"){T.marked="keyword";return E(Be)}if(o&&e=="(")return A(De,Be);if(e==";"||e==",")return E(Be);if(e=="}")return E();if(t=="@")return E(U,Be)}function He(e,t){if(t=="?")return E(He);if(e==":")return E(ve,Ee);if(t=="=")return E(G);var r=T.state.lexical.prev,i=r&&r.info=="interface";return A(i?De:Oe)}function je(e,t){if(t=="*"){T.marked="keyword";return E(Xe,B(";"))}if(t=="default"){T.marked="keyword";return E(U,B(";"))}if(e=="{")return E(ue(Ue,"}"),Xe,B(";"));return A(H)}function Ue(e,t){if(t=="as"){T.marked="keyword";return E(B("variable"))}if(e=="variable")return A(G,Ue)}function Ge(e){if(e=="string")return E();if(e=="(")return A(U);return A(qe,Ke,Xe)}function qe(e,t){if(e=="{")return ce(qe,"}");if(e=="variable")N(t);if(t=="*")T.marked="keyword";return E($e)}function Ke(e){if(e==",")return E(qe,Ke)}function $e(e,t){if(t=="as"){T.marked="keyword";return E(qe)}}function Xe(e,t){if(t=="from"){T.marked="keyword";return E(U)}}function Ye(e){if(e=="]")return E();return A(ue(G,"]"))}function Qe(){return A(W("form"),Le,B("{"),W("}"),ue(Ze,"}"),z,z)}function Ze(){return A(Le,Ee)}function Je(e,t){return e.lastType=="operator"||e.lastType==","||c.test(t.charAt(0))||/[,.]/.test(t.charAt(0))}function et(e,t,r){return t.tokenize==v&&/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(t.lastType)||t.lastType=="quasi"&&/\{\s*$/.test(e.string.slice(0,e.pos-(r||0)))}return{startState:function(e){var t={tokenize:v,lastType:"sof",cc:[],lexical:new C((e||0)-i,0,"block",false),localVars:r.localVars,context:r.localVars&&new I(null,null,false),indented:e||0};if(r.globalVars&&typeof r.globalVars=="object")t.globalVars=r.globalVars;return t},token:function(e,t){if(e.sol()){if(!t.lexical.hasOwnProperty("align"))t.lexical.align=false;t.indented=e.indentation();w(e,t)}if(t.tokenize!=y&&e.eatSpace())return null;var r=t.tokenize(e,t);if(p=="comment")return r;t.lastType=p=="operator"&&(d=="++"||d=="--")?"incdec":p;return L(t,r,p,d,e)},indent:function(t,a){if(t.tokenize==y)return e.Pass;if(t.tokenize!=v)return 0;var s=a&&a.charAt(0),o=t.lexical,l;if(!/^\s*else\b/.test(a))for(var u=t.cc.length-1;u>=0;--u){var c=t.cc[u];if(c==z)o=o.prev;else if(c!=Ne)break}while((o.type=="stat"||o.type=="form")&&(s=="}"||(l=t.cc[t.cc.length-1])&&(l==X||l==Y)&&!/^[,\.=+\-*:?[\(]/.test(a)))o=o.prev;if(n&&o.type==")"&&o.prev.type=="stat")o=o.prev;var f=o.type,h=s==f;if(f=="vardef")return o.indented+(t.lastType=="operator"||t.lastType==","?o.info.length+1:0);else if(f=="form"&&s=="{")return o.indented;else if(f=="form")return o.indented+i;else if(f=="stat")return o.indented+(Je(t,a)?n||i:0);else if(o.info=="switch"&&!h&&r.doubleIndentSwitch!=false)return o.indented+(/^(?:case|default)\b/.test(a)?i:2*i);else if(o.align)return o.column+(h?0:1);else return o.indented+(h?0:i)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:s?null:"/*",blockCommentEnd:s?null:"*/",blockCommentContinue:s?null:" * ",lineComment:s?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:s?"json":"javascript",jsonldMode:a,jsonMode:s,expressionAllowed:et,skipExpression:function(e){var t=e.cc[e.cc.length-1];if(t==U||t==G)e.cc.pop()}}});e.registerHelper("wordChars","javascript",/[\w$]/);e.defineMIME("text/javascript","javascript");e.defineMIME("text/ecmascript","javascript");e.defineMIME("application/javascript","javascript");e.defineMIME("application/x-javascript","javascript");e.defineMIME("application/ecmascript","javascript");e.defineMIME("application/json",{name:"javascript",json:true});e.defineMIME("application/x-json",{name:"javascript",json:true});e.defineMIME("application/ld+json",{name:"javascript",jsonld:true});e.defineMIME("text/typescript",{name:"javascript",typescript:true});e.defineMIME("application/typescript",{name:"javascript",typescript:true})});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror"],e);else e(CodeMirror)})(function(e){"use strict";e.defineMode("css",function(t,r){var i=r.inline;if(!r.propertyKeywords)r=e.resolveMode("text/css");var n=t.indentUnit,a=r.tokenHooks,s=r.documentTypes||{},o=r.mediaTypes||{},l=r.mediaFeatures||{},u=r.mediaValueKeywords||{},c=r.propertyKeywords||{},f=r.nonStandardPropertyKeywords||{},h=r.fontProperties||{},p=r.counterDescriptors||{},d=r.colorKeywords||{},m=r.valueKeywords||{},v=r.allowNested,g=r.lineComment,y=r.supportsAtComponent===true;var x,b;function w(e,t){x=t;return e}function k(e,t){var r=e.next();if(a[r]){var i=a[r](e,t);if(i!==false)return i}if(r=="@"){e.eatWhile(/[\w\\\-]/);return w("def",e.current())}else if(r=="="||(r=="~"||r=="|")&&e.eat("=")){return w(null,"compare")}else if(r=='"'||r=="'"){t.tokenize=C(r);return t.tokenize(e,t)}else if(r=="#"){e.eatWhile(/[\w\\\-]/);return w("atom","hash")}else if(r=="!"){e.match(/^\s*\w*/);return w("keyword","important")}else if(/\d/.test(r)||r=="."&&e.eat(/\d/)){e.eatWhile(/[\w.%]/);return w("number","unit")}else if(r==="-"){if(/[\d.]/.test(e.peek())){e.eatWhile(/[\w.%]/);return w("number","unit")}else if(e.match(/^-[\w\\\-]*/)){e.eatWhile(/[\w\\\-]/);if(e.match(/^\s*:/,false))return w("variable-2","variable-definition");return w("variable-2","variable")}else if(e.match(/^\w+-/)){return w("meta","meta")}}else if(/[,+>*\/]/.test(r)){return w(null,"select-op")}else if(r=="."&&e.match(/^-?[_a-z][_a-z0-9-]*/i)){return w("qualifier","qualifier")}else if(/[:;{}\[\]\(\)]/.test(r)){return w(null,r)}else if(e.match(/[\w-.]+(?=\()/)){if(/^(url(-prefix)?|domain|regexp)$/.test(e.current().toLowerCase())){t.tokenize=S}return w("variable callee","variable")}else if(/[\w\\\-]/.test(r)){e.eatWhile(/[\w\\\-]/);return w("property","word")}else{return w(null,null)}}function C(e){return function(t,r){var i=false,n;while((n=t.next())!=null){if(n==e&&!i){if(e==")")t.backUp(1);break}i=!i&&n=="\\"}if(n==e||!i&&e!=")")r.tokenize=null;return w("string","string")}}function S(e,t){e.next();if(!e.match(/\s*[\"\')]/,false))t.tokenize=C(")");else t.tokenize=null;return w(null,"(")}function L(e,t,r){this.type=e;this.indent=t;this.prev=r}function T(e,t,r,i){e.context=new L(r,t.indentation()+(i===false?0:n),e.context);return r}function A(e){if(e.context.prev)e.context=e.context.prev;return e.context.type}function E(e,t,r){return _[r.context.type](e,t,r)}function M(e,t,r,i){for(var n=i||1;n>0;n--)r.context=r.context.prev;return E(e,t,r)}function N(e){var t=e.current().toLowerCase();if(m.hasOwnProperty(t))b="atom";else if(d.hasOwnProperty(t))b="keyword";else b="variable"}var _={};_.top=function(e,t,r){if(e=="{"){return T(r,t,"block")}else if(e=="}"&&r.context.prev){return A(r)}else if(y&&/@component/i.test(e)){return T(r,t,"atComponentBlock")}else if(/^@(-moz-)?document$/i.test(e)){return T(r,t,"documentTypes")}else if(/^@(media|supports|(-moz-)?document|import)$/i.test(e)){return T(r,t,"atBlock")}else if(/^@(font-face|counter-style)/i.test(e)){r.stateArg=e;return"restricted_atBlock_before"}else if(/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(e)){return"keyframes"}else if(e&&e.charAt(0)=="@"){return T(r,t,"at")}else if(e=="hash"){b="builtin"}else if(e=="word"){b="tag"}else if(e=="variable-definition"){return"maybeprop"}else if(e=="interpolation"){return T(r,t,"interpolation")}else if(e==":"){return"pseudo"}else if(v&&e=="("){return T(r,t,"parens")}return r.context.type};_.block=function(e,t,r){if(e=="word"){var i=t.current().toLowerCase();if(c.hasOwnProperty(i)){b="property";return"maybeprop"}else if(f.hasOwnProperty(i)){b="string-2";return"maybeprop"}else if(v){b=t.match(/^\s*:(?:\s|$)/,false)?"property":"tag";return"block"}else{b+=" error";return"maybeprop"}}else if(e=="meta"){return"block"}else if(!v&&(e=="hash"||e=="qualifier")){b="error";return"block"}else{return _.top(e,t,r)}};_.maybeprop=function(e,t,r){if(e==":")return T(r,t,"prop");return E(e,t,r)};_.prop=function(e,t,r){if(e==";")return A(r);if(e=="{"&&v)return T(r,t,"propBlock");if(e=="}"||e=="{")return M(e,t,r);if(e=="(")return T(r,t,"parens");if(e=="hash"&&!/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(t.current())){b+=" error"}else if(e=="word"){N(t)}else if(e=="interpolation"){return T(r,t,"interpolation")}return"prop"};_.propBlock=function(e,t,r){if(e=="}")return A(r);if(e=="word"){b="property";return"maybeprop"}return r.context.type};_.parens=function(e,t,r){if(e=="{"||e=="}")return M(e,t,r);if(e==")")return A(r);if(e=="(")return T(r,t,"parens");if(e=="interpolation")return T(r,t,"interpolation");if(e=="word")N(t);return"parens"};_.pseudo=function(e,t,r){if(e=="meta")return"pseudo";if(e=="word"){b="variable-3";return r.context.type}return E(e,t,r)};_.documentTypes=function(e,t,r){if(e=="word"&&s.hasOwnProperty(t.current())){b="tag";return r.context.type}else{return _.atBlock(e,t,r)}};_.atBlock=function(e,t,r){if(e=="(")return T(r,t,"atBlock_parens");if(e=="}"||e==";")return M(e,t,r);if(e=="{")return A(r)&&T(r,t,v?"block":"top");if(e=="interpolation")return T(r,t,"interpolation");if(e=="word"){var i=t.current().toLowerCase();if(i=="only"||i=="not"||i=="and"||i=="or")b="keyword";else if(o.hasOwnProperty(i))b="attribute";else if(l.hasOwnProperty(i))b="property";else if(u.hasOwnProperty(i))b="keyword";else if(c.hasOwnProperty(i))b="property";else if(f.hasOwnProperty(i))b="string-2";else if(m.hasOwnProperty(i))b="atom";else if(d.hasOwnProperty(i))b="keyword";else b="error"}return r.context.type};_.atComponentBlock=function(e,t,r){if(e=="}")return M(e,t,r);if(e=="{")return A(r)&&T(r,t,v?"block":"top",false);if(e=="word")b="error";return r.context.type};_.atBlock_parens=function(e,t,r){if(e==")")return A(r);if(e=="{"||e=="}")return M(e,t,r,2);return _.atBlock(e,t,r)};_.restricted_atBlock_before=function(e,t,r){if(e=="{")return T(r,t,"restricted_atBlock");if(e=="word"&&r.stateArg=="@counter-style"){b="variable";return"restricted_atBlock_before"}return E(e,t,r)};_.restricted_atBlock=function(e,t,r){if(e=="}"){r.stateArg=null;return A(r)}if(e=="word"){if(r.stateArg=="@font-face"&&!h.hasOwnProperty(t.current().toLowerCase())||r.stateArg=="@counter-style"&&!p.hasOwnProperty(t.current().toLowerCase()))b="error";else b="property";return"maybeprop"}return"restricted_atBlock"};_.keyframes=function(e,t,r){if(e=="word"){b="variable";return"keyframes"}if(e=="{")return T(r,t,"top");return E(e,t,r)};_.at=function(e,t,r){if(e==";")return A(r);if(e=="{"||e=="}")return M(e,t,r);if(e=="word")b="tag";else if(e=="hash")b="builtin";return"at"};_.interpolation=function(e,t,r){if(e=="}")return A(r);if(e=="{"||e==";")return M(e,t,r);if(e=="word")b="variable";else if(e!="variable"&&e!="("&&e!=")")b="error";return"interpolation"};return{startState:function(e){return{tokenize:null,state:i?"block":"top",stateArg:null,context:new L(i?"block":"top",e||0,null)}},token:function(e,t){if(!t.tokenize&&e.eatSpace())return null;var r=(t.tokenize||k)(e,t);if(r&&typeof r=="object"){x=r[1];r=r[0]}b=r;if(x!="comment")t.state=_[t.state](x,e,t);return b},indent:function(e,t){var r=e.context,i=t&&t.charAt(0);var a=r.indent;if(r.type=="prop"&&(i=="}"||i==")"))r=r.prev;if(r.prev){if(i=="}"&&(r.type=="block"||r.type=="top"||r.type=="interpolation"||r.type=="restricted_atBlock")){r=r.prev;a=r.indent}else if(i==")"&&(r.type=="parens"||r.type=="atBlock_parens")||i=="{"&&(r.type=="at"||r.type=="atBlock")){a=Math.max(0,r.indent-n)}}return a},electricChars:"}",blockCommentStart:"/*",blockCommentEnd:"*/",blockCommentContinue:" * ",lineComment:g,fold:"brace"}});function t(e){var t={};for(var r=0;r"));else return null}else if(e.match("--")){return r(d("comment","--\x3e"))}else if(e.match("DOCTYPE",true,true)){e.eatWhile(/[\w\._\-]/);return r(m(1))}else{return null}}else if(e.eat("?")){e.eatWhile(/[\w\._\-]/);t.tokenize=d("meta","?>");return"meta"}else{u=e.eat("/")?"closeTag":"openTag";t.tokenize=h;return"tag bracket"}}else if(i=="&"){var n;if(e.eat("#")){if(e.eat("x")){n=e.eatWhile(/[a-fA-F\d]/)&&e.eat(";")}else{n=e.eatWhile(/[\d]/)&&e.eat(";")}}else{n=e.eatWhile(/[\w\.\-:]/)&&e.eat(";")}return n?"atom":"error"}else{e.eatWhile(/[^&<]/);return null}}f.isInText=true;function h(e,t){var r=e.next();if(r==">"||r=="/"&&e.eat(">")){t.tokenize=f;u=r==">"?"endTag":"selfcloseTag";return"tag bracket"}else if(r=="="){u="equals";return null}else if(r=="<"){t.tokenize=f;t.state=x;t.tagName=t.tagStart=null;var i=t.tokenize(e,t);return i?i+" tag error":"tag error"}else if(/[\'\"]/.test(r)){t.tokenize=p(r);t.stringStartCol=e.column();return t.tokenize(e,t)}else{e.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);return"word"}}function p(e){var t=function(t,r){while(!t.eol()){if(t.next()==e){r.tokenize=h;break}}return"string"};t.isInAttribute=true;return t}function d(e,t){return function(r,i){while(!r.eol()){if(r.match(t)){i.tokenize=f;break}r.next()}return e}}function m(e){return function(t,r){var i;while((i=t.next())!=null){if(i=="<"){r.tokenize=m(e+1);return r.tokenize(t,r)}else if(i==">"){if(e==1){r.tokenize=f;break}else{r.tokenize=m(e-1);return r.tokenize(t,r)}}}return"meta"}}function v(e,t,r){this.prev=e.context;this.tagName=t;this.indent=e.indented;this.startOfLine=r;if(s.doNotIndent.hasOwnProperty(t)||e.context&&e.context.noIndent)this.noIndent=true}function g(e){if(e.context)e.context=e.context.prev}function y(e,t){var r;while(true){if(!e.context){return}r=e.context.tagName;if(!s.contextGrabbers.hasOwnProperty(r)||!s.contextGrabbers[r].hasOwnProperty(t)){return}g(e)}}function x(e,t,r){if(e=="openTag"){r.tagStart=t.column();return b}else if(e=="closeTag"){return w}else{return x}}function b(e,t,r){if(e=="word"){r.tagName=t.current();c="tag";return S}else if(s.allowMissingTagName&&e=="endTag"){c="tag bracket";return S(e,t,r)}else{c="error";return b}}function w(e,t,r){if(e=="word"){var i=t.current();if(r.context&&r.context.tagName!=i&&s.implicitlyClosed.hasOwnProperty(r.context.tagName))g(r);if(r.context&&r.context.tagName==i||s.matchClosing===false){c="tag";return k}else{c="tag error";return C}}else if(s.allowMissingTagName&&e=="endTag"){c="tag bracket";return k(e,t,r)}else{c="error";return C}}function k(e,t,r){if(e!="endTag"){c="error";return k}g(r);return x}function C(e,t,r){c="error";return k(e,t,r)}function S(e,t,r){if(e=="word"){c="attribute";return L}else if(e=="endTag"||e=="selfcloseTag"){var i=r.tagName,n=r.tagStart;r.tagName=r.tagStart=null;if(e=="selfcloseTag"||s.autoSelfClosers.hasOwnProperty(i)){y(r,i)}else{y(r,i);r.context=new v(r,i,n==r.indented)}return x}c="error";return S}function L(e,t,r){if(e=="equals")return T;if(!s.allowMissing)c="error";return S(e,t,r)}function T(e,t,r){if(e=="string")return A;if(e=="word"&&s.allowUnquoted){c="string";return S}c="error";return S(e,t,r)}function A(e,t,r){if(e=="string")return A;return S(e,t,r)}return{startState:function(e){var t={tokenize:f,state:x,indented:e||0,tagName:null,tagStart:null,context:null};if(e!=null)t.baseIndent=e;return t},token:function(e,t){if(!t.tagName&&e.sol())t.indented=e.indentation();if(e.eatSpace())return null;u=null;var r=t.tokenize(e,t);if((r||u)&&r!="comment"){c=null;t.state=t.state(u||r,e,t);if(c)r=c=="error"?r+" error":c}return r},indent:function(t,r,i){var n=t.context;if(t.tokenize.isInAttribute){if(t.tagStart==t.indented)return t.stringStartCol+1;else return t.indented+a}if(n&&n.noIndent)return e.Pass;if(t.tokenize!=h&&t.tokenize!=f)return i?i.match(/^(\s*)/)[0].length:0;if(t.tagName){if(s.multilineTagIndentPastTag!==false)return t.tagStart+t.tagName.length+2;else return t.tagStart+a*(s.multilineTagIndentFactor||1)}if(s.alignCDATA&&/$/,blockCommentStart:"\x3c!--",blockCommentEnd:"--\x3e",configuration:s.htmlMode?"html":"xml",helperType:s.htmlMode?"html":"xml",skipAttribute:function(e){if(e.state==T)e.state=S},xmlCurrentTag:function(e){return e.tagName?{name:e.tagName,close:e.type=="closeTag"}:null},xmlCurrentContext:function(e){var t=[];for(var r=e.context;r;r=r.prev)if(r.tagName)t.push(r.tagName);return t.reverse()}}});e.defineMIME("text/xml","xml");e.defineMIME("application/xml","xml");if(!e.mimeModes.hasOwnProperty("text/html"))e.defineMIME("text/html",{name:"xml",htmlMode:true})});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"),require("../xml/xml"),require("../javascript/javascript"),require("../css/css"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror","../xml/xml","../javascript/javascript","../css/css"],e);else e(CodeMirror)})(function(e){"use strict";var t={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};function r(e,t,r){var i=e.current(),n=i.search(t);if(n>-1){e.backUp(i.length-n)}else if(i.match(/<\/?$/)){e.backUp(i.length);if(!e.match(t,false))e.match(i)}return r}var i={};function n(e){var t=i[e];if(t)return t;return i[e]=new RegExp("\\s+"+e+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")}function a(e,t){var r=e.match(n(t));return r?/^\s*(.*?)\s*$/.exec(r[2])[1]:""}function s(e,t){return new RegExp((t?"^":"")+"","i")}function o(e,t){for(var r in e){var i=t[r]||(t[r]=[]);var n=e[r];for(var a=n.length-1;a>=0;a--)i.unshift(n[a])}}function l(e,t){for(var r=0;r=0;h--)u.script.unshift(["type",f[h].matches,f[h].mode]);function p(t,n){var o=a.token(t,n.htmlState),c=/\btag\b/.test(o),f;if(c&&!/[<>\s\/]/.test(t.current())&&(f=n.htmlState.tagName&&n.htmlState.tagName.toLowerCase())&&u.hasOwnProperty(f)){n.inTag=f+" "}else if(n.inTag&&c&&/>$/.test(t.current())){var h=/^([\S]+) (.*)/.exec(n.inTag);n.inTag=null;var d=t.current()==">"&&l(u[h[1]],h[2]);var m=e.getMode(i,d);var v=s(h[1],true),g=s(h[1],false);n.token=function(e,t){if(e.match(v,false)){t.token=p;t.localState=t.localMode=null;return null}return r(e,g,t.localMode.token(e,t.localState))};n.localMode=m;n.localState=e.startState(m,a.indent(n.htmlState,"",""))}else if(n.inTag){n.inTag+=t.current();if(t.eol())n.inTag+=" "}return o}return{startState:function(){var t=e.startState(a);return{token:p,inTag:null,localMode:null,localState:null,htmlState:t}},copyState:function(t){var r;if(t.localState){r=e.copyState(t.localMode,t.localState)}return{token:t.token,inTag:t.inTag,localMode:t.localMode,localState:r,htmlState:e.copyState(a,t.htmlState)}},token:function(e,t){return t.token(e,t)},indent:function(t,r,i){if(!t.localMode||/^\s*<\//.test(r))return a.indent(t.htmlState,r,i);else if(t.localMode.indent)return t.localMode.indent(t.localState,r,i);else return e.Pass},innerMode:function(e){return{state:e.localState||e.htmlState,mode:e.localMode||a}}}},"xml","javascript","css");e.defineMIME("text/html","htmlmixed")});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror"],e);else e(CodeMirror)})(function(e){var t=/MSIE \d/.test(navigator.userAgent)&&(document.documentMode==null||document.documentMode<8);var r=e.Pos;var i={"(":")>",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<","<":">>",">":"<<"};function n(e){return e&&e.bracketRegex||/[(){}[\]]/}function a(e,t,a){var o=e.getLineHandle(t.line),l=t.ch-1;var u=a&&a.afterCursor;if(u==null)u=/(^| )cm-fat-cursor($| )/.test(e.getWrapperElement().className);var c=n(a);var f=!u&&l>=0&&c.test(o.text.charAt(l))&&i[o.text.charAt(l)]||c.test(o.text.charAt(l+1))&&i[o.text.charAt(++l)];if(!f)return null;var h=f.charAt(1)==">"?1:-1;if(a&&a.strict&&h>0!=(l==t.ch))return null;var p=e.getTokenTypeAt(r(t.line,l+1));var d=s(e,r(t.line,l+(h>0?1:0)),h,p||null,a);if(d==null)return null;return{from:r(t.line,l),to:d&&d.pos,match:d&&d.ch==f.charAt(0),forward:h>0}}function s(e,t,a,s,o){var l=o&&o.maxScanLineLength||1e4;var u=o&&o.maxScanLines||1e3;var c=[];var f=n(o);var h=a>0?Math.min(t.line+u,e.lastLine()+1):Math.max(e.firstLine()-1,t.line-u);for(var p=t.line;p!=h;p+=a){var d=e.getLine(p);if(!d)continue;var m=a>0?0:d.length-1,v=a>0?d.length:-1;if(d.length>l)continue;if(p==t.line)m=t.ch-(a<0?1:0);for(;m!=v;m+=a){var g=d.charAt(m);if(f.test(g)&&(s===undefined||e.getTokenTypeAt(r(p,m+1))==s)){var y=i[g];if(y&&y.charAt(1)==">"==a>0)c.push(g);else if(!c.length)return{pos:r(p,m),ch:g};else c.pop()}}}return p-a==(a>0?e.lastLine():e.firstLine())?false:null}function o(e,i,n){var s=e.state.matchBrackets.maxHighlightLineLength||1e3;var o=[],l=e.listSelections();for(var u=0;ue){return false}r+=t[i+1];if(r>=e){return true}}}function h(e,t){if(e<65){return e===36}if(e<91){return true}if(e<97){return e===95}if(e<123){return true}if(e<=65535){return e>=170&&o.test(String.fromCharCode(e))}if(t===false){return false}return f(e,u)}function p(e,t){if(e<48){return e===36}if(e<58){return true}if(e<65){return false}if(e<91){return true}if(e<97){return e===95}if(e<123){return true}if(e<=65535){return e>=170&&l.test(String.fromCharCode(e))}if(t===false){return false}return f(e,u)||f(e,c)}var d=function e(t,r){if(r===void 0)r={};this.label=t;this.keyword=r.keyword;this.beforeExpr=!!r.beforeExpr;this.startsExpr=!!r.startsExpr;this.isLoop=!!r.isLoop;this.isAssign=!!r.isAssign;this.prefix=!!r.prefix;this.postfix=!!r.postfix;this.binop=r.binop||null;this.updateContext=null};function m(e,t){return new d(e,{beforeExpr:true,binop:t})}var v={beforeExpr:true};var g={startsExpr:true};var y={};function x(e,t){if(t===void 0)t={};t.keyword=e;return y[e]=new d(e,t)}var b={num:new d("num",g),regexp:new d("regexp",g),string:new d("string",g),name:new d("name",g),eof:new d("eof"),bracketL:new d("[",{beforeExpr:true,startsExpr:true}),bracketR:new d("]"),braceL:new d("{",{beforeExpr:true,startsExpr:true}),braceR:new d("}"),parenL:new d("(",{beforeExpr:true,startsExpr:true}),parenR:new d(")"),comma:new d(",",v),semi:new d(";",v),colon:new d(":",v),dot:new d("."),question:new d("?",v),arrow:new d("=>",v),template:new d("template"),invalidTemplate:new d("invalidTemplate"),ellipsis:new d("...",v),backQuote:new d("`",g),dollarBraceL:new d("${",{beforeExpr:true,startsExpr:true}),eq:new d("=",{beforeExpr:true,isAssign:true}),assign:new d("_=",{beforeExpr:true,isAssign:true}),incDec:new d("++/--",{prefix:true,postfix:true,startsExpr:true}),prefix:new d("!/~",{beforeExpr:true,prefix:true,startsExpr:true}),logicalOR:m("||",1),logicalAND:m("&&",2),bitwiseOR:m("|",3),bitwiseXOR:m("^",4),bitwiseAND:m("&",5),equality:m("==/!=/===/!==",6),relational:m("/<=/>=",7),bitShift:m("<>/>>>",8),plusMin:new d("+/-",{beforeExpr:true,binop:9,prefix:true,startsExpr:true}),modulo:m("%",10),star:m("*",10),slash:m("/",10),starstar:new d("**",{beforeExpr:true}),_break:x("break"),_case:x("case",v),_catch:x("catch"),_continue:x("continue"),_debugger:x("debugger"),_default:x("default",v),_do:x("do",{isLoop:true,beforeExpr:true}),_else:x("else",v),_finally:x("finally"),_for:x("for",{isLoop:true}),_function:x("function",g),_if:x("if"),_return:x("return",v),_switch:x("switch"),_throw:x("throw",v),_try:x("try"),_var:x("var"),_const:x("const"),_while:x("while",{isLoop:true}),_with:x("with"),_new:x("new",{beforeExpr:true,startsExpr:true}),_this:x("this",g),_super:x("super",g),_class:x("class",g),_extends:x("extends",v),_export:x("export"),_import:x("import"),_null:x("null",g),_true:x("true",g),_false:x("false",g),_in:x("in",{beforeExpr:true,binop:7}),_instanceof:x("instanceof",{beforeExpr:true,binop:7}),_typeof:x("typeof",{beforeExpr:true,prefix:true,startsExpr:true}),_void:x("void",{beforeExpr:true,prefix:true,startsExpr:true}),_delete:x("delete",{beforeExpr:true,prefix:true,startsExpr:true})};var w=/\r\n?|\n|\u2028|\u2029/;var k=new RegExp(w.source,"g");function C(e,t){return e===10||e===13||!t&&(e===8232||e===8233)}var S=/[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;var L=/(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g;var T=Object.prototype;var A=T.hasOwnProperty;var E=T.toString;function M(e,t){return A.call(e,t)}var N=Array.isArray||function(e){return E.call(e)==="[object Array]"};var _=function e(t,r){this.line=t;this.column=r};_.prototype.offset=function e(t){return new _(this.line,this.column+t)};var P=function e(t,r,i){this.start=r;this.end=i;if(t.sourceFile!==null){this.source=t.sourceFile}};function I(e,t){for(var r=1,i=0;;){k.lastIndex=i;var n=k.exec(e);if(n&&n.index=2015){t.ecmaVersion-=2009}if(t.allowReserved==null){t.allowReserved=t.ecmaVersion<5}if(N(t.onToken)){var i=t.onToken;t.onToken=function(e){return i.push(e)}}if(N(t.onComment)){t.onComment=R(t,t.onComment)}return t}function R(e,t){return function(r,i,n,a,s,o){var l={type:r?"Block":"Line",value:i,start:n,end:a};if(e.locations){l.loc=new P(this,s,o)}if(e.ranges){l.range=[n,a]}t.push(l)}}var V={};function F(e){return new RegExp("^(?:"+e.replace(/ /g,"|")+")$")}var W=function e(r,n,a){this.options=r=D(r);this.sourceFile=r.sourceFile;this.keywords=F(i[r.ecmaVersion>=6?6:5]);var s="";if(!r.allowReserved){for(var o=r.ecmaVersion;;o--){if(s=t[o]){break}}if(r.sourceType==="module"){s+=" await"}}this.reservedWords=F(s);var l=(s?s+" ":"")+t.strict;this.reservedWordsStrict=F(l);this.reservedWordsStrictBind=F(l+" "+t.strictBind);this.input=String(n);this.containsEsc=false;this.loadPlugins(r.plugins);if(a){this.pos=a;this.lineStart=this.input.lastIndexOf("\n",a-1)+1;this.curLine=this.input.slice(0,this.lineStart).split(w).length}else{this.pos=this.lineStart=0;this.curLine=1}this.type=b.eof;this.value=null;this.start=this.end=this.pos;this.startLoc=this.endLoc=this.curPosition();this.lastTokEndLoc=this.lastTokStartLoc=null;this.lastTokStart=this.lastTokEnd=this.pos;this.context=this.initialContext();this.exprAllowed=true;this.inModule=r.sourceType==="module";this.strict=this.inModule||this.strictDirective(this.pos);this.potentialArrowAt=-1;this.inFunction=this.inGenerator=this.inAsync=false;this.yieldPos=this.awaitPos=0;this.labels=[];if(this.pos===0&&r.allowHashBang&&this.input.slice(0,2)==="#!"){this.skipLineComment(2)}this.scopeStack=[];this.enterFunctionScope();this.regexpState=null};W.prototype.isKeyword=function e(t){return this.keywords.test(t)};W.prototype.isReservedWord=function e(t){return this.reservedWords.test(t)};W.prototype.extend=function e(t,r){this[t]=r(this[t])};W.prototype.loadPlugins=function e(t){var r=this;for(var i in t){var n=V[i];if(!n){throw new Error("Plugin '"+i+"' not found")}n(r,t[i])}};W.prototype.parse=function e(){var t=this.options.program||this.startNode();this.nextToken();return this.parseTopLevel(t)};var z=W.prototype;var B=/^(?:'((?:\\.|[^'])*?)'|"((?:\\.|[^"])*?)"|;)/;z.strictDirective=function(e){var t=this;for(;;){L.lastIndex=e;e+=L.exec(t.input)[0].length;var r=B.exec(t.input.slice(e));if(!r){return false}if((r[1]||r[2])==="use strict"){return true}e+=r[0].length}};z.eat=function(e){if(this.type===e){this.next();return true}else{return false}};z.isContextual=function(e){return this.type===b.name&&this.value===e&&!this.containsEsc};z.eatContextual=function(e){if(!this.isContextual(e)){return false}this.next();return true};z.expectContextual=function(e){if(!this.eatContextual(e)){this.unexpected()}};z.canInsertSemicolon=function(){return this.type===b.eof||this.type===b.braceR||w.test(this.input.slice(this.lastTokEnd,this.start))};z.insertSemicolon=function(){if(this.canInsertSemicolon()){if(this.options.onInsertedSemicolon){this.options.onInsertedSemicolon(this.lastTokEnd,this.lastTokEndLoc)}return true}};z.semicolon=function(){if(!this.eat(b.semi)&&!this.insertSemicolon()){this.unexpected()}};z.afterTrailingComma=function(e,t){if(this.type===e){if(this.options.onTrailingComma){this.options.onTrailingComma(this.lastTokStart,this.lastTokStartLoc)}if(!t){this.next()}return true}};z.expect=function(e){this.eat(e)||this.unexpected()};z.unexpected=function(e){this.raise(e!=null?e:this.start,"Unexpected token")};function H(){this.shorthandAssign=this.trailingComma=this.parenthesizedAssign=this.parenthesizedBind=this.doubleProto=-1}z.checkPatternErrors=function(e,t){if(!e){return}if(e.trailingComma>-1){this.raiseRecoverable(e.trailingComma,"Comma is not permitted after the rest element")}var r=t?e.parenthesizedAssign:e.parenthesizedBind;if(r>-1){this.raiseRecoverable(r,"Parenthesized pattern")}};z.checkExpressionErrors=function(e,t){if(!e){return false}var r=e.shorthandAssign;var i=e.doubleProto;if(!t){return r>=0||i>=0}if(r>=0){this.raise(r,"Shorthand property assignments are valid only in destructuring patterns")}if(i>=0){this.raiseRecoverable(i,"Redefinition of __proto__ property")}};z.checkYieldAwaitInDefaultParams=function(){if(this.yieldPos&&(!this.awaitPos||this.yieldPos=6){e.sourceType=this.options.sourceType}return this.finishNode(e,"Program")};var U={kind:"loop"};var G={kind:"switch"};j.isLet=function(){if(this.options.ecmaVersion<6||!this.isContextual("let")){return false}L.lastIndex=this.pos;var e=L.exec(this.input);var t=this.pos+e[0].length,r=this.input.charCodeAt(t);if(r===91||r===123){return true}if(h(r,true)){var i=t+1;while(p(this.input.charCodeAt(i),true)){++i}var a=this.input.slice(t,i);if(!n.test(a)){return true}}return false};j.isAsyncFunction=function(){if(this.options.ecmaVersion<8||!this.isContextual("async")){return false}L.lastIndex=this.pos;var e=L.exec(this.input);var t=this.pos+e[0].length;return!w.test(this.input.slice(this.pos,t))&&this.input.slice(t,t+8)==="function"&&(t+8===this.input.length||!p(this.input.charAt(t+8)))};j.parseStatement=function(e,t,r){var i=this.type,n=this.startNode(),a;if(this.isLet()){i=b._var;a="let"}switch(i){case b._break:case b._continue:return this.parseBreakContinueStatement(n,i.keyword);case b._debugger:return this.parseDebuggerStatement(n);case b._do:return this.parseDoStatement(n);case b._for:return this.parseForStatement(n);case b._function:if(!e&&this.options.ecmaVersion>=6){this.unexpected()}return this.parseFunctionStatement(n,false);case b._class:if(!e){this.unexpected()}return this.parseClass(n,true);case b._if:return this.parseIfStatement(n);case b._return:return this.parseReturnStatement(n);case b._switch:return this.parseSwitchStatement(n);case b._throw:return this.parseThrowStatement(n);case b._try:return this.parseTryStatement(n);case b._const:case b._var:a=a||this.value;if(!e&&a!=="var"){this.unexpected()}return this.parseVarStatement(n,a);case b._while:return this.parseWhileStatement(n);case b._with:return this.parseWithStatement(n);case b.braceL:return this.parseBlock();case b.semi:return this.parseEmptyStatement(n);case b._export:case b._import:if(!this.options.allowImportExportEverywhere){if(!t){this.raise(this.start,"'import' and 'export' may only appear at the top level")}if(!this.inModule){this.raise(this.start,"'import' and 'export' may appear only with 'sourceType: module'")}}return i===b._import?this.parseImport(n):this.parseExport(n,r);default:if(this.isAsyncFunction()){if(!e){this.unexpected()}this.next();return this.parseFunctionStatement(n,true)}var s=this.value,o=this.parseExpression();if(i===b.name&&o.type==="Identifier"&&this.eat(b.colon)){return this.parseLabeledStatement(n,s,o)}else{return this.parseExpressionStatement(n,o)}}};j.parseBreakContinueStatement=function(e,t){var r=this;var i=t==="break";this.next();if(this.eat(b.semi)||this.insertSemicolon()){e.label=null}else if(this.type!==b.name){this.unexpected()}else{e.label=this.parseIdent();this.semicolon()}var n=0;for(;n=6){this.eat(b.semi)}else{this.semicolon()}return this.finishNode(e,"DoWhileStatement")};j.parseForStatement=function(e){this.next();var t=this.options.ecmaVersion>=9&&(this.inAsync||!this.inFunction&&this.options.allowAwaitOutsideFunction)&&this.eatContextual("await")?this.lastTokStart:-1;this.labels.push(U);this.enterLexicalScope();this.expect(b.parenL);if(this.type===b.semi){if(t>-1){this.unexpected(t)}return this.parseFor(e,null)}var r=this.isLet();if(this.type===b._var||this.type===b._const||r){var i=this.startNode(),n=r?"let":this.value;this.next();this.parseVar(i,true,n);this.finishNode(i,"VariableDeclaration");if((this.type===b._in||this.options.ecmaVersion>=6&&this.isContextual("of"))&&i.declarations.length===1&&!(n!=="var"&&i.declarations[0].init)){if(this.options.ecmaVersion>=9){if(this.type===b._in){if(t>-1){this.unexpected(t)}}else{e.await=t>-1}}return this.parseForIn(e,i)}if(t>-1){this.unexpected(t)}return this.parseFor(e,i)}var a=new H;var s=this.parseExpression(true,a);if(this.type===b._in||this.options.ecmaVersion>=6&&this.isContextual("of")){if(this.options.ecmaVersion>=9){if(this.type===b._in){if(t>-1){this.unexpected(t)}}else{e.await=t>-1}}this.toAssignable(s,false,a);this.checkLVal(s);return this.parseForIn(e,s)}else{this.checkExpressionErrors(a,true)}if(t>-1){this.unexpected(t)}return this.parseFor(e,s)};j.parseFunctionStatement=function(e,t){this.next();return this.parseFunction(e,true,false,t)};j.parseIfStatement=function(e){this.next();e.test=this.parseParenExpression();e.consequent=this.parseStatement(!this.strict&&this.type===b._function);e.alternate=this.eat(b._else)?this.parseStatement(!this.strict&&this.type===b._function):null;return this.finishNode(e,"IfStatement")};j.parseReturnStatement=function(e){if(!this.inFunction&&!this.options.allowReturnOutsideFunction){this.raise(this.start,"'return' outside of function")}this.next();if(this.eat(b.semi)||this.insertSemicolon()){e.argument=null}else{e.argument=this.parseExpression();this.semicolon()}return this.finishNode(e,"ReturnStatement")};j.parseSwitchStatement=function(e){var t=this;this.next();e.discriminant=this.parseParenExpression();e.cases=[];this.expect(b.braceL);this.labels.push(G);this.enterLexicalScope();var r;for(var i=false;this.type!==b.braceR;){if(t.type===b._case||t.type===b._default){var n=t.type===b._case;if(r){t.finishNode(r,"SwitchCase")}e.cases.push(r=t.startNode());r.consequent=[];t.next();if(n){r.test=t.parseExpression()}else{if(i){t.raiseRecoverable(t.lastTokStart,"Multiple default clauses")}i=true;r.test=null}t.expect(b.colon)}else{if(!r){t.unexpected()}r.consequent.push(t.parseStatement(true))}}this.exitLexicalScope();if(r){this.finishNode(r,"SwitchCase")}this.next();this.labels.pop();return this.finishNode(e,"SwitchStatement")};j.parseThrowStatement=function(e){this.next();if(w.test(this.input.slice(this.lastTokEnd,this.start))){this.raise(this.lastTokEnd,"Illegal newline after throw")}e.argument=this.parseExpression();this.semicolon();return this.finishNode(e,"ThrowStatement")};var q=[];j.parseTryStatement=function(e){this.next();e.block=this.parseBlock();e.handler=null;if(this.type===b._catch){var t=this.startNode();this.next();if(this.eat(b.parenL)){t.param=this.parseBindingAtom();this.enterLexicalScope();this.checkLVal(t.param,"let");this.expect(b.parenR)}else{if(this.options.ecmaVersion<10){this.unexpected()}t.param=null;this.enterLexicalScope()}t.body=this.parseBlock(false);this.exitLexicalScope();e.handler=this.finishNode(t,"CatchClause")} +e.finalizer=this.eat(b._finally)?this.parseBlock():null;if(!e.handler&&!e.finalizer){this.raise(e.start,"Missing catch or finally clause")}return this.finishNode(e,"TryStatement")};j.parseVarStatement=function(e,t){this.next();this.parseVar(e,false,t);this.semicolon();return this.finishNode(e,"VariableDeclaration")};j.parseWhileStatement=function(e){this.next();e.test=this.parseParenExpression();this.labels.push(U);e.body=this.parseStatement(false);this.labels.pop();return this.finishNode(e,"WhileStatement")};j.parseWithStatement=function(e){if(this.strict){this.raise(this.start,"'with' in strict mode")}this.next();e.object=this.parseParenExpression();e.body=this.parseStatement(false);return this.finishNode(e,"WithStatement")};j.parseEmptyStatement=function(e){this.next();return this.finishNode(e,"EmptyStatement")};j.parseLabeledStatement=function(e,t,r){var i=this;for(var n=0,a=i.labels;n=0;l--){var u=i.labels[l];if(u.statementStart===e.start){u.statementStart=i.start;u.kind=o}else{break}}this.labels.push({name:t,kind:o,statementStart:this.start});e.body=this.parseStatement(true);if(e.body.type==="ClassDeclaration"||e.body.type==="VariableDeclaration"&&e.body.kind!=="var"||e.body.type==="FunctionDeclaration"&&(this.strict||e.body.generator||e.body.async)){this.raiseRecoverable(e.body.start,"Invalid labeled declaration")}this.labels.pop();e.label=r;return this.finishNode(e,"LabeledStatement")};j.parseExpressionStatement=function(e,t){e.expression=t;this.semicolon();return this.finishNode(e,"ExpressionStatement")};j.parseBlock=function(e){var t=this;if(e===void 0)e=true;var r=this.startNode();r.body=[];this.expect(b.braceL);if(e){this.enterLexicalScope()}while(!this.eat(b.braceR)){var i=t.parseStatement(true);r.body.push(i)}if(e){this.exitLexicalScope()}return this.finishNode(r,"BlockStatement")};j.parseFor=function(e,t){e.init=t;this.expect(b.semi);e.test=this.type===b.semi?null:this.parseExpression();this.expect(b.semi);e.update=this.type===b.parenR?null:this.parseExpression();this.expect(b.parenR);this.exitLexicalScope();e.body=this.parseStatement(false);this.labels.pop();return this.finishNode(e,"ForStatement")};j.parseForIn=function(e,t){var r=this.type===b._in?"ForInStatement":"ForOfStatement";this.next();if(r==="ForInStatement"){if(t.type==="AssignmentPattern"||t.type==="VariableDeclaration"&&t.declarations[0].init!=null&&(this.strict||t.declarations[0].id.type!=="Identifier")){this.raise(t.start,"Invalid assignment in for-in loop head")}}e.left=t;e.right=r==="ForInStatement"?this.parseExpression():this.parseMaybeAssign();this.expect(b.parenR);this.exitLexicalScope();e.body=this.parseStatement(false);this.labels.pop();return this.finishNode(e,r)};j.parseVar=function(e,t,r){var i=this;e.declarations=[];e.kind=r;for(;;){var n=i.startNode();i.parseVarId(n,r);if(i.eat(b.eq)){n.init=i.parseMaybeAssign(t)}else if(r==="const"&&!(i.type===b._in||i.options.ecmaVersion>=6&&i.isContextual("of"))){i.unexpected()}else if(n.id.type!=="Identifier"&&!(t&&(i.type===b._in||i.isContextual("of")))){i.raise(i.lastTokEnd,"Complex binding patterns require an initialization value")}else{n.init=null}e.declarations.push(i.finishNode(n,"VariableDeclarator"));if(!i.eat(b.comma)){break}}return e};j.parseVarId=function(e,t){e.id=this.parseBindingAtom(t);this.checkLVal(e.id,t,false)};j.parseFunction=function(e,t,r,i){this.initFunction(e);if(this.options.ecmaVersion>=9||this.options.ecmaVersion>=6&&!i){e.generator=this.eat(b.star)}if(this.options.ecmaVersion>=8){e.async=!!i}if(t){e.id=t==="nullableID"&&this.type!==b.name?null:this.parseIdent();if(e.id){this.checkLVal(e.id,this.inModule&&!this.inFunction?"let":"var")}}var n=this.inGenerator,a=this.inAsync,s=this.yieldPos,o=this.awaitPos,l=this.inFunction;this.inGenerator=e.generator;this.inAsync=e.async;this.yieldPos=0;this.awaitPos=0;this.inFunction=true;this.enterFunctionScope();if(!t){e.id=this.type===b.name?this.parseIdent():null}this.parseFunctionParams(e);this.parseFunctionBody(e,r);this.inGenerator=n;this.inAsync=a;this.yieldPos=s;this.awaitPos=o;this.inFunction=l;return this.finishNode(e,t?"FunctionDeclaration":"FunctionExpression")};j.parseFunctionParams=function(e){this.expect(b.parenL);e.params=this.parseBindingList(b.parenR,false,this.options.ecmaVersion>=8);this.checkYieldAwaitInDefaultParams()};j.parseClass=function(e,t){var r=this;this.next();this.parseClassId(e,t);this.parseClassSuper(e);var i=this.startNode();var n=false;i.body=[];this.expect(b.braceL);while(!this.eat(b.braceR)){var a=r.parseClassMember(i);if(a&&a.type==="MethodDefinition"&&a.kind==="constructor"){if(n){r.raise(a.start,"Duplicate constructor in the same class")}n=true}}e.body=this.finishNode(i,"ClassBody");return this.finishNode(e,t?"ClassDeclaration":"ClassExpression")};j.parseClassMember=function(e){var t=this;if(this.eat(b.semi)){return null}var r=this.startNode();var i=function(e,i){if(i===void 0)i=false;var n=t.start,a=t.startLoc;if(!t.eatContextual(e)){return false}if(t.type!==b.parenL&&(!i||!t.canInsertSemicolon())){return true}if(r.key){t.unexpected()}r.computed=false;r.key=t.startNodeAt(n,a);r.key.name=e;t.finishNode(r.key,"Identifier");return false};r.kind="method";r.static=i("static");var n=this.eat(b.star);var a=false;if(!n){if(this.options.ecmaVersion>=8&&i("async",true)){a=true;n=this.options.ecmaVersion>=9&&this.eat(b.star)}else if(i("get")){r.kind="get"}else if(i("set")){r.kind="set"}}if(!r.key){this.parsePropertyName(r)}var s=r.key;if(!r.computed&&!r.static&&(s.type==="Identifier"&&s.name==="constructor"||s.type==="Literal"&&s.value==="constructor")){if(r.kind!=="method"){this.raise(s.start,"Constructor can't have get/set modifier")}if(n){this.raise(s.start,"Constructor can't be a generator")}if(a){this.raise(s.start,"Constructor can't be an async method")}r.kind="constructor"}else if(r.static&&s.type==="Identifier"&&s.name==="prototype"){this.raise(s.start,"Classes may not have a static property named prototype")}this.parseClassMethod(e,r,n,a);if(r.kind==="get"&&r.value.params.length!==0){this.raiseRecoverable(r.value.start,"getter should have no params")}if(r.kind==="set"&&r.value.params.length!==1){this.raiseRecoverable(r.value.start,"setter should have exactly one param")}if(r.kind==="set"&&r.value.params[0].type==="RestElement"){this.raiseRecoverable(r.value.params[0].start,"Setter cannot use rest params")}return r};j.parseClassMethod=function(e,t,r,i){t.value=this.parseMethod(r,i);e.body.push(this.finishNode(t,"MethodDefinition"))};j.parseClassId=function(e,t){e.id=this.type===b.name?this.parseIdent():t===true?this.unexpected():null};j.parseClassSuper=function(e){e.superClass=this.eat(b._extends)?this.parseExprSubscripts():null};j.parseExport=function(e,t){var r=this;this.next();if(this.eat(b.star)){this.expectContextual("from");if(this.type!==b.string){this.unexpected()}e.source=this.parseExprAtom();this.semicolon();return this.finishNode(e,"ExportAllDeclaration")}if(this.eat(b._default)){this.checkExport(t,"default",this.lastTokStart);var i;if(this.type===b._function||(i=this.isAsyncFunction())){var n=this.startNode();this.next();if(i){this.next()}e.declaration=this.parseFunction(n,"nullableID",false,i)}else if(this.type===b._class){var a=this.startNode();e.declaration=this.parseClass(a,"nullableID")}else{e.declaration=this.parseMaybeAssign();this.semicolon()}return this.finishNode(e,"ExportDefaultDeclaration")}if(this.shouldParseExportStatement()){e.declaration=this.parseStatement(true);if(e.declaration.type==="VariableDeclaration"){this.checkVariableExport(t,e.declaration.declarations)}else{this.checkExport(t,e.declaration.id.name,e.declaration.id.start)}e.specifiers=[];e.source=null}else{e.declaration=null;e.specifiers=this.parseExportSpecifiers(t);if(this.eatContextual("from")){if(this.type!==b.string){this.unexpected()}e.source=this.parseExprAtom()}else{for(var s=0,o=e.specifiers;s=6&&e){switch(e.type){case"Identifier":if(this.inAsync&&e.name==="await"){this.raise(e.start,"Can not use 'await' as identifier inside an async function")}break;case"ObjectPattern":case"ArrayPattern":case"RestElement":break;case"ObjectExpression":e.type="ObjectPattern";if(r){this.checkPatternErrors(r,true)}for(var n=0,a=e.properties;n=9&&e.type==="SpreadElement"){return}if(this.options.ecmaVersion>=6&&(e.computed||e.method||e.shorthand)){return}var i=e.key;var n;switch(i.type){case"Identifier":n=i.name;break;case"Literal":n=String(i.value);break;default:return}var a=e.kind;if(this.options.ecmaVersion>=6){if(n==="__proto__"&&a==="init"){if(t.proto){if(r&&r.doubleProto<0){r.doubleProto=i.start}else{this.raiseRecoverable(i.start,"Redefinition of __proto__ property")}}t.proto=true}return}n="$"+n;var s=t[n];if(s){var o;if(a==="init"){o=this.strict&&s.init||s.get||s.set}else{o=s.init||s[a]}if(o){this.raiseRecoverable(i.start,"Redefinition of property")}}else{s=t[n]={init:false,get:false,set:false}}s[a]=true};$.parseExpression=function(e,t){var r=this;var i=this.start,n=this.startLoc;var a=this.parseMaybeAssign(e,t);if(this.type===b.comma){var s=this.startNodeAt(i,n);s.expressions=[a];while(this.eat(b.comma)){s.expressions.push(r.parseMaybeAssign(e,t))}return this.finishNode(s,"SequenceExpression")}return a};$.parseMaybeAssign=function(e,t,r){if(this.inGenerator&&this.isContextual("yield")){return this.parseYield()}var i=false,n=-1,a=-1;if(t){n=t.parenthesizedAssign;a=t.trailingComma;t.parenthesizedAssign=t.trailingComma=-1}else{t=new H;i=true}var s=this.start,o=this.startLoc;if(this.type===b.parenL||this.type===b.name){this.potentialArrowAt=this.start}var l=this.parseMaybeConditional(e,t);if(r){l=r.call(this,l,s,o)}if(this.type.isAssign){var u=this.startNodeAt(s,o);u.operator=this.value;u.left=this.type===b.eq?this.toAssignable(l,false,t):l;if(!i){H.call(t)}t.shorthandAssign=-1;this.checkLVal(l);this.next();u.right=this.parseMaybeAssign(e);return this.finishNode(u,"AssignmentExpression")}else{if(i){this.checkExpressionErrors(t,true)}}if(n>-1){t.parenthesizedAssign=n}if(a>-1){t.trailingComma=a}return l};$.parseMaybeConditional=function(e,t){var r=this.start,i=this.startLoc;var n=this.parseExprOps(e,t);if(this.checkExpressionErrors(t)){return n}if(this.eat(b.question)){var a=this.startNodeAt(r,i);a.test=n;a.consequent=this.parseMaybeAssign();this.expect(b.colon);a.alternate=this.parseMaybeAssign(e);return this.finishNode(a,"ConditionalExpression")}return n};$.parseExprOps=function(e,t){var r=this.start,i=this.startLoc;var n=this.parseMaybeUnary(t,false);if(this.checkExpressionErrors(t)){return n}return n.start===r&&n.type==="ArrowFunctionExpression"?n:this.parseExprOp(n,r,i,-1,e)};$.parseExprOp=function(e,t,r,i,n){var a=this.type.binop;if(a!=null&&(!n||this.type!==b._in)){if(a>i){var s=this.type===b.logicalOR||this.type===b.logicalAND;var o=this.value;this.next();var l=this.start,u=this.startLoc;var c=this.parseExprOp(this.parseMaybeUnary(null,false),l,u,a,n);var f=this.buildBinary(t,r,e,c,o,s);return this.parseExprOp(f,t,r,i,n)}}return e};$.buildBinary=function(e,t,r,i,n,a){var s=this.startNodeAt(e,t);s.left=r;s.operator=n;s.right=i;return this.finishNode(s,a?"LogicalExpression":"BinaryExpression")};$.parseMaybeUnary=function(e,t){var r=this;var i=this.start,n=this.startLoc,a;if(this.isContextual("await")&&(this.inAsync||!this.inFunction&&this.options.allowAwaitOutsideFunction)){a=this.parseAwait();t=true}else if(this.type.prefix){var s=this.startNode(),o=this.type===b.incDec;s.operator=this.value;s.prefix=true;this.next();s.argument=this.parseMaybeUnary(null,true);this.checkExpressionErrors(e,true);if(o){this.checkLVal(s.argument)}else if(this.strict&&s.operator==="delete"&&s.argument.type==="Identifier"){this.raiseRecoverable(s.start,"Deleting local variable in strict mode")}else{t=true}a=this.finishNode(s,o?"UpdateExpression":"UnaryExpression")}else{a=this.parseExprSubscripts(e);if(this.checkExpressionErrors(e)){return a}while(this.type.postfix&&!this.canInsertSemicolon()){var l=r.startNodeAt(i,n);l.operator=r.value;l.prefix=false;l.argument=a;r.checkLVal(a);r.next();a=r.finishNode(l,"UpdateExpression")}}if(!t&&this.eat(b.starstar)){return this.buildBinary(i,n,a,this.parseMaybeUnary(null,false),"**",false)}else{return a}};$.parseExprSubscripts=function(e){var t=this.start,r=this.startLoc;var i=this.parseExprAtom(e);var n=i.type==="ArrowFunctionExpression"&&this.input.slice(this.lastTokStart,this.lastTokEnd)!==")";if(this.checkExpressionErrors(e)||n){return i}var a=this.parseSubscripts(i,t,r);if(e&&a.type==="MemberExpression"){if(e.parenthesizedAssign>=a.start){e.parenthesizedAssign=-1}if(e.parenthesizedBind>=a.start){e.parenthesizedBind=-1}}return a};$.parseSubscripts=function(e,t,r,i){var n=this;var a=this.options.ecmaVersion>=8&&e.type==="Identifier"&&e.name==="async"&&this.lastTokEnd===e.end&&!this.canInsertSemicolon()&&this.input.slice(e.start,e.end)==="async";for(var s=void 0;;){if((s=n.eat(b.bracketL))||n.eat(b.dot)){var o=n.startNodeAt(t,r);o.object=e;o.property=s?n.parseExpression():n.parseIdent(true);o.computed=!!s;if(s){n.expect(b.bracketR)}e=n.finishNode(o,"MemberExpression")}else if(!i&&n.eat(b.parenL)){var l=new H,u=n.yieldPos,c=n.awaitPos;n.yieldPos=0;n.awaitPos=0;var f=n.parseExprList(b.parenR,n.options.ecmaVersion>=8,false,l);if(a&&!n.canInsertSemicolon()&&n.eat(b.arrow)){n.checkPatternErrors(l,false);n.checkYieldAwaitInDefaultParams();n.yieldPos=u;n.awaitPos=c;return n.parseArrowExpression(n.startNodeAt(t,r),f,true)}n.checkExpressionErrors(l,true);n.yieldPos=u||n.yieldPos;n.awaitPos=c||n.awaitPos;var h=n.startNodeAt(t,r);h.callee=e;h.arguments=f;e=n.finishNode(h,"CallExpression")}else if(n.type===b.backQuote){var p=n.startNodeAt(t,r);p.tag=e;p.quasi=n.parseTemplate({isTagged:true});e=n.finishNode(p,"TaggedTemplateExpression")}else{return e}}};$.parseExprAtom=function(e){var t,r=this.potentialArrowAt===this.start;switch(this.type){case b._super:if(!this.inFunction){this.raise(this.start,"'super' outside of function or class")}t=this.startNode();this.next();if(this.type!==b.dot&&this.type!==b.bracketL&&this.type!==b.parenL){this.unexpected()}return this.finishNode(t,"Super");case b._this:t=this.startNode();this.next();return this.finishNode(t,"ThisExpression");case b.name:var i=this.start,n=this.startLoc,a=this.containsEsc;var s=this.parseIdent(this.type!==b.name);if(this.options.ecmaVersion>=8&&!a&&s.name==="async"&&!this.canInsertSemicolon()&&this.eat(b._function)){return this.parseFunction(this.startNodeAt(i,n),false,false,true)}if(r&&!this.canInsertSemicolon()){if(this.eat(b.arrow)){return this.parseArrowExpression(this.startNodeAt(i,n),[s],false)}if(this.options.ecmaVersion>=8&&s.name==="async"&&this.type===b.name&&!a){s=this.parseIdent();if(this.canInsertSemicolon()||!this.eat(b.arrow)){this.unexpected()}return this.parseArrowExpression(this.startNodeAt(i,n),[s],true)}}return s;case b.regexp:var o=this.value;t=this.parseLiteral(o.value);t.regex={pattern:o.pattern,flags:o.flags};return t;case b.num:case b.string:return this.parseLiteral(this.value);case b._null:case b._true:case b._false:t=this.startNode();t.value=this.type===b._null?null:this.type===b._true;t.raw=this.type.keyword;this.next();return this.finishNode(t,"Literal");case b.parenL:var l=this.start,u=this.parseParenAndDistinguishExpression(r);if(e){if(e.parenthesizedAssign<0&&!this.isSimpleAssignTarget(u)){e.parenthesizedAssign=l}if(e.parenthesizedBind<0){e.parenthesizedBind=l}}return u;case b.bracketL:t=this.startNode();this.next();t.elements=this.parseExprList(b.bracketR,true,true,e);return this.finishNode(t,"ArrayExpression");case b.braceL:return this.parseObj(false,e);case b._function:t=this.startNode();this.next();return this.parseFunction(t,false);case b._class:return this.parseClass(this.startNode(),false);case b._new:return this.parseNew();case b.backQuote:return this.parseTemplate();default:this.unexpected()}};$.parseLiteral=function(e){var t=this.startNode();t.value=e;t.raw=this.input.slice(this.start,this.end);this.next();return this.finishNode(t,"Literal")};$.parseParenExpression=function(){this.expect(b.parenL);var e=this.parseExpression();this.expect(b.parenR);return e};$.parseParenAndDistinguishExpression=function(e){var t=this;var r=this.start,i=this.startLoc,n,a=this.options.ecmaVersion>=8;if(this.options.ecmaVersion>=6){this.next();var s=this.start,o=this.startLoc;var l=[],u=true,c=false;var f=new H,h=this.yieldPos,p=this.awaitPos,d;this.yieldPos=0;this.awaitPos=0;while(this.type!==b.parenR){u?u=false:t.expect(b.comma);if(a&&t.afterTrailingComma(b.parenR,true)){c=true;break}else if(t.type===b.ellipsis){d=t.start;l.push(t.parseParenItem(t.parseRestBinding()));if(t.type===b.comma){t.raise(t.start,"Comma is not permitted after the rest element")}break}else{l.push(t.parseMaybeAssign(false,f,t.parseParenItem))}}var m=this.start,v=this.startLoc;this.expect(b.parenR);if(e&&!this.canInsertSemicolon()&&this.eat(b.arrow)){this.checkPatternErrors(f,false);this.checkYieldAwaitInDefaultParams();this.yieldPos=h;this.awaitPos=p;return this.parseParenArrowList(r,i,l)}if(!l.length||c){this.unexpected(this.lastTokStart)}if(d){this.unexpected(d)}this.checkExpressionErrors(f,true);this.yieldPos=h||this.yieldPos;this.awaitPos=p||this.awaitPos;if(l.length>1){n=this.startNodeAt(s,o);n.expressions=l;this.finishNodeAt(n,"SequenceExpression",m,v)}else{n=l[0]}}else{n=this.parseParenExpression()}if(this.options.preserveParens){var g=this.startNodeAt(r,i);g.expression=n;return this.finishNode(g,"ParenthesizedExpression")}else{return n}};$.parseParenItem=function(e){return e};$.parseParenArrowList=function(e,t,r){return this.parseArrowExpression(this.startNodeAt(e,t),r)};var X=[];$.parseNew=function(){var e=this.startNode();var t=this.parseIdent(true);if(this.options.ecmaVersion>=6&&this.eat(b.dot)){e.meta=t;var r=this.containsEsc;e.property=this.parseIdent(true);if(e.property.name!=="target"||r){this.raiseRecoverable(e.property.start,"The only valid meta property for new is new.target")}if(!this.inFunction){this.raiseRecoverable(e.start,"new.target can only be used in functions")}return this.finishNode(e,"MetaProperty")}var i=this.start,n=this.startLoc;e.callee=this.parseSubscripts(this.parseExprAtom(),i,n,true);if(this.eat(b.parenL)){e.arguments=this.parseExprList(b.parenR,this.options.ecmaVersion>=8,false)}else{e.arguments=X}return this.finishNode(e,"NewExpression")};$.parseTemplateElement=function(e){var t=e.isTagged;var r=this.startNode();if(this.type===b.invalidTemplate){if(!t){this.raiseRecoverable(this.start,"Bad escape sequence in untagged template literal")}r.value={raw:this.value,cooked:null}}else{r.value={raw:this.input.slice(this.start,this.end).replace(/\r\n?/g,"\n"),cooked:this.value}}this.next();r.tail=this.type===b.backQuote;return this.finishNode(r,"TemplateElement")};$.parseTemplate=function(e){var t=this;if(e===void 0)e={};var r=e.isTagged;if(r===void 0)r=false;var i=this.startNode();this.next();i.expressions=[];var n=this.parseTemplateElement({isTagged:r});i.quasis=[n];while(!n.tail){if(t.type===b.eof){t.raise(t.pos,"Unterminated template literal")}t.expect(b.dollarBraceL);i.expressions.push(t.parseExpression());t.expect(b.braceR);i.quasis.push(n=t.parseTemplateElement({isTagged:r}))}this.next();return this.finishNode(i,"TemplateLiteral")};$.isAsyncProp=function(e){return!e.computed&&e.key.type==="Identifier"&&e.key.name==="async"&&(this.type===b.name||this.type===b.num||this.type===b.string||this.type===b.bracketL||this.type.keyword||this.options.ecmaVersion>=9&&this.type===b.star)&&!w.test(this.input.slice(this.lastTokEnd,this.start))};$.parseObj=function(e,t){var r=this;var i=this.startNode(),n=true,a={};i.properties=[];this.next();while(!this.eat(b.braceR)){if(!n){r.expect(b.comma);if(r.afterTrailingComma(b.braceR)){break}}else{n=false}var s=r.parseProperty(e,t);if(!e){r.checkPropClash(s,a,t)}i.properties.push(s)}return this.finishNode(i,e?"ObjectPattern":"ObjectExpression")};$.parseProperty=function(e,t){var r=this.startNode(),i,n,a,s;if(this.options.ecmaVersion>=9&&this.eat(b.ellipsis)){if(e){r.argument=this.parseIdent(false);if(this.type===b.comma){this.raise(this.start,"Comma is not permitted after the rest element")}return this.finishNode(r,"RestElement")}if(this.type===b.parenL&&t){if(t.parenthesizedAssign<0){t.parenthesizedAssign=this.start}if(t.parenthesizedBind<0){t.parenthesizedBind=this.start}}r.argument=this.parseMaybeAssign(false,t);if(this.type===b.comma&&t&&t.trailingComma<0){t.trailingComma=this.start}return this.finishNode(r,"SpreadElement")}if(this.options.ecmaVersion>=6){r.method=false;r.shorthand=false;if(e||t){a=this.start;s=this.startLoc}if(!e){i=this.eat(b.star)}}var o=this.containsEsc;this.parsePropertyName(r);if(!e&&!o&&this.options.ecmaVersion>=8&&!i&&this.isAsyncProp(r)){n=true;i=this.options.ecmaVersion>=9&&this.eat(b.star);this.parsePropertyName(r,t)}else{n=false}this.parsePropertyValue(r,e,i,n,a,s,t,o);return this.finishNode(r,"Property")};$.parsePropertyValue=function(e,t,r,i,n,a,s,o){if((r||i)&&this.type===b.colon){this.unexpected()}if(this.eat(b.colon)){e.value=t?this.parseMaybeDefault(this.start,this.startLoc):this.parseMaybeAssign(false,s);e.kind="init"}else if(this.options.ecmaVersion>=6&&this.type===b.parenL){if(t){this.unexpected()}e.kind="init";e.method=true;e.value=this.parseMethod(r,i)}else if(!t&&!o&&this.options.ecmaVersion>=5&&!e.computed&&e.key.type==="Identifier"&&(e.key.name==="get"||e.key.name==="set")&&(this.type!==b.comma&&this.type!==b.braceR)){if(r||i){this.unexpected()}e.kind=e.key.name;this.parsePropertyName(e);e.value=this.parseMethod(false);var l=e.kind==="get"?0:1;if(e.value.params.length!==l){var u=e.value.start;if(e.kind==="get"){this.raiseRecoverable(u,"getter should have no params")}else{this.raiseRecoverable(u,"setter should have exactly one param")}}else{if(e.kind==="set"&&e.value.params[0].type==="RestElement"){this.raiseRecoverable(e.value.params[0].start,"Setter cannot use rest params")}}}else if(this.options.ecmaVersion>=6&&!e.computed&&e.key.type==="Identifier"){this.checkUnreserved(e.key);e.kind="init";if(t){e.value=this.parseMaybeDefault(n,a,e.key)}else if(this.type===b.eq&&s){if(s.shorthandAssign<0){s.shorthandAssign=this.start}e.value=this.parseMaybeDefault(n,a,e.key)}else{e.value=e.key}e.shorthand=true}else{this.unexpected()}};$.parsePropertyName=function(e){if(this.options.ecmaVersion>=6){if(this.eat(b.bracketL)){e.computed=true;e.key=this.parseMaybeAssign();this.expect(b.bracketR);return e.key}else{e.computed=false}}return e.key=this.type===b.num||this.type===b.string?this.parseExprAtom():this.parseIdent(true)};$.initFunction=function(e){e.id=null;if(this.options.ecmaVersion>=6){e.generator=false;e.expression=false}if(this.options.ecmaVersion>=8){e.async=false}};$.parseMethod=function(e,t){var r=this.startNode(),i=this.inGenerator,n=this.inAsync,a=this.yieldPos,s=this.awaitPos,o=this.inFunction;this.initFunction(r);if(this.options.ecmaVersion>=6){r.generator=e}if(this.options.ecmaVersion>=8){r.async=!!t}this.inGenerator=r.generator;this.inAsync=r.async;this.yieldPos=0;this.awaitPos=0;this.inFunction=true;this.enterFunctionScope();this.expect(b.parenL);r.params=this.parseBindingList(b.parenR,false,this.options.ecmaVersion>=8);this.checkYieldAwaitInDefaultParams();this.parseFunctionBody(r,false);this.inGenerator=i;this.inAsync=n;this.yieldPos=a;this.awaitPos=s;this.inFunction=o;return this.finishNode(r,"FunctionExpression")};$.parseArrowExpression=function(e,t,r){var i=this.inGenerator,n=this.inAsync,a=this.yieldPos,s=this.awaitPos,o=this.inFunction;this.enterFunctionScope();this.initFunction(e);if(this.options.ecmaVersion>=8){e.async=!!r}this.inGenerator=false;this.inAsync=e.async;this.yieldPos=0;this.awaitPos=0;this.inFunction=true;e.params=this.toAssignableList(t,true);this.parseFunctionBody(e,true);this.inGenerator=i;this.inAsync=n;this.yieldPos=a;this.awaitPos=s;this.inFunction=o;return this.finishNode(e,"ArrowFunctionExpression")};$.parseFunctionBody=function(e,t){var r=t&&this.type!==b.braceL;var i=this.strict,n=false;if(r){e.body=this.parseMaybeAssign();e.expression=true;this.checkParams(e,false)}else{var a=this.options.ecmaVersion>=7&&!this.isSimpleParamList(e.params);if(!i||a){n=this.strictDirective(this.end);if(n&&a){this.raiseRecoverable(e.start,"Illegal 'use strict' directive in function with non-simple parameter list")}}var s=this.labels;this.labels=[];if(n){this.strict=true}this.checkParams(e,!i&&!n&&!t&&this.isSimpleParamList(e.params));e.body=this.parseBlock(false);e.expression=false;this.adaptDirectivePrologue(e.body.body);this.labels=s}this.exitFunctionScope();if(this.strict&&e.id){ +this.checkLVal(e.id,"none")}this.strict=i};$.isSimpleParamList=function(e){for(var t=0,r=e;t0)t[r]=arguments[r+1];for(var i=0,n=t;i=1;t--){var r=e.context[t];if(r.token==="function"){return r.generator}}return false};ne.updateContext=function(e){var t,r=this.type;if(r.keyword&&e===b.dot){this.exprAllowed=false}else if(t=r.updateContext){t.call(this,e)}else{this.exprAllowed=r.beforeExpr}};b.parenR.updateContext=b.braceR.updateContext=function(){if(this.context.length===1){this.exprAllowed=true;return}var e=this.context.pop();if(e===ie.b_stat&&this.curContext().token==="function"){e=this.context.pop()}this.exprAllowed=!e.isExpr};b.braceL.updateContext=function(e){this.context.push(this.braceIsBlock(e)?ie.b_stat:ie.b_expr);this.exprAllowed=true};b.dollarBraceL.updateContext=function(){this.context.push(ie.b_tmpl);this.exprAllowed=true};b.parenL.updateContext=function(e){var t=e===b._if||e===b._for||e===b._with||e===b._while;this.context.push(t?ie.p_stat:ie.p_expr);this.exprAllowed=true};b.incDec.updateContext=function(){};b._function.updateContext=b._class.updateContext=function(e){if(e.beforeExpr&&e!==b.semi&&e!==b._else&&!((e===b.colon||e===b.braceL)&&this.curContext()===ie.b_stat)){this.context.push(ie.f_expr)}else{this.context.push(ie.f_stat)}this.exprAllowed=false};b.backQuote.updateContext=function(){if(this.curContext()===ie.q_tmpl){this.context.pop()}else{this.context.push(ie.q_tmpl)}this.exprAllowed=false};b.star.updateContext=function(e){if(e===b._function){var t=this.context.length-1;if(this.context[t]===ie.f_expr){this.context[t]=ie.f_expr_gen}else{this.context[t]=ie.f_gen}}this.exprAllowed=true};b.name.updateContext=function(e){var t=false;if(this.options.ecmaVersion>=6&&e!==b.dot){if(this.value==="of"&&!this.exprAllowed||this.value==="yield"&&this.inGeneratorContext()){t=true}}this.exprAllowed=t};var ae={$LONE:["ASCII","ASCII_Hex_Digit","AHex","Alphabetic","Alpha","Any","Assigned","Bidi_Control","Bidi_C","Bidi_Mirrored","Bidi_M","Case_Ignorable","CI","Cased","Changes_When_Casefolded","CWCF","Changes_When_Casemapped","CWCM","Changes_When_Lowercased","CWL","Changes_When_NFKC_Casefolded","CWKCF","Changes_When_Titlecased","CWT","Changes_When_Uppercased","CWU","Dash","Default_Ignorable_Code_Point","DI","Deprecated","Dep","Diacritic","Dia","Emoji","Emoji_Component","Emoji_Modifier","Emoji_Modifier_Base","Emoji_Presentation","Extender","Ext","Grapheme_Base","Gr_Base","Grapheme_Extend","Gr_Ext","Hex_Digit","Hex","IDS_Binary_Operator","IDSB","IDS_Trinary_Operator","IDST","ID_Continue","IDC","ID_Start","IDS","Ideographic","Ideo","Join_Control","Join_C","Logical_Order_Exception","LOE","Lowercase","Lower","Math","Noncharacter_Code_Point","NChar","Pattern_Syntax","Pat_Syn","Pattern_White_Space","Pat_WS","Quotation_Mark","QMark","Radical","Regional_Indicator","RI","Sentence_Terminal","STerm","Soft_Dotted","SD","Terminal_Punctuation","Term","Unified_Ideograph","UIdeo","Uppercase","Upper","Variation_Selector","VS","White_Space","space","XID_Continue","XIDC","XID_Start","XIDS"],General_Category:["Cased_Letter","LC","Close_Punctuation","Pe","Connector_Punctuation","Pc","Control","Cc","cntrl","Currency_Symbol","Sc","Dash_Punctuation","Pd","Decimal_Number","Nd","digit","Enclosing_Mark","Me","Final_Punctuation","Pf","Format","Cf","Initial_Punctuation","Pi","Letter","L","Letter_Number","Nl","Line_Separator","Zl","Lowercase_Letter","Ll","Mark","M","Combining_Mark","Math_Symbol","Sm","Modifier_Letter","Lm","Modifier_Symbol","Sk","Nonspacing_Mark","Mn","Number","N","Open_Punctuation","Ps","Other","C","Other_Letter","Lo","Other_Number","No","Other_Punctuation","Po","Other_Symbol","So","Paragraph_Separator","Zp","Private_Use","Co","Punctuation","P","punct","Separator","Z","Space_Separator","Zs","Spacing_Mark","Mc","Surrogate","Cs","Symbol","S","Titlecase_Letter","Lt","Unassigned","Cn","Uppercase_Letter","Lu"],Script:["Adlam","Adlm","Ahom","Anatolian_Hieroglyphs","Hluw","Arabic","Arab","Armenian","Armn","Avestan","Avst","Balinese","Bali","Bamum","Bamu","Bassa_Vah","Bass","Batak","Batk","Bengali","Beng","Bhaiksuki","Bhks","Bopomofo","Bopo","Brahmi","Brah","Braille","Brai","Buginese","Bugi","Buhid","Buhd","Canadian_Aboriginal","Cans","Carian","Cari","Caucasian_Albanian","Aghb","Chakma","Cakm","Cham","Cherokee","Cher","Common","Zyyy","Coptic","Copt","Qaac","Cuneiform","Xsux","Cypriot","Cprt","Cyrillic","Cyrl","Deseret","Dsrt","Devanagari","Deva","Duployan","Dupl","Egyptian_Hieroglyphs","Egyp","Elbasan","Elba","Ethiopic","Ethi","Georgian","Geor","Glagolitic","Glag","Gothic","Goth","Grantha","Gran","Greek","Grek","Gujarati","Gujr","Gurmukhi","Guru","Han","Hani","Hangul","Hang","Hanunoo","Hano","Hatran","Hatr","Hebrew","Hebr","Hiragana","Hira","Imperial_Aramaic","Armi","Inherited","Zinh","Qaai","Inscriptional_Pahlavi","Phli","Inscriptional_Parthian","Prti","Javanese","Java","Kaithi","Kthi","Kannada","Knda","Katakana","Kana","Kayah_Li","Kali","Kharoshthi","Khar","Khmer","Khmr","Khojki","Khoj","Khudawadi","Sind","Lao","Laoo","Latin","Latn","Lepcha","Lepc","Limbu","Limb","Linear_A","Lina","Linear_B","Linb","Lisu","Lycian","Lyci","Lydian","Lydi","Mahajani","Mahj","Malayalam","Mlym","Mandaic","Mand","Manichaean","Mani","Marchen","Marc","Masaram_Gondi","Gonm","Meetei_Mayek","Mtei","Mende_Kikakui","Mend","Meroitic_Cursive","Merc","Meroitic_Hieroglyphs","Mero","Miao","Plrd","Modi","Mongolian","Mong","Mro","Mroo","Multani","Mult","Myanmar","Mymr","Nabataean","Nbat","New_Tai_Lue","Talu","Newa","Nko","Nkoo","Nushu","Nshu","Ogham","Ogam","Ol_Chiki","Olck","Old_Hungarian","Hung","Old_Italic","Ital","Old_North_Arabian","Narb","Old_Permic","Perm","Old_Persian","Xpeo","Old_South_Arabian","Sarb","Old_Turkic","Orkh","Oriya","Orya","Osage","Osge","Osmanya","Osma","Pahawh_Hmong","Hmng","Palmyrene","Palm","Pau_Cin_Hau","Pauc","Phags_Pa","Phag","Phoenician","Phnx","Psalter_Pahlavi","Phlp","Rejang","Rjng","Runic","Runr","Samaritan","Samr","Saurashtra","Saur","Sharada","Shrd","Shavian","Shaw","Siddham","Sidd","SignWriting","Sgnw","Sinhala","Sinh","Sora_Sompeng","Sora","Soyombo","Soyo","Sundanese","Sund","Syloti_Nagri","Sylo","Syriac","Syrc","Tagalog","Tglg","Tagbanwa","Tagb","Tai_Le","Tale","Tai_Tham","Lana","Tai_Viet","Tavt","Takri","Takr","Tamil","Taml","Tangut","Tang","Telugu","Telu","Thaana","Thaa","Thai","Tibetan","Tibt","Tifinagh","Tfng","Tirhuta","Tirh","Ugaritic","Ugar","Vai","Vaii","Warang_Citi","Wara","Yi","Yiii","Zanabazar_Square","Zanb"]};Array.prototype.push.apply(ae.$LONE,ae.General_Category);ae.gc=ae.General_Category;ae.sc=ae.Script_Extensions=ae.scx=ae.Script;var se=W.prototype;var oe=function e(t){this.parser=t;this.validFlags="gim"+(t.options.ecmaVersion>=6?"uy":"")+(t.options.ecmaVersion>=9?"s":"");this.source="";this.flags="";this.start=0;this.switchU=false;this.switchN=false;this.pos=0;this.lastIntValue=0;this.lastStringValue="";this.lastAssertionIsQuantifiable=false;this.numCapturingParens=0;this.maxBackReference=0;this.groupNames=[];this.backReferenceNames=[]};oe.prototype.reset=function e(t,r,i){var n=i.indexOf("u")!==-1;this.start=t|0;this.source=r+"";this.flags=i;this.switchU=n&&this.parser.options.ecmaVersion>=6;this.switchN=n&&this.parser.options.ecmaVersion>=9};oe.prototype.raise=function e(t){this.parser.raiseRecoverable(this.start,"Invalid regular expression: /"+this.source+"/: "+t)};oe.prototype.at=function e(t){var r=this.source;var i=r.length;if(t>=i){return-1}var n=r.charCodeAt(t);if(!this.switchU||n<=55295||n>=57344||t+1>=i){return n}return(n<<10)+r.charCodeAt(t+1)-56613888};oe.prototype.nextIndex=function e(t){var r=this.source;var i=r.length;if(t>=i){return i}var n=r.charCodeAt(t);if(!this.switchU||n<=55295||n>=57344||t+1>=i){return t+1}return t+2};oe.prototype.current=function e(){return this.at(this.pos)};oe.prototype.lookahead=function e(){return this.at(this.nextIndex(this.pos))};oe.prototype.advance=function e(){this.pos=this.nextIndex(this.pos)};oe.prototype.eat=function e(t){if(this.current()===t){this.advance();return true}return false};function le(e){if(e<=65535){return String.fromCharCode(e)}e-=65536;return String.fromCharCode((e>>10)+55296,(e&1023)+56320)}se.validateRegExpFlags=function(e){var t=this;var r=e.validFlags;var i=e.flags;for(var n=0;n-1){t.raise(e.start,"Duplicate regular expression flag")}}};se.validateRegExpPattern=function(e){this.regexp_pattern(e);if(!e.switchN&&this.options.ecmaVersion>=9&&e.groupNames.length>0){e.switchN=true;this.regexp_pattern(e)}};se.regexp_pattern=function(e){e.pos=0;e.lastIntValue=0;e.lastStringValue="";e.lastAssertionIsQuantifiable=false;e.numCapturingParens=0;e.maxBackReference=0;e.groupNames.length=0;e.backReferenceNames.length=0;this.regexp_disjunction(e);if(e.pos!==e.source.length){if(e.eat(41)){e.raise("Unmatched ')'")}if(e.eat(93)||e.eat(125)){e.raise("Lone quantifier brackets")}}if(e.maxBackReference>e.numCapturingParens){e.raise("Invalid escape")}for(var t=0,r=e.backReferenceNames;t=9){r=e.eat(60)}if(e.eat(61)||e.eat(33)){this.regexp_disjunction(e);if(!e.eat(41)){e.raise("Unterminated group")}e.lastAssertionIsQuantifiable=!r;return true}}e.pos=t;return false};se.regexp_eatQuantifier=function(e,t){if(t===void 0)t=false;if(this.regexp_eatQuantifierPrefix(e,t)){e.eat(63);return true}return false};se.regexp_eatQuantifierPrefix=function(e,t){return e.eat(42)||e.eat(43)||e.eat(63)||this.regexp_eatBracedQuantifier(e,t)};se.regexp_eatBracedQuantifier=function(e,t){var r=e.pos;if(e.eat(123)){var i=0,n=-1;if(this.regexp_eatDecimalDigits(e)){i=e.lastIntValue;if(e.eat(44)&&this.regexp_eatDecimalDigits(e)){n=e.lastIntValue}if(e.eat(125)){if(n!==-1&&n=9){this.regexp_groupSpecifier(e)}else if(e.current()===63){e.raise("Invalid group")}this.regexp_disjunction(e);if(e.eat(41)){e.numCapturingParens+=1;return true}e.raise("Unterminated group")}return false};se.regexp_eatExtendedAtom=function(e){return e.eat(46)||this.regexp_eatReverseSolidusAtomEscape(e)||this.regexp_eatCharacterClass(e)||this.regexp_eatUncapturingGroup(e)||this.regexp_eatCapturingGroup(e)||this.regexp_eatInvalidBracedQuantifier(e)||this.regexp_eatExtendedPatternCharacter(e)};se.regexp_eatInvalidBracedQuantifier=function(e){if(this.regexp_eatBracedQuantifier(e,true)){e.raise("Nothing to repeat")}return false};se.regexp_eatSyntaxCharacter=function(e){var t=e.current();if(ue(t)){e.lastIntValue=t;e.advance();return true}return false};function ue(e){return e===36||e>=40&&e<=43||e===46||e===63||e>=91&&e<=94||e>=123&&e<=125}se.regexp_eatPatternCharacters=function(e){var t=e.pos;var r=0;while((r=e.current())!==-1&&!ue(r)){e.advance()}return e.pos!==t};se.regexp_eatExtendedPatternCharacter=function(e){var t=e.current();if(t!==-1&&t!==36&&!(t>=40&&t<=43)&&t!==46&&t!==63&&t!==91&&t!==94&&t!==124){e.advance();return true}return false};se.regexp_groupSpecifier=function(e){if(e.eat(63)){if(this.regexp_eatGroupName(e)){if(e.groupNames.indexOf(e.lastStringValue)!==-1){e.raise("Duplicate capture group name")}e.groupNames.push(e.lastStringValue);return}e.raise("Invalid group")}};se.regexp_eatGroupName=function(e){e.lastStringValue="";if(e.eat(60)){if(this.regexp_eatRegExpIdentifierName(e)&&e.eat(62)){return true}e.raise("Invalid capture group name")}return false};se.regexp_eatRegExpIdentifierName=function(e){e.lastStringValue="";if(this.regexp_eatRegExpIdentifierStart(e)){e.lastStringValue+=le(e.lastIntValue);while(this.regexp_eatRegExpIdentifierPart(e)){e.lastStringValue+=le(e.lastIntValue)}return true}return false};se.regexp_eatRegExpIdentifierStart=function(e){var t=e.pos;var r=e.current();e.advance();if(r===92&&this.regexp_eatRegExpUnicodeEscapeSequence(e)){r=e.lastIntValue}if(ce(r)){e.lastIntValue=r;return true}e.pos=t;return false};function ce(e){return h(e,true)||e===36||e===95}se.regexp_eatRegExpIdentifierPart=function(e){var t=e.pos;var r=e.current();e.advance();if(r===92&&this.regexp_eatRegExpUnicodeEscapeSequence(e)){r=e.lastIntValue}if(fe(r)){e.lastIntValue=r;return true}e.pos=t;return false};function fe(e){return p(e,true)||e===36||e===95||e===8204||e===8205}se.regexp_eatAtomEscape=function(e){if(this.regexp_eatBackReference(e)||this.regexp_eatCharacterClassEscape(e)||this.regexp_eatCharacterEscape(e)||e.switchN&&this.regexp_eatKGroupName(e)){return true}if(e.switchU){if(e.current()===99){e.raise("Invalid unicode escape")}e.raise("Invalid escape")}return false};se.regexp_eatBackReference=function(e){var t=e.pos;if(this.regexp_eatDecimalEscape(e)){var r=e.lastIntValue;if(e.switchU){if(r>e.maxBackReference){e.maxBackReference=r}return true}if(r<=e.numCapturingParens){return true}e.pos=t}return false};se.regexp_eatKGroupName=function(e){if(e.eat(107)){if(this.regexp_eatGroupName(e)){e.backReferenceNames.push(e.lastStringValue);return true}e.raise("Invalid named reference")}return false};se.regexp_eatCharacterEscape=function(e){return this.regexp_eatControlEscape(e)||this.regexp_eatCControlLetter(e)||this.regexp_eatZero(e)||this.regexp_eatHexEscapeSequence(e)||this.regexp_eatRegExpUnicodeEscapeSequence(e)||!e.switchU&&this.regexp_eatLegacyOctalEscapeSequence(e)||this.regexp_eatIdentityEscape(e)};se.regexp_eatCControlLetter=function(e){var t=e.pos;if(e.eat(99)){if(this.regexp_eatControlLetter(e)){return true}e.pos=t}return false};se.regexp_eatZero=function(e){if(e.current()===48&&!ge(e.lookahead())){e.lastIntValue=0;e.advance();return true}return false};se.regexp_eatControlEscape=function(e){var t=e.current();if(t===116){e.lastIntValue=9;e.advance();return true}if(t===110){e.lastIntValue=10;e.advance();return true}if(t===118){e.lastIntValue=11;e.advance();return true}if(t===102){e.lastIntValue=12;e.advance();return true}if(t===114){e.lastIntValue=13;e.advance();return true}return false};se.regexp_eatControlLetter=function(e){var t=e.current();if(he(t)){e.lastIntValue=t%32;e.advance();return true}return false};function he(e){return e>=65&&e<=90||e>=97&&e<=122}se.regexp_eatRegExpUnicodeEscapeSequence=function(e){var t=e.pos;if(e.eat(117)){if(this.regexp_eatFixedHexDigits(e,4)){var r=e.lastIntValue;if(e.switchU&&r>=55296&&r<=56319){var i=e.pos;if(e.eat(92)&&e.eat(117)&&this.regexp_eatFixedHexDigits(e,4)){var n=e.lastIntValue;if(n>=56320&&n<=57343){e.lastIntValue=(r-55296)*1024+(n-56320)+65536;return true}}e.pos=i;e.lastIntValue=r}return true}if(e.switchU&&e.eat(123)&&this.regexp_eatHexDigits(e)&&e.eat(125)&&pe(e.lastIntValue)){return true}if(e.switchU){e.raise("Invalid unicode escape")}e.pos=t}return false};function pe(e){return e>=0&&e<=1114111}se.regexp_eatIdentityEscape=function(e){if(e.switchU){if(this.regexp_eatSyntaxCharacter(e)){return true}if(e.eat(47)){e.lastIntValue=47;return true}return false}var t=e.current();if(t!==99&&(!e.switchN||t!==107)){e.lastIntValue=t;e.advance();return true}return false};se.regexp_eatDecimalEscape=function(e){e.lastIntValue=0;var t=e.current();if(t>=49&&t<=57){do{e.lastIntValue=10*e.lastIntValue+(t-48);e.advance()}while((t=e.current())>=48&&t<=57);return true}return false};se.regexp_eatCharacterClassEscape=function(e){var t=e.current();if(de(t)){e.lastIntValue=-1;e.advance();return true}if(e.switchU&&this.options.ecmaVersion>=9&&(t===80||t===112)){e.lastIntValue=-1;e.advance();if(e.eat(123)&&this.regexp_eatUnicodePropertyValueExpression(e)&&e.eat(125)){return true}e.raise("Invalid property name")}return false};function de(e){return e===100||e===68||e===115||e===83||e===119||e===87}se.regexp_eatUnicodePropertyValueExpression=function(e){var t=e.pos;if(this.regexp_eatUnicodePropertyName(e)&&e.eat(61)){var r=e.lastStringValue;if(this.regexp_eatUnicodePropertyValue(e)){var i=e.lastStringValue;this.regexp_validateUnicodePropertyNameAndValue(e,r,i);return true}}e.pos=t;if(this.regexp_eatLoneUnicodePropertyNameOrValue(e)){var n=e.lastStringValue;this.regexp_validateUnicodePropertyNameOrValue(e,n);return true}return false};se.regexp_validateUnicodePropertyNameAndValue=function(e,t,r){if(!ae.hasOwnProperty(t)||ae[t].indexOf(r)===-1){e.raise("Invalid property name")}};se.regexp_validateUnicodePropertyNameOrValue=function(e,t){if(ae.$LONE.indexOf(t)===-1){e.raise("Invalid property name")}};se.regexp_eatUnicodePropertyName=function(e){var t=0;e.lastStringValue="";while(me(t=e.current())){e.lastStringValue+=le(t);e.advance()}return e.lastStringValue!==""};function me(e){return he(e)||e===95}se.regexp_eatUnicodePropertyValue=function(e){var t=0;e.lastStringValue="";while(ve(t=e.current())){e.lastStringValue+=le(t);e.advance()}return e.lastStringValue!==""};function ve(e){return me(e)||ge(e)}se.regexp_eatLoneUnicodePropertyNameOrValue=function(e){return this.regexp_eatUnicodePropertyValue(e)};se.regexp_eatCharacterClass=function(e){if(e.eat(91)){e.eat(94);this.regexp_classRanges(e);if(e.eat(93)){return true}e.raise("Unterminated character class")}return false};se.regexp_classRanges=function(e){var t=this;while(this.regexp_eatClassAtom(e)){var r=e.lastIntValue;if(e.eat(45)&&t.regexp_eatClassAtom(e)){var i=e.lastIntValue;if(e.switchU&&(r===-1||i===-1)){e.raise("Invalid character class")}if(r!==-1&&i!==-1&&r>i){e.raise("Range out of order in character class")}}}};se.regexp_eatClassAtom=function(e){var t=e.pos;if(e.eat(92)){if(this.regexp_eatClassEscape(e)){return true}if(e.switchU){var r=e.current();if(r===99||be(r)){e.raise("Invalid class escape")}e.raise("Invalid escape")}e.pos=t}var i=e.current();if(i!==93){e.lastIntValue=i;e.advance();return true}return false};se.regexp_eatClassEscape=function(e){var t=e.pos;if(e.eat(98)){e.lastIntValue=8;return true}if(e.switchU&&e.eat(45)){e.lastIntValue=45;return true}if(!e.switchU&&e.eat(99)){if(this.regexp_eatClassControlLetter(e)){return true}e.pos=t}return this.regexp_eatCharacterClassEscape(e)||this.regexp_eatCharacterEscape(e)};se.regexp_eatClassControlLetter=function(e){var t=e.current();if(ge(t)||t===95){e.lastIntValue=t%32;e.advance();return true}return false};se.regexp_eatHexEscapeSequence=function(e){var t=e.pos;if(e.eat(120)){if(this.regexp_eatFixedHexDigits(e,2)){return true}if(e.switchU){e.raise("Invalid escape")}e.pos=t}return false};se.regexp_eatDecimalDigits=function(e){var t=e.pos;var r=0;e.lastIntValue=0;while(ge(r=e.current())){e.lastIntValue=10*e.lastIntValue+(r-48);e.advance()}return e.pos!==t};function ge(e){return e>=48&&e<=57}se.regexp_eatHexDigits=function(e){var t=e.pos;var r=0;e.lastIntValue=0;while(ye(r=e.current())){e.lastIntValue=16*e.lastIntValue+xe(r);e.advance()}return e.pos!==t};function ye(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function xe(e){if(e>=65&&e<=70){return 10+(e-65)}if(e>=97&&e<=102){return 10+(e-97)}return e-48}se.regexp_eatLegacyOctalEscapeSequence=function(e){if(this.regexp_eatOctalDigit(e)){var t=e.lastIntValue;if(this.regexp_eatOctalDigit(e)){var r=e.lastIntValue;if(t<=3&&this.regexp_eatOctalDigit(e)){e.lastIntValue=t*64+r*8+e.lastIntValue}else{e.lastIntValue=t*8+r}}else{e.lastIntValue=t}return true}return false};se.regexp_eatOctalDigit=function(e){var t=e.current();if(be(t)){e.lastIntValue=t-48;e.advance();return true}e.lastIntValue=0;return false};function be(e){return e>=48&&e<=55}se.regexp_eatFixedHexDigits=function(e,t){var r=e.pos;e.lastIntValue=0;for(var i=0;i=this.input.length){return this.finishToken(b.eof)}if(e.override){return e.override(this)}else{this.readToken(this.fullCharCodeAtPos())}};ke.readToken=function(e){if(h(e,this.options.ecmaVersion>=6)||e===92){return this.readWord()}return this.getTokenFromCode(e)};ke.fullCharCodeAtPos=function(){var e=this.input.charCodeAt(this.pos);if(e<=55295||e>=57344){return e}var t=this.input.charCodeAt(this.pos+1);return(e<<10)+t-56613888};ke.skipBlockComment=function(){var e=this;var t=this.options.onComment&&this.curPosition();var r=this.pos,i=this.input.indexOf("*/",this.pos+=2);if(i===-1){this.raise(this.pos-2,"Unterminated comment")}this.pos=i+2;if(this.options.locations){k.lastIndex=r;var n;while((n=k.exec(this.input))&&n.index8&&t<14||t>=5760&&S.test(String.fromCharCode(t))){++e.pos}else{break e}}}};ke.finishToken=function(e,t){this.end=this.pos;if(this.options.locations){this.endLoc=this.curPosition()}var r=this.type;this.type=e;this.value=t;this.updateContext(r)};ke.readToken_dot=function(){var e=this.input.charCodeAt(this.pos+1);if(e>=48&&e<=57){return this.readNumber(true)}var t=this.input.charCodeAt(this.pos+2);if(this.options.ecmaVersion>=6&&e===46&&t===46){this.pos+=3;return this.finishToken(b.ellipsis)}else{++this.pos;return this.finishToken(b.dot)}};ke.readToken_slash=function(){var e=this.input.charCodeAt(this.pos+1);if(this.exprAllowed){++this.pos;return this.readRegexp()}if(e===61){return this.finishOp(b.assign,2)}return this.finishOp(b.slash,1)};ke.readToken_mult_modulo_exp=function(e){var t=this.input.charCodeAt(this.pos+1);var r=1;var i=e===42?b.star:b.modulo;if(this.options.ecmaVersion>=7&&e===42&&t===42){++r;i=b.starstar;t=this.input.charCodeAt(this.pos+2)}if(t===61){return this.finishOp(b.assign,r+1)}return this.finishOp(i,r)};ke.readToken_pipe_amp=function(e){var t=this.input.charCodeAt(this.pos+1);if(t===e){return this.finishOp(e===124?b.logicalOR:b.logicalAND,2)}if(t===61){return this.finishOp(b.assign,2)}return this.finishOp(e===124?b.bitwiseOR:b.bitwiseAND,1)};ke.readToken_caret=function(){var e=this.input.charCodeAt(this.pos+1);if(e===61){return this.finishOp(b.assign,2)}return this.finishOp(b.bitwiseXOR,1)};ke.readToken_plus_min=function(e){var t=this.input.charCodeAt(this.pos+1);if(t===e){if(t===45&&!this.inModule&&this.input.charCodeAt(this.pos+2)===62&&(this.lastTokEnd===0||w.test(this.input.slice(this.lastTokEnd,this.pos)))){this.skipLineComment(3);this.skipSpace();return this.nextToken()}return this.finishOp(b.incDec,2)}if(t===61){return this.finishOp(b.assign,2)}return this.finishOp(b.plusMin,1)};ke.readToken_lt_gt=function(e){var t=this.input.charCodeAt(this.pos+1);var r=1;if(t===e){r=e===62&&this.input.charCodeAt(this.pos+2)===62?3:2;if(this.input.charCodeAt(this.pos+r)===61){return this.finishOp(b.assign,r+1)}return this.finishOp(b.bitShift,r)}if(t===33&&e===60&&!this.inModule&&this.input.charCodeAt(this.pos+2)===45&&this.input.charCodeAt(this.pos+3)===45){this.skipLineComment(4);this.skipSpace();return this.nextToken()}if(t===61){r=2}return this.finishOp(b.relational,r)};ke.readToken_eq_excl=function(e){var t=this.input.charCodeAt(this.pos+1);if(t===61){return this.finishOp(b.equality,this.input.charCodeAt(this.pos+2)===61?3:2)}if(e===61&&t===62&&this.options.ecmaVersion>=6){this.pos+=2;return this.finishToken(b.arrow)}return this.finishOp(e===61?b.eq:b.prefix,1)};ke.getTokenFromCode=function(e){switch(e){case 46:return this.readToken_dot();case 40:++this.pos;return this.finishToken(b.parenL);case 41:++this.pos;return this.finishToken(b.parenR);case 59:++this.pos;return this.finishToken(b.semi);case 44:++this.pos;return this.finishToken(b.comma);case 91:++this.pos +;return this.finishToken(b.bracketL);case 93:++this.pos;return this.finishToken(b.bracketR);case 123:++this.pos;return this.finishToken(b.braceL);case 125:++this.pos;return this.finishToken(b.braceR);case 58:++this.pos;return this.finishToken(b.colon);case 63:++this.pos;return this.finishToken(b.question);case 96:if(this.options.ecmaVersion<6){break}++this.pos;return this.finishToken(b.backQuote);case 48:var t=this.input.charCodeAt(this.pos+1);if(t===120||t===88){return this.readRadixNumber(16)}if(this.options.ecmaVersion>=6){if(t===111||t===79){return this.readRadixNumber(8)}if(t===98||t===66){return this.readRadixNumber(2)}}case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return this.readNumber(false);case 34:case 39:return this.readString(e);case 47:return this.readToken_slash();case 37:case 42:return this.readToken_mult_modulo_exp(e);case 124:case 38:return this.readToken_pipe_amp(e);case 94:return this.readToken_caret();case 43:case 45:return this.readToken_plus_min(e);case 60:case 62:return this.readToken_lt_gt(e);case 61:case 33:return this.readToken_eq_excl(e);case 126:return this.finishOp(b.prefix,1)}this.raise(this.pos,"Unexpected character '"+Ce(e)+"'")};ke.finishOp=function(e,t){var r=this.input.slice(this.pos,this.pos+t);this.pos+=t;return this.finishToken(e,r)};ke.readRegexp=function(){var e=this;var t,r,i=this.pos;for(;;){if(e.pos>=e.input.length){e.raise(i,"Unterminated regular expression")}var n=e.input.charAt(e.pos);if(w.test(n)){e.raise(i,"Unterminated regular expression")}if(!t){if(n==="["){r=true}else if(n==="]"&&r){r=false}else if(n==="/"&&!r){break}t=n==="\\"}else{t=false}++e.pos}var a=this.input.slice(i,this.pos);++this.pos;var s=this.pos;var o=this.readWord1();if(this.containsEsc){this.unexpected(s)}var l=this.regexpState||(this.regexpState=new oe(this));l.reset(i,a,o);this.validateRegExpFlags(l);this.validateRegExpPattern(l);var u=null;try{u=new RegExp(a,o)}catch(e){}return this.finishToken(b.regexp,{pattern:a,flags:o,value:u})};ke.readInt=function(e,t){var r=this;var i=this.pos,n=0;for(var a=0,s=t==null?Infinity:t;a=97){l=o-97+10}else if(o>=65){l=o-65+10}else if(o>=48&&o<=57){l=o-48}else{l=Infinity}if(l>=e){break}++r.pos;n=n*e+l}if(this.pos===i||t!=null&&this.pos-i!==t){return null}return n};ke.readRadixNumber=function(e){this.pos+=2;var t=this.readInt(e);if(t==null){this.raise(this.start+2,"Expected number in radix "+e)}if(h(this.fullCharCodeAtPos())){this.raise(this.pos,"Identifier directly after number")}return this.finishToken(b.num,t)};ke.readNumber=function(e){var t=this.pos;if(!e&&this.readInt(10)===null){this.raise(t,"Invalid number")}var r=this.pos-t>=2&&this.input.charCodeAt(t)===48;if(r&&this.strict){this.raise(t,"Invalid number")}if(r&&/[89]/.test(this.input.slice(t,this.pos))){r=false}var i=this.input.charCodeAt(this.pos);if(i===46&&!r){++this.pos;this.readInt(10);i=this.input.charCodeAt(this.pos)}if((i===69||i===101)&&!r){i=this.input.charCodeAt(++this.pos);if(i===43||i===45){++this.pos}if(this.readInt(10)===null){this.raise(t,"Invalid number")}}if(h(this.fullCharCodeAtPos())){this.raise(this.pos,"Identifier directly after number")}var n=this.input.slice(t,this.pos);var a=r?parseInt(n,8):parseFloat(n);return this.finishToken(b.num,a)};ke.readCodePoint=function(){var e=this.input.charCodeAt(this.pos),t;if(e===123){if(this.options.ecmaVersion<6){this.unexpected()}var r=++this.pos;t=this.readHexChar(this.input.indexOf("}",this.pos)-this.pos);++this.pos;if(t>1114111){this.invalidStringToken(r,"Code point out of bounds")}}else{t=this.readHexChar(4)}return t};function Ce(e){if(e<=65535){return String.fromCharCode(e)}e-=65536;return String.fromCharCode((e>>10)+55296,(e&1023)+56320)}ke.readString=function(e){var t=this;var r="",i=++this.pos;for(;;){if(t.pos>=t.input.length){t.raise(t.start,"Unterminated string constant")}var n=t.input.charCodeAt(t.pos);if(n===e){break}if(n===92){r+=t.input.slice(i,t.pos);r+=t.readEscapedChar(false);i=t.pos}else{if(C(n,t.options.ecmaVersion>=10)){t.raise(t.start,"Unterminated string constant")}++t.pos}}r+=this.input.slice(i,this.pos++);return this.finishToken(b.string,r)};var Se={};ke.tryReadTemplateToken=function(){this.inTemplateElement=true;try{this.readTmplToken()}catch(e){if(e===Se){this.readInvalidTemplateToken()}else{throw e}}this.inTemplateElement=false};ke.invalidStringToken=function(e,t){if(this.inTemplateElement&&this.options.ecmaVersion>=9){throw Se}else{this.raise(e,t)}};ke.readTmplToken=function(){var e=this;var t="",r=this.pos;for(;;){if(e.pos>=e.input.length){e.raise(e.start,"Unterminated template")}var i=e.input.charCodeAt(e.pos);if(i===96||i===36&&e.input.charCodeAt(e.pos+1)===123){if(e.pos===e.start&&(e.type===b.template||e.type===b.invalidTemplate)){if(i===36){e.pos+=2;return e.finishToken(b.dollarBraceL)}else{++e.pos;return e.finishToken(b.backQuote)}}t+=e.input.slice(r,e.pos);return e.finishToken(b.template,t)}if(i===92){t+=e.input.slice(r,e.pos);t+=e.readEscapedChar(true);r=e.pos}else if(C(i)){t+=e.input.slice(r,e.pos);++e.pos;switch(i){case 13:if(e.input.charCodeAt(e.pos)===10){++e.pos}case 10:t+="\n";break;default:t+=String.fromCharCode(i);break}if(e.options.locations){++e.curLine;e.lineStart=e.pos}r=e.pos}else{++e.pos}}};ke.readInvalidTemplateToken=function(){var e=this;for(;this.pos=48&&t<=55){var r=this.input.substr(this.pos-1,3).match(/^[0-7]+/)[0];var i=parseInt(r,8);if(i>255){r=r.slice(0,-1);i=parseInt(r,8)}this.pos+=r.length-1;t=this.input.charCodeAt(this.pos);if((r!=="0"||t===56||t===57)&&(this.strict||e)){this.invalidStringToken(this.pos-1-r.length,e?"Octal literal in template string":"Octal literal in strict mode")}return String.fromCharCode(i)}return String.fromCharCode(t)}};ke.readHexChar=function(e){var t=this.pos;var r=this.readInt(16,e);if(r===null){this.invalidStringToken(t,"Bad character escape sequence")}return r};ke.readWord1=function(){var e=this;this.containsEsc=false;var t="",r=true,i=this.pos;var n=this.options.ecmaVersion>=6;while(this.pos=r)){s[u](n,o,e)}if((t==null||n.start===t)&&(r==null||n.end===r)&&i(u,n)){throw new a(n,o)}})(e,o)}catch(e){if(e instanceof a){return e}throw e}}function u(e,t,r,i,s){r=n(r);if(!i){i=v}try{(function e(n,s,o){var l=o||n.type;if(n.start>t||n.end=t&&r(l,n)){throw new a(n,s)}i[l](n,s,e)})(e,s)}catch(e){if(e instanceof a){return e}throw e}}function f(e,t,r,i,s){r=n(r);if(!i){i=v}var o;(function e(n,s,l){if(n.start>t){return}var u=l||n.type;if(n.end<=t&&(!o||o.node.end 0.1 || correlation < -0.1) {\n console.log(event + \":\", correlation);\n }\n}\n// → brushed teeth: -0.3805211953\n// → work: -0.1371988681\n// → reading: 0.1106828054\n", + "exercises": [ + { + "name": "The sum of a range", + "file": "code/solutions/04_1_the_sum_of_a_range.js", + "number": 1, + "type": "js", + "code": "// Your code here.\n\nconsole.log(range(1, 10));\n// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\nconsole.log(range(5, 2, -1));\n// → [5, 4, 3, 2]\nconsole.log(sum(range(1, 10)));\n// → 55", + "solution": "function range(start, end, step = start < end ? 1 : -1) {\n let array = [];\n\n if (step > 0) {\n for (let i = start; i <= end; i += step) array.push(i);\n } else {\n for (let i = start; i >= end; i += step) array.push(i);\n }\n return array;\n}\n\nfunction sum(array) {\n let total = 0;\n for (let value of array) {\n total += value;\n }\n return total;\n}\n\nconsole.log(range(1, 10))\n// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\nconsole.log(range(5, 2, -1));\n// → [5, 4, 3, 2]\nconsole.log(sum(range(1, 10)));\n// → 55" + }, + { + "name": "Reversing an array", + "file": "code/solutions/04_2_reversing_an_array.js", + "number": 2, + "type": "js", + "code": "// Your code here.\n\nconsole.log(reverseArray([\"A\", \"B\", \"C\"]));\n// → [\"C\", \"B\", \"A\"];\nlet arrayValue = [1, 2, 3, 4, 5];\nreverseArrayInPlace(arrayValue);\nconsole.log(arrayValue);\n// → [5, 4, 3, 2, 1]", + "solution": "function reverseArray(array) {\n let output = [];\n for (let i = array.length - 1; i >= 0; i--) {\n output.push(array[i]);\n }\n return output;\n}\n\nfunction reverseArrayInPlace(array) {\n for (let i = 0; i < Math.floor(array.length / 2); i++) {\n let old = array[i];\n array[i] = array[array.length - 1 - i];\n array[array.length - 1 - i] = old;\n }\n return array;\n}\n\nconsole.log(reverseArray([\"A\", \"B\", \"C\"]));\n// → [\"C\", \"B\", \"A\"];\nlet arrayValue = [1, 2, 3, 4, 5];\nreverseArrayInPlace(arrayValue);\nconsole.log(arrayValue);\n// → [5, 4, 3, 2, 1]" + }, + { + "name": "A list", + "file": "code/solutions/04_3_a_list.js", + "number": 3, + "type": "js", + "code": "// Your code here.\n\nconsole.log(arrayToList([10, 20]));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(listToArray(arrayToList([10, 20, 30])));\n// → [10, 20, 30]\nconsole.log(prepend(10, prepend(20, null)));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(nth(arrayToList([10, 20, 30]), 1));\n// → 20", + "solution": "function arrayToList(array) {\n let list = null;\n for (let i = array.length - 1; i >= 0; i--) {\n list = {value: array[i], rest: list};\n }\n return list;\n}\n\nfunction listToArray(list) {\n let array = [];\n for (let node = list; node; node = node.rest) {\n array.push(node.value);\n }\n return array;\n}\n\nfunction prepend(value, list) {\n return {value, rest: list};\n}\n\nfunction nth(list, n) {\n if (!list) return undefined;\n else if (n == 0) return list.value;\n else return nth(list.rest, n - 1);\n}\n\nconsole.log(arrayToList([10, 20]));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(listToArray(arrayToList([10, 20, 30])));\n// → [10, 20, 30]\nconsole.log(prepend(10, prepend(20, null)));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(nth(arrayToList([10, 20, 30]), 1));\n// → 20" + }, + { + "name": "Deep comparison", + "file": "code/solutions/04_4_deep_comparison.js", + "number": 4, + "type": "js", + "code": "// Your code here.\n\nlet obj = {here: {is: \"an\"}, object: 2};\nconsole.log(deepEqual(obj, obj));\n// → true\nconsole.log(deepEqual(obj, {here: 1, object: 2}));\n// → false\nconsole.log(deepEqual(obj, {here: {is: \"an\"}, object: 2}));\n// → true", + "solution": "function deepEqual(a, b) {\n if (a === b) return true;\n \n if (a == null || typeof a != \"object\" ||\n b == null || typeof b != \"object\") return false;\n\n let keysA = Object.keys(a), keysB = Object.keys(b);\n\n if (keysA.length != keysB.length) return false;\n\n for (let key of keysA) {\n if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;\n }\n\n return true;\n}\n\nlet obj = {here: {is: \"an\"}, object: 2};\nconsole.log(deepEqual(obj, obj));\n// → true\nconsole.log(deepEqual(obj, {here: 1, object: 2}));\n// → false\nconsole.log(deepEqual(obj, {here: {is: \"an\"}, object: 2}));\n// → true" + } + ], + "include": [ + "code/journal.js", + "code/chapter/04_data.js" + ] + }, + { + "number": 5, + "id": "05_higher_order", + "title": "Higher-Order Functions", + "start_code": "function textScripts(text) {\n let scripts = countBy(text, char => {\n let script = characterScript(char.codePointAt(0));\n return script ? script.name : \"none\";\n }).filter(({name}) => name != \"none\");\n\n let total = scripts.reduce((n, {count}) => n + count, 0);\n if (total == 0) return \"No scripts found\";\n\n return scripts.map(({name, count}) => {\n return `${Math.round(count * 100 / total)}% ${name}`;\n }).join(\", \");\n}\n\nconsole.log(textScripts('英国的狗说\"woof\", 俄罗斯的狗说\"тяв\"'));\n", + "exercises": [ + { + "name": "Flattening", + "file": "code/solutions/05_1_flattening.js", + "number": 1, + "type": "js", + "code": "let arrays = [[1, 2, 3], [4, 5], [6]];\n// Your code here.\n// → [1, 2, 3, 4, 5, 6]", + "solution": "let arrays = [[1, 2, 3], [4, 5], [6]];\n\nconsole.log(arrays.reduce((flat, current) => flat.concat(current), []));\n// → [1, 2, 3, 4, 5, 6]" + }, + { + "name": "Your own loop", + "file": "code/solutions/05_2_your_own_loop.js", + "number": 2, + "type": "js", + "code": "// Your code here.\n\nloop(3, n => n > 0, n => n - 1, console.log);\n// → 3\n// → 2\n// → 1", + "solution": "function loop(start, test, update, body) {\n for (let value = start; test(value); value = update(value)) {\n body(value);\n }\n}\n\nloop(3, n => n > 0, n => n - 1, console.log);\n// → 3\n// → 2\n// → 1" + }, + { + "name": "Everything", + "file": "code/solutions/05_3_everything.js", + "number": 3, + "type": "js", + "code": "function every(array, test) {\n // Your code here.\n}\n\nconsole.log(every([1, 3, 5], n => n < 10));\n// → true\nconsole.log(every([2, 4, 16], n => n < 10));\n// → false\nconsole.log(every([], n => n < 10));\n// → true", + "solution": "function every(array, predicate) {\n for (let element of array) {\n if (!predicate(element)) return false;\n }\n return true;\n}\n\nfunction every2(array, predicate) {\n return !array.some(element => !predicate(element));\n}\n\nconsole.log(every([1, 3, 5], n => n < 10));\n// → true\nconsole.log(every([2, 4, 16], n => n < 10));\n// → false\nconsole.log(every([], n => n < 10));\n// → true" + }, + { + "name": "Dominant writing direction", + "file": "code/solutions/05_4_dominant_writing_direction.js", + "number": 4, + "type": "js", + "code": "function dominantDirection(text) {\n // Your code here.\n}\n\nconsole.log(dominantDirection(\"Hello!\"));\n// → ltr\nconsole.log(dominantDirection(\"Hey, مساء الخير\"));\n// → rtl", + "solution": "function dominantDirection(text) {\n let counted = countBy(text, char => {\n let script = characterScript(char.codePointAt(0));\n return script ? script.direction : \"none\";\n }).filter(({name}) => name != \"none\");\n\n if (counted.length == 0) return \"ltr\";\n\n return counted.reduce((a, b) => a.count > b.count ? a : b).name;\n}\n\nconsole.log(dominantDirection(\"Hello!\"));\n// → ltr\nconsole.log(dominantDirection(\"Hey, مساء الخير\"));\n// → rtl" + } + ], + "include": [ + "code/scripts.js", + "code/chapter/05_higher_order.js", + "code/intro.js" + ] + }, + { + "number": 6, + "id": "06_object", + "title": "The Secret Life of Objects", + "start_code": "class Temperature {\n constructor(celsius) {\n this.celsius = celsius;\n }\n get fahrenheit() {\n return this.celsius * 1.8 + 32;\n }\n set fahrenheit(value) {\n this.celsius = (value - 32) / 1.8;\n }\n\n static fromFahrenheit(value) {\n return new Temperature((value - 32) / 1.8);\n }\n}\n\nlet temp = new Temperature(22);\nconsole.log(temp.fahrenheit);\ntemp.fahrenheit = 86;\nconsole.log(temp.celsius);\n", + "exercises": [ + { + "name": "A vector type", + "file": "code/solutions/06_1_a_vector_type.js", + "number": 1, + "type": "js", + "code": "// Your code here.\n\nconsole.log(new Vec(1, 2).plus(new Vec(2, 3)));\n// → Vec{x: 3, y: 5}\nconsole.log(new Vec(1, 2).minus(new Vec(2, 3)));\n// → Vec{x: -1, y: -1}\nconsole.log(new Vec(3, 4).length);\n// → 5", + "solution": "class Vec {\n constructor(x, y) {\n this.x = x;\n this.y = y;\n }\n\n plus(other) {\n return new Vec(this.x + other.x, this.y + other.y);\n }\n\n minus(other) {\n return new Vec(this.x - other.x, this.y - other.y);\n }\n\n get length() {\n return Math.sqrt(this.x * this.x + this.y * this.y);\n }\n}\n\nconsole.log(new Vec(1, 2).plus(new Vec(2, 3)));\n// → Vec{x: 3, y: 5}\nconsole.log(new Vec(1, 2).minus(new Vec(2, 3)));\n// → Vec{x: -1, y: -1}\nconsole.log(new Vec(3, 4).length);\n// → 5" + }, + { + "name": "Groups", + "file": "code/solutions/06_2_groups.js", + "number": 2, + "type": "js", + "code": "class Group {\n // Your code here.\n}\n\nlet group = Group.from([10, 20]);\nconsole.log(group.has(10));\n// → true\nconsole.log(group.has(30));\n// → false\ngroup.add(10);\ngroup.delete(10);\nconsole.log(group.has(10));\n// → false", + "solution": "class Group {\n constructor() {\n this.members = [];\n }\n\n add(value) {\n if (!this.has(value)) {\n this.members.push(value);\n }\n }\n\n delete(value) {\n this.members = this.members.filter(v => v !== value);\n }\n\n has(value) {\n return this.members.includes(value);\n }\n\n static from(collection) {\n let group = new Group;\n for (let value of collection) {\n group.add(value);\n }\n return group;\n }\n}\n\nlet group = Group.from([10, 20]);\nconsole.log(group.has(10));\n// → true\nconsole.log(group.has(30));\n// → false\ngroup.add(10);\ngroup.delete(10);\nconsole.log(group.has(10));" + }, + { + "name": "Iterable groups", + "file": "code/solutions/06_3_iterable_groups.js", + "number": 3, + "type": "js", + "code": "// Your code here (and the code from the previous exercise)\n\nfor (let value of Group.from([\"a\", \"b\", \"c\"])) {\n console.log(value);\n}\n// → a\n// → b\n// → c", + "solution": "class Group {\n constructor() {\n this.members = [];\n }\n\n add(value) {\n if (!this.has(value)) {\n this.members.push(value);\n }\n }\n\n delete(value) {\n this.members = this.members.filter(v => v !== value);\n }\n\n has(value) {\n return this.members.includes(value);\n }\n\n static from(collection) {\n let group = new Group;\n for (let value of collection) {\n group.add(value);\n }\n return group;\n }\n\n [Symbol.iterator]() {\n return new GroupIterator(this);\n }\n}\n\nclass GroupIterator {\n constructor(group) {\n this.group = group;\n this.position = 0;\n }\n\n next() {\n if (this.position >= this.group.members.length) {\n return {done: true};\n } else {\n let result = {value: this.group.members[this.position],\n done: false};\n this.position++;\n return result;\n }\n }\n}\n\nfor (let value of Group.from([\"a\", \"b\", \"c\"])) {\n console.log(value);\n}\n// → a\n// → b\n// → c" + }, + { + "name": "Borrowing a method", + "file": "code/solutions/06_4_borrowing_a_method.js", + "number": 4, + "type": "js", + "code": "let map = {one: true, two: true, hasOwnProperty: true};\n\n// Fix this call\nconsole.log(map.hasOwnProperty(\"one\"));\n// → true", + "solution": "let map = {one: true, two: true, hasOwnProperty: true};\n\nconsole.log(Object.prototype.hasOwnProperty.call(map, \"one\"));\n// → true" + } + ], + "include": [ + "code/chapter/06_object.js" + ] + }, + { + "number": 7, + "id": "07_robot", + "title": "Project: A Robot", + "start_code": "runRobotAnimation(VillageState.random(),\n goalOrientedRobot, []);\n", + "exercises": [ + { + "name": "Measuring a robot", + "file": "code/solutions/07_1_measuring_a_robot.js", + "number": 1, + "type": "js", + "code": "function compareRobots(robot1, memory1, robot2, memory2) {\n // Your code here\n}\n\ncompareRobots(routeRobot, [], goalOrientedRobot, []);", + "solution": "function countSteps(state, robot, memory) {\n for (let steps = 0;; steps++) {\n if (state.parcels.length == 0) return steps;\n let action = robot(state, memory);\n state = state.move(action.direction);\n memory = action.memory;\n }\n}\n\nfunction compareRobots(robot1, memory1, robot2, memory2) {\n let total1 = 0, total2 = 0;\n for (let i = 0; i < 100; i++) {\n let state = VillageState.random();\n total1 += countSteps(state, robot1, memory1);\n total2 += countSteps(state, robot2, memory2);\n }\n console.log(`Robot 1 needed ${total1 / 100} steps per task`)\n console.log(`Robot 2 needed ${total2 / 100}`)\n}\n\ncompareRobots(routeRobot, [], goalOrientedRobot, []);" + }, + { + "name": "Robot efficiency", + "file": "code/solutions/07_2_robot_efficiency.js", + "number": 2, + "type": "js", + "code": "// Your code here\n\nrunRobotAnimation(VillageState.random(), yourRobot, memory);", + "solution": "function lazyRobot({place, parcels}, route) {\n if (route.length == 0) {\n // Describe a route for every parcel\n let routes = parcels.map(parcel => {\n if (parcel.place != place) {\n return {route: findRoute(roadGraph, place, parcel.place),\n pickUp: true};\n } else {\n return {route: findRoute(roadGraph, place, parcel.address),\n pickUp: false};\n }\n });\n\n // This determines the precedence a route gets when choosing.\n // Route length counts negatively, routes that pick up a package\n // get a small bonus.\n function score({route, pickUp}) {\n return (pickUp ? 0.5 : 0) - route.length;\n }\n route = routes.reduce((a, b) => score(a) > score(b) ? a : b).route;\n }\n\n return {direction: route[0], memory: route.slice(1)};\n}\n\nrunRobotAnimation(VillageState.random(), lazyRobot, []);" + }, + { + "name": "Persistent group", + "file": "code/solutions/07_3_persistent_group.js", + "number": 3, + "type": "js", + "code": "class PGroup {\n // Your code here\n}\n\nlet a = PGroup.empty.add(\"a\");\nlet ab = a.add(\"b\");\nlet b = ab.delete(\"a\");\n\nconsole.log(b.has(\"b\"));\n// → true\nconsole.log(a.has(\"b\"));\n// → false\nconsole.log(b.has(\"a\"));\n// → false", + "solution": "class PGroup {\n constructor(members) {\n this.members = members;\n }\n\n add(value) {\n if (this.has(value)) return this;\n return new PGroup(this.members.concat([value]));\n }\n\n delete(value) {\n if (!this.has(value)) return this;\n return new PGroup(this.members.filter(m => m !== value));\n }\n\n has(value) {\n return this.members.includes(value);\n }\n}\n\nPGroup.empty = new PGroup([]);\n\nlet a = PGroup.empty.add(\"a\");\nlet ab = a.add(\"b\");\nlet b = ab.delete(\"a\");\n\nconsole.log(b.has(\"b\"));\n// → true\nconsole.log(a.has(\"b\"));\n// → false\nconsole.log(b.has(\"a\"));\n// → false" + } + ], + "include": [ + "code/chapter/07_robot.js", + "code/animatevillage.js" + ] + }, + { + "number": 8, + "id": "08_error", + "title": "Bugs and Errors", + "start_code": "", + "exercises": [ + { + "name": "Retry", + "file": "code/solutions/08_1_retry.js", + "number": 1, + "type": "js", + "code": "class MultiplicatorUnitFailure extends Error {}\n\nfunction primitiveMultiply(a, b) {\n if (Math.random() < 0.2) {\n return a * b;\n } else {\n throw new MultiplicatorUnitFailure(\"Klunk\");\n }\n}\n\nfunction reliableMultiply(a, b) {\n // Your code here.\n}\n\nconsole.log(reliableMultiply(8, 8));\n// → 64", + "solution": "class MultiplicatorUnitFailure extends Error {}\n\nfunction primitiveMultiply(a, b) {\n if (Math.random() < 0.2) {\n return a * b;\n } else {\n throw new MultiplicatorUnitFailure(\"Klunk\");\n }\n}\n\nfunction reliableMultiply(a, b) {\n for (;;) {\n try {\n return primitiveMultiply(a, b);\n } catch (e) {\n if (!(e instanceof MultiplicatorUnitFailure))\n throw e;\n }\n }\n}\n\nconsole.log(reliableMultiply(8, 8));\n// → 64" + }, + { + "name": "The locked box", + "file": "code/solutions/08_2_the_locked_box.js", + "number": 2, + "type": "js", + "code": "const box = {\n locked: true,\n unlock() { this.locked = false; },\n lock() { this.locked = true; },\n _content: [],\n get content() {\n if (this.locked) throw new Error(\"Locked!\");\n return this._content;\n }\n};\n\nfunction withBoxUnlocked(body) {\n // Your code here.\n}\n\nwithBoxUnlocked(function() {\n box.content.push(\"gold piece\");\n});\n\ntry {\n withBoxUnlocked(function() {\n throw new Error(\"Pirates on the horizon! Abort!\");\n });\n} catch (e) {\n console.log(\"Error raised: \" + e);\n}\nconsole.log(box.locked);\n// → true", + "solution": "const box = {\n locked: true,\n unlock() { this.locked = false; },\n lock() { this.locked = true; },\n _content: [],\n get content() {\n if (this.locked) throw new Error(\"Locked!\");\n return this._content;\n }\n};\n\nfunction withBoxUnlocked(body) {\n let locked = box.locked;\n if (!locked) {\n return body();\n }\n\n box.unlock();\n try {\n return body();\n } finally {\n box.lock();\n }\n}\n\nwithBoxUnlocked(function() {\n box.content.push(\"gold piece\");\n});\n\ntry {\n withBoxUnlocked(function() {\n throw new Error(\"Pirates on the horizon! Abort!\");\n });\n} catch (e) {\n console.log(\"Error raised:\", e);\n}\n\nconsole.log(box.locked);\n// → true" + } + ], + "include": [ + "code/chapter/08_error.js" + ] + }, + { + "number": 9, + "id": "09_regexp", + "title": "Regular Expressions", + "start_code": "function parseINI(string) {\n // Start with an object to hold the top-level fields\n let result = {};\n let section = result;\n string.split(/\\r?\\n/).forEach(line => {\n let match;\n if (match = line.match(/^(\\w+)=(.*)$/)) {\n section[match[1]] = match[2];\n } else if (match = line.match(/^\\[(.*)\\]$/)) {\n section = result[match[1]] = {};\n } else if (!/^\\s*(;.*)?$/.test(line)) {\n throw new Error(\"Line '\" + line + \"' is not valid.\");\n }\n });\n return result;\n}\n\nconsole.log(parseINI(`\nname=Vasilis\n[address]\ncity=Tessaloniki`));\n", + "exercises": [ + { + "name": "Regexp golf", + "file": "code/solutions/09_1_regexp_golf.js", + "number": 1, + "type": "js", + "code": "// Fill in the regular expressions\n\nverify(/.../,\n [\"my car\", \"bad cats\"],\n [\"camper\", \"high art\"]);\n\nverify(/.../,\n [\"pop culture\", \"mad props\"],\n [\"plop\", \"prrrop\"]);\n\nverify(/.../,\n [\"ferret\", \"ferry\", \"ferrari\"],\n [\"ferrum\", \"transfer A\"]);\n\nverify(/.../,\n [\"how delicious\", \"spacious room\"],\n [\"ruinous\", \"consciousness\"]);\n\nverify(/.../,\n [\"bad punctuation .\"],\n [\"escape the period\"]);\n\nverify(/.../,\n [\"hottentottententen\"],\n [\"no\", \"hotten totten tenten\"]);\n\nverify(/.../,\n [\"red platypus\", \"wobbling nest\"],\n [\"earth bed\", \"learning ape\", \"BEET\"]);\n\n\nfunction verify(regexp, yes, no) {\n // Ignore unfinished exercises\n if (regexp.source == \"...\") return;\n for (let str of yes) if (!regexp.test(str)) {\n console.log(`Failure to match '${str}'`);\n }\n for (let str of no) if (regexp.test(str)) {\n console.log(`Unexpected match for '${str}'`);\n }\n}", + "solution": "// Fill in the regular expressions\n\nverify(/ca[rt]/,\n [\"my car\", \"bad cats\"],\n [\"camper\", \"high art\"]);\n\nverify(/pr?op/,\n [\"pop culture\", \"mad props\"],\n [\"plop\", \"prrrop\"]);\n\nverify(/ferr(et|y|ari)/,\n [\"ferret\", \"ferry\", \"ferrari\"],\n [\"ferrum\", \"transfer A\"]);\n\nverify(/ious\\b/,\n [\"how delicious\", \"spacious room\"],\n [\"ruinous\", \"consciousness\"]);\n\nverify(/\\s[.,:;]/,\n [\"bad punctuation .\"],\n [\"escape the dot\"]);\n\nverify(/\\w{7}/,\n [\"hottentottententen\"],\n [\"no\", \"hotten totten tenten\"]);\n\nverify(/\\b[^\\We]+\\b/i,\n [\"red platypus\", \"wobbling nest\"],\n [\"earth bed\", \"learning ape\", \"BEET\"]);\n\n\nfunction verify(regexp, yes, no) {\n // Ignore unfinished exercises\n if (regexp.source == \"...\") return;\n for (let str of yes) if (!regexp.test(str)) {\n console.log(`Failure to match '${str}'`);\n }\n for (let str of no) if (regexp.test(str)) {\n console.log(`Unexpected match for '${str}'`);\n }\n}" + }, + { + "name": "Quoting style", + "file": "code/solutions/09_2_quoting_style.js", + "number": 2, + "type": "js", + "code": "let text = \"'I'm the cook,' he said, 'it's my job.'\";\n// Change this call.\nconsole.log(text.replace(/A/g, \"B\"));\n// → \"I'm the cook,\" he said, \"it's my job.\"", + "solution": "let text = \"'I'm the cook,' he said, 'it's my job.'\";\n\nconsole.log(text.replace(/(^|\\W)'|'(\\W|$)/g, '$1\"$2'));\n// → \"I'm the cook,\" he said, \"it's my job.\"" + }, + { + "name": "Numbers again", + "file": "code/solutions/09_3_numbers_again.js", + "number": 3, + "type": "js", + "code": "// Fill in this regular expression.\nlet number = /^...$/;\n\n// Tests:\nfor (let str of [\"1\", \"-1\", \"+15\", \"1.55\", \".5\", \"5.\",\n \"1.3e2\", \"1E-4\", \"1e+12\"]) {\n if (!number.test(str)) {\n console.log(`Failed to match '${str}'`);\n }\n}\nfor (let str of [\"1a\", \"+-1\", \"1.2.3\", \"1+1\", \"1e4.5\",\n \".5.\", \"1f5\", \".\"]) {\n if (number.test(str)) {\n console.log(`Incorrectly accepted '${str}'`);\n }\n}", + "solution": "// Fill in this regular expression.\nlet number = /^[+\\-]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][+\\-]?\\d+)?$/;\n\n// Tests:\nfor (let str of [\"1\", \"-1\", \"+15\", \"1.55\", \".5\", \"5.\",\n \"1.3e2\", \"1E-4\", \"1e+12\"]) {\n if (!number.test(str)) {\n console.log(`Failed to match '${str}'`);\n }\n}\nfor (let str of [\"1a\", \"+-1\", \"1.2.3\", \"1+1\", \"1e4.5\",\n \".5.\", \"1f5\", \".\"]) {\n if (number.test(str)) {\n console.log(`Incorrectly accepted '${str}'`);\n }\n}" + } + ], + "include": null + }, + { + "number": 10, + "id": "10_modules", + "title": "Modules", + "start_code": "", + "exercises": [ + { + "name": "Roads module", + "file": "code/solutions/10_2_roads_module.js", + "number": 2, + "type": "js", + "code": "// Add dependencies and exports\n\nconst roads = [\n \"Alice's House-Bob's House\", \"Alice's House-Cabin\",\n \"Alice's House-Post Office\", \"Bob's House-Town Hall\",\n \"Daria's House-Ernie's House\", \"Daria's House-Town Hall\",\n \"Ernie's House-Grete's House\", \"Grete's House-Farm\",\n \"Grete's House-Shop\", \"Marketplace-Farm\",\n \"Marketplace-Post Office\", \"Marketplace-Shop\",\n \"Marketplace-Town Hall\", \"Shop-Town Hall\"\n];", + "solution": "const {buildGraph} = require(\"./graph\");\n\nconst roads = [\n \"Alice's House-Bob's House\", \"Alice's House-Cabin\",\n \"Alice's House-Post Office\", \"Bob's House-Town Hall\",\n \"Daria's House-Ernie's House\", \"Daria's House-Town Hall\",\n \"Ernie's House-Grete's House\", \"Grete's House-Farm\",\n \"Grete's House-Shop\", \"Marketplace-Farm\",\n \"Marketplace-Post Office\", \"Marketplace-Shop\",\n \"Marketplace-Town Hall\", \"Shop-Town Hall\"\n];\n\nexports.roadGraph = buildGraph(roads.map(r => r.split(\"-\")));" + } + ], + "include": [ + "code/packages_chapter_10.js", + "code/chapter/07_robot.js" + ] + }, + { + "number": 11, + "id": "11_async", + "title": "Asynchronous Programming", + "start_code": "findInStorage(bigOak, \"events on 2017-12-21\")\n .then(console.log);\n", + "exercises": [ + { + "name": "Tracking the scalpel", + "file": "code/solutions/11_1_tracking_the_scalpel.js", + "number": 1, + "type": "js", + "code": "async function locateScalpel(nest) {\n // Your code here.\n}\n\nfunction locateScalpel2(nest) {\n // Your code here.\n}\n\nlocateScalpel(bigOak).then(console.log);\n// → Butcher Shop", + "solution": "async function locateScalpel(nest) {\n let current = nest.name;\n for (;;) {\n let next = await anyStorage(nest, current, \"scalpel\");\n if (next == current) return current;\n current = next;\n }\n}\n\nfunction locateScalpel2(nest) {\n function loop(current) {\n return anyStorage(nest, current, \"scalpel\").then(next => {\n if (next == current) return current;\n else return loop(next);\n });\n }\n return loop(nest.name);\n}\n\nlocateScalpel(bigOak).then(console.log);\n// → Butcher's Shop\nlocateScalpel2(bigOak).then(console.log);\n// → Butcher's Shop" + }, + { + "name": "Building Promise.all", + "file": "code/solutions/11_2_building_promiseall.js", + "number": 2, + "type": "js", + "code": "function Promise_all(promises) {\n return new Promise((resolve, reject) => {\n // Your code here.\n });\n}\n\n// Test code.\nPromise_all([]).then(array => {\n console.log(\"This should be []:\", array);\n});\nfunction soon(val) {\n return new Promise(resolve => {\n setTimeout(() => resolve(val), Math.random() * 500);\n });\n}\nPromise_all([soon(1), soon(2), soon(3)]).then(array => {\n console.log(\"This should be [1, 2, 3]:\", array);\n});\nPromise_all([soon(1), Promise.reject(\"X\"), soon(3)])\n .then(array => {\n console.log(\"We should not get here\");\n })\n .catch(error => {\n if (error != \"X\") {\n console.log(\"Unexpected failure:\", error);\n }\n });", + "solution": "function Promise_all(promises) {\n return new Promise((resolve, reject) => {\n let results = [];\n let pending = promises.length;\n for (let i = 0; i < promises.length; i++) {\n promises[i].then(result => {\n results[i] = result;\n pending--;\n if (pending == 0) resolve(results);\n }).catch(reject);\n }\n if (promises.length == 0) resolve(results);\n });\n}\n\n// Test code.\nPromise_all([]).then(array => {\n console.log(\"This should be []:\", array);\n});\nfunction soon(val) {\n return new Promise(resolve => {\n setTimeout(() => resolve(val), Math.random() * 500);\n });\n}\nPromise_all([soon(1), soon(2), soon(3)]).then(array => {\n console.log(\"This should be [1, 2, 3]:\", array);\n});\nPromise_all([soon(1), Promise.reject(\"X\"), soon(3)]).then(array => {\n console.log(\"We should not get here\");\n}).catch(error => {\n if (error != \"X\") {\n console.log(\"Unexpected failure:\", error);\n }\n});" + } + ], + "include": [ + "code/crow-tech.js", + "code/chapter/11_async.js" + ] + }, + { + "number": 12, + "id": "12_language", + "title": "Project: A Programming Language", + "start_code": "run(`\ndo(define(plusOne, fun(a, +(a, 1))),\n print(plusOne(10)))\n`);\n\nrun(`\ndo(define(pow, fun(base, exp,\n if(==(exp, 0),\n 1,\n *(base, pow(base, -(exp, 1)))))),\n print(pow(2, 10)))\n`);\n", + "exercises": [ + { + "name": "Arrays", + "file": "code/solutions/12_1_arrays.js", + "number": 1, + "type": "js", + "code": "// Modify these definitions...\n\ntopScope.array = \"...\";\n\ntopScope.length = \"...\";\n\ntopScope.element = \"...\";\n\nrun(`\ndo(define(sum, fun(array,\n do(define(i, 0),\n define(sum, 0),\n while(<(i, length(array)),\n do(define(sum, +(sum, element(array, i))),\n define(i, +(i, 1)))),\n sum))),\n print(sum(array(1, 2, 3))))\n`);\n// → 6", + "solution": "topScope.array = (...values) => values;\n\ntopScope.length = array => array.length;\n\ntopScope.element = (array, i) => array[i];\n\nrun(`\ndo(define(sum, fun(array,\n do(define(i, 0),\n define(sum, 0),\n while(<(i, length(array)),\n do(define(sum, +(sum, element(array, i))),\n define(i, +(i, 1)))),\n sum))),\n print(sum(array(1, 2, 3))))\n`);\n// → 6" + }, + { + "name": "Comments", + "file": "code/solutions/12_3_comments.js", + "number": 3, + "type": "js", + "code": "// This is the old skipSpace. Modify it...\nfunction skipSpace(string) {\n let first = string.search(/\\S/);\n if (first == -1) return \"\";\n return string.slice(first);\n}\n\nconsole.log(parse(\"# hello\\nx\"));\n// → {type: \"word\", name: \"x\"}\n\nconsole.log(parse(\"a # one\\n # two\\n()\"));\n// → {type: \"apply\",\n// operator: {type: \"word\", name: \"a\"},\n// args: []}", + "solution": "function skipSpace(string) {\n let skippable = string.match(/^(\\s|#.*)*/);\n return string.slice(skippable[0].length);\n}\n\nconsole.log(parse(\"# hello\\nx\"));\n// → {type: \"word\", name: \"x\"}\n\nconsole.log(parse(\"a # one\\n # two\\n()\"));\n// → {type: \"apply\",\n// operator: {type: \"word\", name: \"a\"},\n// args: []}" + }, + { + "name": "Fixing scope", + "file": "code/solutions/12_4_fixing_scope.js", + "number": 4, + "type": "js", + "code": "specialForms.set = (args, scope) => {\n // Your code here.\n};\n\nrun(`\ndo(define(x, 4),\n define(setx, fun(val, set(x, val))),\n setx(50),\n print(x))\n`);\n// → 50\nrun(`set(quux, true)`);\n// → Some kind of ReferenceError", + "solution": "specialForms.set = (args, env) => {\n if (args.length != 2 || args[0].type != \"word\") {\n throw new SyntaxError(\"Bad use of set\");\n }\n let varName = args[0].name;\n let value = evaluate(args[1], env);\n\n for (let scope = env; scope; scope = Object.getPrototypeOf(scope)) {\n if (Object.prototype.hasOwnProperty.call(scope, varName)) {\n scope[varName] = value;\n return value;\n }\n }\n throw new ReferenceError(`Setting undefined variable ${varName}`);\n};\n\nrun(`\ndo(define(x, 4),\n define(setx, fun(val, set(x, val))),\n setx(50),\n print(x))\n`);\n// → 50\nrun(`set(quux, true)`);\n// → Some kind of ReferenceError" + } + ], + "include": [ + "code/chapter/12_language.js" + ] + }, + { + "number": 13, + "id": "13_browser", + "title": "JavaScript and the Browser", + "start_code": "", + "exercises": [], + "include": null + }, + { + "number": 14, + "id": "14_dom", + "title": "The Document Object Model", + "start_code": "\n\n

\n \n

\n\n", + "exercises": [ + { + "name": "Build a table", + "file": "code/solutions/14_1_build_a_table.html", + "number": 1, + "type": "html", + "code": "\n\n

Mountains

\n\n
\n\n", + "solution": "\n\n\n\n

Mountains

\n\n
\n\n" + }, + { + "name": "Elements by tag name", + "file": "code/solutions/14_2_elements_by_tag_name.html", + "number": 2, + "type": "html", + "code": "\n\n

Heading with a span element.

\n

A paragraph with one, two\n spans.

\n\n", + "solution": "\n\n

Heading with a span element.

\n

A paragraph with one, two\n spans.

\n\n" + }, + { + "name": "The cat's hat", + "file": "code/solutions/14_3_the_cats_hat.html", + "number": 3, + "type": "html", + "code": "\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n\n\n\n\n" + } + ], + "include": null + }, + { + "number": 15, + "id": "15_event", + "title": "Handling Events", + "start_code": "\n\n

Drag the bar to change its width:

\n
\n
\n\n", + "exercises": [ + { + "name": "Balloon", + "file": "code/solutions/15_1_balloon.html", + "number": 1, + "type": "html", + "code": "\n\n

🎈

\n\n", + "solution": "\n\n

🎈

\n\n" + }, + { + "name": "Mouse trail", + "file": "code/solutions/15_2_mouse_trail.html", + "number": 2, + "type": "html", + "code": "\n\n\n\n", + "solution": "\n\n\n\n\n\n" + }, + { + "name": "Tabs", + "file": "code/solutions/15_3_tabs.html", + "number": 3, + "type": "html", + "code": "\n\n\n
Tab one
\n
Tab two
\n
Tab three
\n
\n", + "solution": "\n\n\n
Tab one
\n
Tab two
\n
Tab three
\n
\n" + } + ], + "include": null + }, + { + "number": 16, + "id": "16_game", + "title": "Project: A Platform Game", + "start_code": "\n\n\n\n\n\n\n \n\n", + "exercises": [ + { + "name": "Game over", + "file": "code/solutions/16_1_game_over.html", + "number": 1, + "type": "html", + "code": "\n\n\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n\n\n" + }, + { + "name": "Pausing the game", + "file": "code/solutions/16_2_pausing_the_game.html", + "number": 2, + "type": "html", + "code": "\n\n\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n\n\n" + }, + { + "name": "A monster", + "file": "code/solutions/16_3_a_monster.html", + "number": 3, + "type": "html", + "code": "\n\n\n\n\n\n\n\n \n", + "solution": "\n\n\n\n\n\n\n\n\n \n" + } + ], + "include": [ + "code/chapter/16_game.js", + "code/levels.js" + ] + }, + { + "number": 17, + "id": "17_canvas", + "title": "Drawing on Canvas", + "start_code": "\n\n\n\n\n\n \n\n", + "exercises": [ + { + "name": "Shapes", + "file": "code/solutions/17_1_shapes.html", + "number": 1, + "type": "html", + "code": "\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n" + }, + { + "name": "The pie chart", + "file": "code/solutions/17_2_the_pie_chart.html", + "number": 2, + "type": "html", + "code": "\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n" + }, + { + "name": "A bouncing ball", + "file": "code/solutions/17_3_a_bouncing_ball.html", + "number": 3, + "type": "html", + "code": "\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n" + } + ], + "include": [ + "code/chapter/16_game.js", + "code/levels.js", + "code/chapter/17_canvas.js" + ] + }, + { + "number": 18, + "id": "18_http", + "title": "HTTP and Forms", + "start_code": "\n\n\nNotes:
\n\n\n\n", + "exercises": [ + { + "name": "Content negotiation", + "file": "code/solutions/18_1_content_negotiation.js", + "number": 1, + "type": "js", + "code": "// Your code here.", + "solution": "const url = \"https://eloquentjavascript.net/author\";\nconst types = [\"text/plain\",\n \"text/html\",\n \"application/json\",\n \"application/rainbows+unicorns\"];\n\nasync function showTypes() {\n for (let type of types) {\n let resp = await fetch(url, {headers: {accept: type}});\n console.log(`${type}: ${await resp.text()}\\n`);\n }\n}\n\nshowTypes();" + }, + { + "name": "A JavaScript workbench", + "file": "code/solutions/18_2_a_javascript_workbench.html", + "number": 2, + "type": "html", + "code": "\n\n\n\n\n
\n\n",
+        "solution": "\n\n\n\n\n
\n\n"
+      },
+      {
+        "name": "Conway's Game of Life",
+        "file": "code/solutions/18_3_conways_game_of_life.html",
+        "number": 3,
+        "type": "html",
+        "code": "\n\n\n
\n\n\n", + "solution": "\n\n\n
\n\n\n\n" + } + ], + "include": [ + "code/chapter/18_http.js" + ] + }, + { + "number": 19, + "id": "19_paint", + "title": "Project: A Pixel Art Editor", + "start_code": "\n\n\n
\n\n", + "exercises": [ + { + "name": "Keyboard bindings", + "file": "code/solutions/19_1_keyboard_bindings.html", + "number": 1, + "type": "html", + "code": "\n\n\n
\n", + "solution": "\n\n\n
\n" + }, + { + "name": "Efficient drawing", + "file": "code/solutions/19_2_efficient_drawing.html", + "number": 2, + "type": "html", + "code": "\n\n\n
\n", + "solution": "\n\n\n
\n" + }, + { + "name": "Circles", + "file": "code/solutions/19_3_circles.html", + "number": 3, + "type": "html", + "code": "\n\n\n
\n", + "solution": "\n\n\n
\n" + }, + { + "name": "Proper lines", + "file": "code/solutions/19_4_proper_lines.html", + "number": 4, + "type": "html", + "code": "\n\n\n
\n", + "solution": "\n\n\n
\n" + } + ], + "include": [ + "code/chapter/19_paint.js" + ] + }, + { + "number": 20, + "id": "20_node", + "title": "Node.js", + "start_code": "", + "exercises": [ + { + "name": "Search tool", + "file": "code/solutions/20_1_search_tool.js", + "number": 1, + "type": "js", + "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", + "solution": "const {statSync, readdirSync, readFileSync} = require(\"fs\");\n\nlet searchTerm = new RegExp(process.argv[2]);\n\nfor (let arg of process.argv.slice(3)) {\n search(arg);\n}\n\nfunction search(file) {\n let stats = statSync(file);\n if (stats.isDirectory()) {\n for (let f of readdirSync(file)) {\n search(file + \"/\" + f);\n }\n } else if (searchTerm.test(readFileSync(file, \"utf8\"))) {\n console.log(file);\n }\n}\n" + }, + { + "name": "Directory creation", + "file": "code/solutions/20_2_directory_creation.js", + "number": 2, + "type": "js", + "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", + "solution": "// This code won't work on its own, but is also included in the\n// code/file_server.js file, which defines the whole system.\n\nconst {mkdir} = require(\"fs\").promises;\n\nmethods.MKCOL = async function(request) {\n let path = urlPath(request.url);\n let stats;\n try {\n stats = await stat(path);\n } catch (error) {\n if (error.code != \"ENOENT\") throw error;\n await mkdir(path);\n return {status: 204};\n }\n if (stats.isDirectory()) return {status: 204};\n else return {status: 400, body: \"Not a directory\"};\n};\n" + }, + { + "name": "A public space on the web", + "file": "code/solutions/20_3_a_public_space_on_the_web.zip", + "number": 3, + "type": "js", + "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", + "solution": "// This solutions consists of multiple files. Download it\n// though the link below.\n" + } + ], + "include": null + }, + { + "number": 21, + "id": "21_skillsharing", + "title": "Project: Skill-Sharing Website", + "start_code": "", + "exercises": [ + { + "name": "Disk persistence", + "file": "code/solutions/21_1_disk_persistence.js", + "number": 1, + "type": "js", + "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", + "solution": "// This isn't a stand-alone file, only a redefinition of a few\n// fragments from skillsharing/skillsharing_server.js\n\nconst {readFileSync, writeFile} = require(\"fs\");\n\nconst fileName = \"./talks.json\";\n\nfunction loadTalks() {\n let json;\n try {\n json = JSON.parse(readFileSync(fileName, \"utf8\"));\n } catch (e) {\n json = {};\n }\n return Object.assign(Object.create(null), json);\n}\n\nSkillShareServer.prototype.updated = function() {\n this.version++;\n let response = this.talkResponse();\n this.waiting.forEach(resolve => resolve(response));\n this.waiting = [];\n\n writeFile(fileName, JSON.stringify(this.talks), e => {\n if (e) throw e;\n });\n};\n\n// The line that starts the server must be changed to\nnew SkillShareServer(loadTalks()).start(8000);\n" + }, + { + "name": "Comment field resets", + "file": "code/solutions/21_2_comment_field_resets.js", + "number": 2, + "type": "js", + "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", + "solution": "// This isn't a stand-alone file, only a redefinition of the main\n// component from skillsharing/public/skillsharing_client.js\n\nclass Talk {\n constructor(talk, dispatch) {\n this.comments = elt(\"div\");\n this.dom = elt(\n \"section\", {className: \"talk\"},\n elt(\"h2\", null, talk.title, \" \", elt(\"button\", {\n type: \"button\",\n onclick: () => dispatch({type: \"deleteTalk\",\n talk: talk.title})\n }, \"Delete\")),\n elt(\"div\", null, \"by \",\n elt(\"strong\", null, talk.presenter)),\n elt(\"p\", null, talk.summary),\n this.comments,\n elt(\"form\", {\n onsubmit(event) {\n event.preventDefault();\n let form = event.target;\n dispatch({type: \"newComment\",\n talk: talk.title,\n message: form.elements.comment.value});\n form.reset();\n }\n }, elt(\"input\", {type: \"text\", name: \"comment\"}), \" \",\n elt(\"button\", {type: \"submit\"}, \"Add comment\")));\n this.syncState(talk);\n }\n\n syncState(talk) {\n this.talk = talk;\n this.comments.textContent = \"\";\n for (let comment of talk.comments) {\n this.comments.appendChild(renderComment(comment));\n }\n }\n}\n\nclass SkillShareApp {\n constructor(state, dispatch) {\n this.dispatch = dispatch;\n this.talkDOM = elt(\"div\", {className: \"talks\"});\n this.talkMap = Object.create(null);\n this.dom = elt(\"div\", null,\n renderUserField(state.user, dispatch),\n this.talkDOM,\n renderTalkForm(dispatch));\n this.syncState(state);\n }\n\n syncState(state) {\n if (state.talks == this.talks) return;\n this.talks = state.talks;\n\n for (let talk of state.talks) {\n let cmp = this.talkMap[talk.title];\n if (cmp && cmp.talk.author == talk.author &&\n cmp.talk.summary == talk.summary) {\n cmp.syncState(talk);\n } else {\n if (cmp) cmp.dom.remove();\n cmp = new Talk(talk, this.dispatch);\n this.talkMap[talk.title] = cmp;\n this.talkDOM.appendChild(cmp.dom);\n }\n }\n for (let title of Object.keys(this.talkMap)) {\n if (!state.talks.some(talk => talk.title == title)) {\n this.talkMap[title].dom.remove();\n delete this.talkMap[title];\n }\n }\n }\n}\n" + } + ], + "include": null + }, + { + "title": "JavaScript and Performance", + "number": 22, + "start_code": "\n\n\n", + "include": [ + "code/draw_layout.js", + "code/chapter/22_fast.js" + ], + "exercises": [ + { + "name": "Pathfinding", + "file": "code/solutions/22_1_pathfinding.js", + "number": 1, + "type": "js", + "code": "function findPath(a, b) {\n // Your code here...\n}\n\nlet graph = treeGraph(4, 4);\nlet root = graph[0], leaf = graph[graph.length - 1];\nconsole.log(findPath(root, leaf).length);\n// → 4\n\nleaf.connect(root);\nconsole.log(findPath(root, leaf).length);\n// → 2\n", + "solution": "function findPath(a, b) {\n let work = [[a]];\n for (let path of work) {\n let end = path[path.length - 1];\n if (end == b) return path;\n for (let next of end.edges) {\n if (!work.some(path => path[path.length - 1] == next)) {\n work.push(path.concat([next]));\n }\n }\n }\n}\n\nlet graph = treeGraph(4, 4);\nlet root = graph[0], leaf = graph[graph.length - 1];\nconsole.log(findPath(root, leaf).length);\n// → 4\n\nleaf.connect(root);\nconsole.log(findPath(root, leaf).length);\n// → 2\n" + }, + { + "name": "Timing", + "file": "code/solutions/22_2_timing.js", + "number": 2, + "type": "js", + "code": "", + "solution": "function findPath(a, b) {\n let work = [[a]];\n for (let path of work) {\n let end = path[path.length - 1];\n if (end == b) return path;\n for (let next of end.edges) {\n if (!work.some(path => path[path.length - 1] == next)) {\n work.push(path.concat([next]));\n }\n }\n }\n}\n\nfunction time(findPath) {\n let graph = treeGraph(6, 6);\n let startTime = Date.now();\n let result = findPath(graph[0], graph[graph.length - 1]);\n console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`);\n}\ntime(findPath);\n" + }, + { + "name": "Optimizing", + "file": "code/solutions/22_3_optimizing.js", + "number": 3, + "type": "js", + "code": "", + "solution": "function time(findPath) {\n let graph = treeGraph(6, 6);\n let startTime = Date.now();\n let result = findPath(graph[0], graph[graph.length - 1]);\n console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`);\n}\n\nfunction findPath_set(a, b) {\n let work = [[a]];\n let reached = new Set([a]);\n for (let path of work) {\n let end = path[path.length - 1];\n if (end == b) return path;\n for (let next of end.edges) {\n if (!reached.has(next)) {\n reached.add(next);\n work.push(path.concat([next]));\n }\n }\n }\n}\n\ntime(findPath_set);\n\nfunction pathToArray(path) {\n let result = [];\n for (; path; path = path.via) result.unshift(path.at);\n return result;\n}\n\nfunction findPath_list(a, b) {\n let work = [{at: a, via: null}];\n let reached = new Set([a]);\n for (let path of work) {\n if (path.at == b) return pathToArray(path);\n for (let next of path.at.edges) {\n if (!reached.has(next)) {\n reached.add(next);\n work.push({at: next, via: path});\n }\n }\n }\n}\n\ntime(findPath_list);\n" + } + ] + } +]; diff --git a/docs/js/code.js b/docs/js/code.js new file mode 100644 index 000000000..93eb6a6be --- /dev/null +++ b/docs/js/code.js @@ -0,0 +1,217 @@ +addEventListener("load", () => { + let editor = CodeMirror.fromTextArea(document.querySelector("#editor"), { + mode: "javascript", + extraKeys: { + "Ctrl-Enter": runCode, + "Cmd-Enter": runCode + }, + matchBrackets: true, + lineNumbers: true + }) + function guessType(code) { + return /^[\s\w\n:]* { + clearTimeout(reGuess) + reGuess = setTimeout(() => { + if (context.type == null) { + let mode = guessType(editor.getValue()) == "html" ? "text/html" : "javascript" + if (mode != editor.getOption("mode")) + editor.setOption("mode", mode) + } + }, 500) + }) + + function hasIncludes(code, include) { + if (!include) return code + + let re = /(?:\s|)* - - - - -
- - -

Introduction

- -
- -

Creemos que estamos creando el sistema para nuestros propios propósitos. Creemos que lo estamos haciendo a nuestra imagen... Pero la computadora no es realmente como nosotros. Es una proyección de una parte muy pequeña de nosotros: esa porción dedicada a la lógica, el orden, las reglas y la claridad. quote}}

- -
Ellen Ullman, Close to the Machine: Technophilia and its Discontents
- -
Picture of a screwdriver and a circuit board
- -

This is a book about instructing computers. Computers are about as common as screwdrivers today, but they are quite a bit more complex, and making them do what you want them to do isn’t always easy.

- -

If the task you have for your computer is a common, well-understood one, such as showing you your email or acting like a calculator, you can open the appropriate application and get to work. But for unique or open-ended tasks, there probably is no application.

- -

That is where programming may come in. Programming is the act of constructing a program—a set of precise instructions telling a computer what to do. Because computers are dumb, pedantic beasts, programming is fundamentally tedious and frustrating.

- -

Fortunately, if you can get over that fact, and maybe even enjoy the rigor of thinking in terms that dumb machines can deal with, programming can be rewarding. It allows you to do things in seconds that would take forever by hand. It is a way to make your computer tool do things that it couldn’t do before. And it provides a wonderful exercise in abstract thinking.

- -

Most programming is done with programming languages. A programming language is an artificially constructed language used to instruct computers. It is interesting that the most effective way we’ve found to communicate with a computer borrows so heavily from the way we communicate with each other. Like human languages, computer languages allow words and phrases to be combined in new ways, making it possible to express ever new concepts.

- -

At one point language-based interfaces, such as the BASIC and DOS prompts of the 1980s and 1990s, were the main method of interacting with computers. They have largely been replaced with visual interfaces, which are easier to learn but offer less freedom. Computer languages are still there, if you know where to look. One such language, JavaScript, is built into every modern web browser and is thus available on almost every device.

- -

This book will try to make you familiar enough with this language to do useful and amusing things with it.

- -

On programming

- -

Besides explaining JavaScript, I will introduce the basic principles of programming. Programming, it turns out, is hard. The fundamental rules are simple and clear, but programs built on top of these rules tend to become complex enough to introduce their own rules and complexity. You’re building your own maze, in a way, and you might just get lost in it.

- -

There will be times when reading this book feels terribly frustrating. If you are new to programming, there will be a lot of new material to digest. Much of this material will then be combined in ways that require you to make additional connections.

- -

It is up to you to make the necessary effort. When you are struggling to follow the book, do not jump to any conclusions about your own capabilities. You are fine—you just need to keep at it. Take a break, reread some material, and make sure you read and understand the example programs and exercises. Learning is hard work, but everything you learn is yours and will make subsequent learning easier.

- -
- -

When action grows unprofitable, gather information; when information grows unprofitable, sleep.

- -
Ursula K. Le Guin, The Left Hand of Darkness
- -
- -

A program is many things. It is a piece of text typed by a programmer, it is the directing force that makes the computer do what it does, it is data in the computer’s memory, yet it controls the actions performed on this same memory. Analogies that try to compare programs to objects we are familiar with tend to fall short. A superficially fitting one is that of a machine—lots of separate parts tend to be involved, and to make the whole thing tick, we have to consider the ways in which these parts interconnect and contribute to the operation of the whole.

- -

A computer is a physical machine that acts as a host for these immaterial machines. Computers themselves can do only stupidly straightforward things. The reason they are so useful is that they do these things at an incredibly high speed. A program can ingeniously combine an enormous number of these simple actions to do very complicated things.

- -

A program is a building of thought. It is costless to build, it is weightless, and it grows easily under our typing hands.

- -

But without care, a program’s size and complexity will grow out of control, confusing even the person who created it. Keeping programs under control is the main problem of programming. When a program works, it is beautiful. The art of programming is the skill of controlling complexity. The great program is subdued—made simple in its complexity.

- -

Some programmers believe that this complexity is best managed by using only a small set of well-understood techniques in their programs. They have composed strict rules (“best practices”) prescribing the form programs should have and carefully stay within their safe little zone.

- -

This is not only boring, it is ineffective. New problems often require new solutions. The field of programming is young and still developing rapidly, and it is varied enough to have room for wildly different approaches. There are many terrible mistakes to make in program design, and you should go ahead and make them so that you understand them. A sense of what a good program looks like is developed in practice, not learned from a list of rules.

- -

Why language matters

- -

In the beginning, at the birth of computing, there were no programming languages. Programs looked something like this:

- -
00110001 00000000 00000000
-00110001 00000001 00000001
-00110011 00000001 00000010
-01010001 00001011 00000010
-00100010 00000010 00001000
-01000011 00000001 00000000
-01000001 00000001 00000001
-00010000 00000010 00000000
-01100010 00000000 00000000
- -

That is a program to add the numbers from 1 to 10 together and print out the result: 1 + 2 + ... + 10 = 55. It could run on a simple, hypothetical machine. To program early computers, it was necessary to set large arrays of switches in the right position or punch holes in strips of cardboard and feed them to the computer. You can probably imagine how tedious and error-prone this procedure was. Even writing simple programs required much cleverness and discipline. Complex ones were nearly inconceivable.

- -

Of course, manually entering these arcane patterns of bits (the ones and zeros) did give the programmer a profound sense of being a mighty wizard. And that has to be worth something in terms of job satisfaction.

- -

Each line of the previous program contains a single instruction. It could be written in English like this:

- -
    - -
  1. - -

    Store the number 0 in memory location 0.

  2. - -
  3. - -

    Store the number 1 in memory location 1.

  4. - -
  5. - -

    Store the value of memory location 1 in memory location 2.

  6. - -
  7. - -

    Subtract the number 11 from the value in memory location 2.

  8. - -
  9. - -

    If the value in memory location 2 is the number 0, continue with instruction 9.

  10. - -
  11. - -

    Add the value of memory location 1 to memory location 0.

  12. - -
  13. - -

    Add the number 1 to the value of memory location 1.

  14. - -
  15. - -

    Continue with instruction 3.

  16. - -
  17. - -

    Output the value of memory location 0.

  18. - -
- -

Although that is already more readable than the soup of bits, it is still rather obscure. Using names instead of numbers for the instructions and memory locations helps.

- -
 Set “total” to 0.
- Set “count” to 1.
-[loop]
- Set “compare” to “count”.
- Subtract 11 from “compare”.
- If “compare” is zero, continue at [end].
- Add “count” to “total”.
- Add 1 to “count”.
- Continue at [loop].
-[end]
- Output “total”.
- -

Can you see how the program works at this point? The first two lines give two memory locations their starting values: total will be used to build up the result of the computation, and count will keep track of the number that we are currently looking at. The lines using compare are probably the weirdest ones. The program wants to see whether count is equal to 11 to decide whether it can stop running. Because our hypothetical machine is rather primitive, it can only test whether a number is zero and make a decision based on that. So it uses the memory location labeled compare to compute the value of count - 11 and makes a decision based on that value. The next two lines add the value of count to the result and increment count by 1 every time the program has decided that count is not 11 yet.

- -

Here is the same program in JavaScript:

- -
let total = 0, count = 1;
-while (count <= 10) {
-  total += count;
-  count += 1;
-}
-console.log(total);
-// → 55
- -

This version gives us a few more improvements. Most important, there is no need to specify the way we want the program to jump back and forth anymore. The while construct takes care of that. It continues executing the block (wrapped in braces) below it as long as the condition it was given holds. That condition is count <= 10, which means “count is less than or equal to 10”. We no longer have to create a temporary value and compare that to zero, which was just an uninteresting detail. Part of the power of programming languages is that they can take care of uninteresting details for us.

- -

At the end of the program, after the while construct has finished, the console.log operation is used to write out the result.

- -

Finally, here is what the program could look like if we happened to have the convenient operations range and sum available, which respectively create a collection of numbers within a range and compute the sum of a collection of numbers:

- -
console.log(sum(range(1, 10)));
-// → 55
- -

The moral of this story is that the same program can be expressed in both long and short, unreadable and readable ways. The first version of the program was extremely obscure, whereas this last one is almost English: log the sum of the range of numbers from 1 to 10. (We will see in later chapters how to define operations like sum and range.)

- -

A good programming language helps the programmer by allowing them to talk about the actions that the computer has to perform on a higher level. It helps omit details, provides convenient building blocks (such as while and console.log), allows you to define your own building blocks (such as sum and range), and makes those blocks easy to compose.

- -

What is JavaScript?

- -

JavaScript was introduced in 1995 as a way to add programs to web pages in the Netscape Navigator browser. The language has since been adopted by all other major graphical web browsers. It has made modern web applications possible—applications with which you can interact directly without doing a page reload for every action. JavaScript is also used in more traditional websites to provide various forms of interactivity and cleverness.

- -

It is important to note that JavaScript has almost nothing to do with the programming language named Java. The similar name was inspired by marketing considerations rather than good judgment. When JavaScript was being introduced, the Java language was being heavily marketed and was gaining popularity. Someone thought it was a good idea to try to ride along on this success. Now we are stuck with the name.

- -

After its adoption outside of Netscape, a standard document was written to describe the way the JavaScript language should work so that the various pieces of software that claimed to support JavaScript were actually talking about the same language. This is called the ECMAScript standard, after the Ecma International organization that did the standardization. In practice, the terms ECMAScript and JavaScript can be used interchangeably—they are two names for the same language.

- -

There are those who will say terrible things about JavaScript. Many of these things are true. When I was required to write something in JavaScript for the first time, I quickly came to despise it. It would accept almost anything I typed but interpret it in a way that was completely different from what I meant. This had a lot to do with the fact that I did not have a clue what I was doing, of course, but there is a real issue here: JavaScript is ridiculously liberal in what it allows. The idea behind this design was that it would make programming in JavaScript easier for beginners. In actuality, it mostly makes finding problems in your programs harder because the system will not point them out to you.

- -

This flexibility also has its advantages, though. It leaves space for a lot of techniques that are impossible in more rigid languages, and as you will see (for example in Chapter 10), it can be used to overcome some of JavaScript’s shortcomings. After learning the language properly and working with it for a while, I have learned to actually like JavaScript.

- -

There have been several versions of JavaScript. ECMAScript version 3 was the widely supported version in the time of JavaScript’s ascent to dominance, roughly between 2000 and 2010. During this time, work was underway on an ambitious version 4, which planned a number of radical improvements and extensions to the language. Changing a living, widely used language in such a radical way turned out to be politically difficult, and work on the version 4 was abandoned in 2008, leading to a much less ambitious version 5, which made only some uncontroversial improvements, coming out in 2009. Then in 2015 version 6 came out, a major update that included some of the ideas planned for version 4. Since then we’ve had new, small updates every year.

- -

The fact that the language is evolving means that browsers have to constantly keep up, and if you’re using an older browser, it may not support every feature. The language designers are careful to not make any changes that could break existing programs, so new browsers can still run old programs. In this book, I’m using the 2017 version of JavaScript.

- -

Web browsers are not the only platforms on which JavaScript is used. Some databases, such as MongoDB and CouchDB, use JavaScript as their scripting and query language. Several platforms for desktop and server programming, most notably the Node.js project (the subject of Chapter 20), provide an environment for programming JavaScript outside of the browser.

- -

Code, and what to do with it

- -

Code is the text that makes up programs. Most chapters in this book contain quite a lot of code. I believe reading code and writing code are indispensable parts of learning to program. Try to not just glance over the examples—read them attentively and understand them. This may be slow and confusing at first, but I promise that you’ll quickly get the hang of it. The same goes for the exercises. Don’t assume you understand them until you’ve actually written a working solution.

- -

I recommend you try your solutions to exercises in an actual JavaScript interpreter. That way, you’ll get immediate feedback on whether what you are doing is working, and, I hope, you’ll be tempted to experiment and go beyond the exercises.

- -

When reading this book in your browser, you can edit (and run) all example programs by clicking them.

- -

If you want to run the programs defined in this book outside of the book’s website, some care will be required. Many examples stand on their own and should work in any JavaScript environment. But code in later chapters is often written for a specific environment (the browser or Node.js) and can run only there. In addition, many chapters define bigger programs, and the pieces of code that appear in them depend on each other or on external files. The sandbox on the website provides links to Zip files containing all the scripts and data files necessary to run the code for a given chapter.

- -

Overview of this book

- -

This book contains roughly three parts. The first 12 chapters discuss the JavaScript language. The next seven chapters are about web browsers and the way JavaScript is used to program them. Finally, two chapters are devoted to Node.js, another environment to program JavaScript in.

- -

Throughout the book, there are five project chapters, which describe larger example programs to give you a taste of actual programming. In order of appearance, we will work through building a delivery robot, a programming language, a platform game, a pixel paint program, and a dynamic website.

- -

The language part of the book starts with four chapters that introduce the basic structure of the JavaScript language. They introduce control structures (such as the while word you saw in this introduction), functions (writing your own building blocks), and data structures. After these, you will be able to write basic programs. Next, Chapters 5 and 6 introduce techniques to use functions and objects to write more abstract code and keep complexity under control.

- -

After a first project chapter, the language part of the book continues with chapters on error handling and bug fixing, regular expressions (an important tool for working with text), modularity (another defense against complexity), and asynchronous programming (dealing with events that take time). The second project chapter concludes the first part of the book.

- -

The second part, Chapters 13 to 19, describes the tools that browser JavaScript has access to. You’ll learn to display things on the screen (Chapters 14 and 17), respond to user input (Chapter 15), and communicate over the network (Chapter 18). There are again two project chapters in this part.

- -

After that, Chapter 20 describes Node.js, and Chapter 21 builds a small website using that tool.

- -

Typographic conventions

- -

In this book, text written in a monospaced font will represent elements of programs—sometimes they are self-sufficient fragments, and sometimes they just refer to part of a nearby program. Programs (of which you have already seen a few) are written as follows:

- -
function factorial(n) {
-  if (n == 0) {
-    return 1;
-  } else {
-    return factorial(n - 1) * n;
-  }
-}
- -

Sometimes, to show the output that a program produces, the expected output is written after it, with two slashes and an arrow in front.

- -
console.log(factorial(8));
-// → 40320
- -

Good luck!

-
diff --git a/docs/01_values.html b/docs/01_values.html deleted file mode 100644 index ab9ab5d2a..000000000 --- a/docs/01_values.html +++ /dev/null @@ -1,293 +0,0 @@ - - - - - Values, Types, and Operators :: Eloquent JavaScript - - - - - - -
- - -

Chapter 1Values, Types, and Operators

- -
- -

Below the surface of the machine, the program moves. Without effort, it expands and contracts. In great harmony, electrons scatter and regroup. The forms on the monitor are but ripples on the water. The essence stays invisibly below.

- -
Master Yuan-Ma, The Book of Programming
- -
Picture of a sea of bits
- -

Inside the computer’s world, there is only data. You can read data, modify data, create new data—but that which isn’t data cannot be mentioned. All this data is stored as long sequences of bits and is thus fundamentally alike.

- -

Bits are any kind of two-valued things, usually described as zeros and ones. Inside the computer, they take forms such as a high or low electrical charge, a strong or weak signal, or a shiny or dull spot on the surface of a CD. Any piece of discrete information can be reduced to a sequence of zeros and ones and thus represented in bits.

- -

For example, we can express the number 13 in bits. It works the same way as a decimal number, but instead of 10 different digits, you have only 2, and the weight of each increases by a factor of 2 from right to left. Here are the bits that make up the number 13, with the weights of the digits shown below them:

- -
   0   0   0   0   1   1   0   1
- 128  64  32  16   8   4   2   1
- -

So that’s the binary number 00001101. Its non-zero digits stand for 8, 4, and 1, and add up to 13.

- -

Values

- -

Imagine a sea of bits—an ocean of them. A typical modern computer has more than 30 billion bits in its volatile data storage (working memory). Nonvolatile storage (the hard disk or equivalent) tends to have yet a few orders of magnitude more.

- -

To be able to work with such quantities of bits without getting lost, we must separate them into chunks that represent pieces of information. In a JavaScript environment, those chunks are called values. Though all values are made of bits, they play different roles. Every value has a type that determines its role. Some values are numbers, some values are pieces of text, some values are functions, and so on.

- -

To create a value, you must merely invoke its name. This is convenient. You don’t have to gather building material for your values or pay for them. You just call for one, and whoosh, you have it. They are not really created from thin air, of course. Every value has to be stored somewhere, and if you want to use a gigantic amount of them at the same time, you might run out of memory. Fortunately, this is a problem only if you need them all simultaneously. As soon as you no longer use a value, it will dissipate, leaving behind its bits to be recycled as building material for the next generation of values.

- -

This chapter introduces the atomic elements of JavaScript programs, that is, the simple value types and the operators that can act on such values.

- -

Numbers

- -

Values of the number type are, unsurprisingly, numeric values. In a JavaScript program, they are written as follows:

- -
13
- -

Use that in a program, and it will cause the bit pattern for the number 13 to come into existence inside the computer’s memory.

- -

JavaScript uses a fixed number of bits, 64 of them, to store a single number value. There are only so many patterns you can make with 64 bits, which means that the number of different numbers that can be represented is limited. With N decimal digits, you can represent 10N numbers. Similarly, given 64 binary digits, you can represent 264 different numbers, which is about 18 quintillion (an 18 with 18 zeros after it). That’s a lot.

- -

Computer memory used to be much smaller, and people tended to use groups of 8 or 16 bits to represent their numbers. It was easy to accidentally overflow such small numbers—to end up with a number that did not fit into the given number of bits. Today, even computers that fit in your pocket have plenty of memory, so you are free to use 64-bit chunks, and you need to worry about overflow only when dealing with truly astronomical numbers.

- -

Not all whole numbers less than 18 quintillion fit in a JavaScript number, though. Those bits also store negative numbers, so one bit indicates the sign of the number. A bigger issue is that nonwhole numbers must also be represented. To do this, some of the bits are used to store the position of the decimal point. The actual maximum whole number that can be stored is more in the range of 9 quadrillion (15 zeros)—which is still pleasantly huge.

- -

Fractional numbers are written by using a dot.

- -
9.81
- -

For very big or very small numbers, you may also use scientific notation by adding an e (for exponent), followed by the exponent of the number.

- -
2.998e8
- -

That is 2.998 × 108 = 299,800,000.

- -

Calculations with whole numbers (also called integers) smaller than the aforementioned 9 quadrillion are guaranteed to always be precise. Unfortunately, calculations with fractional numbers are generally not. Just as π (pi) cannot be precisely expressed by a finite number of decimal digits, many numbers lose some precision when only 64 bits are available to store them. This is a shame, but it causes practical problems only in specific situations. The important thing is to be aware of it and treat fractional digital numbers as approximations, not as precise values.

- -

Arithmetic

- -

The main thing to do with numbers is arithmetic. Arithmetic operations such as addition or multiplication take two number values and produce a new number from them. Here is what they look like in JavaScript:

- -
100 + 4 * 11
- -

The + and * symbols are called operators. The first stands for addition, and the second stands for multiplication. Putting an operator between two values will apply it to those values and produce a new value.

- -

But does the example mean “add 4 and 100, and multiply the result by 11,” or is the multiplication done before the adding? As you might have guessed, the multiplication happens first. But as in mathematics, you can change this by wrapping the addition in parentheses.

- -
(100 + 4) * 11
- -

For subtraction, there is the - operator, and division can be done with the / operator.

- -

When operators appear together without parentheses, the order in which they are applied is determined by the precedence of the operators. The example shows that multiplication comes before addition. The / operator has the same precedence as *. Likewise for + and -. When multiple operators with the same precedence appear next to each other, as in 1 - 2 + 1, they are applied left to right: (1 - 2) + 1.

- -

These rules of precedence are not something you should worry about. When in doubt, just add parentheses.

- -

There is one more arithmetic operator, which you might not immediately recognize. The % symbol is used to represent the remainder operation. X % Y is the remainder of dividing X by Y. For example, 314 % 100 produces 14, and 144 % 12 gives 0. The remainder operator’s precedence is the same as that of multiplication and division. You’ll also often see this operator referred to as modulo.

- -

Special numbers

- -

There are three special values in JavaScript that are considered numbers but don’t behave like normal numbers.

- -

The first two are Infinity and -Infinity, which represent the positive and negative infinities. Infinity - 1 is still Infinity, and so on. Don’t put too much trust in infinity-based computation, though. It isn’t mathematically sound, and it will quickly lead to the next special number: NaN.

- -

NaN stands for “not a number”, even though it is a value of the number type. You’ll get this result when you, for example, try to calculate 0 / 0 (zero divided by zero), Infinity - Infinity, or any number of other numeric operations that don’t yield a meaningful result.

- -

Strings

- -

The next basic data type is the string. Strings are used to represent text. They are written by enclosing their content in quotes.

- -
`Down on the sea`
-"Lie on the ocean"
-'Float on the ocean'
- -

You can use single quotes, double quotes, or backticks to mark strings, as long as the quotes at the start and the end of the string match.

- -

Almost anything can be put between quotes, and JavaScript will make a string value out of it. But a few characters are more difficult. You can imagine how putting quotes between quotes might be hard. Newlines (the characters you get when you press enter) can be included without escaping only when the string is quoted with backticks (`).

- -

To make it possible to include such characters in a string, the following notation is used: whenever a backslash (\) is found inside quoted text, it indicates that the character after it has a special meaning. This is called escaping the character. A quote that is preceded by a backslash will not end the string but be part of it. When an n character occurs after a backslash, it is interpreted as a newline. Similarly, a t after a backslash means a tab character. Take the following string:

- -
"This is the first line\nAnd this is the second"
- -

The actual text contained is this:

- -
This is the first line
-And this is the second
- -

There are, of course, situations where you want a backslash in a string to be just a backslash, not a special code. If two backslashes follow each other, they will collapse together, and only one will be left in the resulting string value. This is how the string “A newline character is written like "\n".” can be expressed:

- -
"A newline character is written like \"\\n\"."
- -

Strings, too, have to be modeled as a series of bits to be able to exist inside the computer. The way JavaScript does this is based on the Unicode standard. This standard assigns a number to virtually every character you would ever need, including characters from Greek, Arabic, Japanese, Armenian, and so on. If we have a number for every character, a string can be described by a sequence of numbers.

- -

And that’s what JavaScript does. But there’s a complication: JavaScript’s representation uses 16 bits per string element, which can describe up to 216 different characters. But Unicode defines more characters than that—about twice as many, at this point. So some characters, such as many emoji, take up two “character positions” in JavaScript strings. We’ll come back to this in Chapter 5.

- -

Strings cannot be divided, multiplied, or subtracted, but the + operator can be used on them. It does not add, but it concatenates—it glues two strings together. The following line will produce the string "concatenate":

- -
"con" + "cat" + "e" + "nate"
- -

String values have a number of associated functions (methods) that can be used to perform other operations on them. I’ll say more about these in Chapter 4.

- -

Strings written with single or double quotes behave very much the same—the only difference is in which type of quote you need to escape inside of them. Backtick-quoted strings, usually called template -literals, can do a few more tricks. Apart from being able to span lines, they can also embed other values.

- -
`half of 100 is ${100 / 2}`
- -

When you write something inside ${} in a template literal, its result will be computed, converted to a string, and included at that position. The example produces “half of 100 is 50”.

- -

Unary operators

- -

Not all operators are symbols. Some are written as words. One example is the typeof operator, which produces a string value naming the type of the value you give it.

- -
console.log(typeof 4.5)
-// → number
-console.log(typeof "x")
-// → string
- -

We will use console.log in example code to indicate that we want to see the result of evaluating something. More about that in the next chapter.

- -

The other operators shown all operated on two values, but typeof takes only one. Operators that use two values are called binary operators, while those that take one are called unary operators. The minus operator can be used both as a binary operator and as a unary operator.

- -
console.log(- (10 - 2))
-// → -8
- -

Boolean values

- -

It is often useful to have a value that distinguishes between only two possibilities, like “yes” and “no” or “on” and “off”. For this purpose, JavaScript has a Boolean type, which has just two values, true and false, which are written as those words.

- -

Comparison

- -

Here is one way to produce Boolean values:

- -
console.log(3 > 2)
-// → true
-console.log(3 < 2)
-// → false
- -

The > and < signs are the traditional symbols for “is greater than” and “is less than”, respectively. They are binary operators. Applying them results in a Boolean value that indicates whether they hold true in this case.

- -

Strings can be compared in the same way.

- -
console.log("Aardvark" < "Zoroaster")
-// → true
- -

The way strings are ordered is roughly alphabetic but not really what you’d expect to see in a dictionary: uppercase letters are always “less” than lowercase ones, so "Z" < "a", and nonalphabetic characters (!, -, and so on) are also included in the ordering. When comparing strings, JavaScript goes over the characters from left to right, comparing the Unicode codes one by one.

- -

Other similar operators are >= (greater than or equal to), <= (less than or equal to), == (equal to), and != (not equal to).

- -
console.log("Itchy" != "Scratchy")
-// → true
-console.log("Apple" == "Orange")
-// → false
- -

There is only one value in JavaScript that is not equal to itself, and that is NaN (“not a number”).

- -
console.log(NaN == NaN)
-// → false
- -

NaN is supposed to denote the result of a nonsensical computation, and as such, it isn’t equal to the result of any other nonsensical computations.

- -

Logical operators

- -

There are also some operations that can be applied to Boolean values themselves. JavaScript supports three logical operators: and, or, and not. These can be used to “reason” about Booleans.

- -

The && operator represents logical and. It is a binary operator, and its result is true only if both the values given to it are true.

- -
console.log(true && false)
-// → false
-console.log(true && true)
-// → true
- -

The || operator denotes logical or. It produces true if either of the values given to it is true.

- -
console.log(false || true)
-// → true
-console.log(false || false)
-// → false
- -

Not is written as an exclamation mark (!). It is a unary operator that flips the value given to it—!true produces false, and !false gives true.

- -

When mixing these Boolean operators with arithmetic and other operators, it is not always obvious when parentheses are needed. In practice, you can usually get by with knowing that of the operators we have seen so far, || has the lowest precedence, then comes &&, then the comparison operators (>, ==, and so on), and then the rest. This order has been chosen such that, in typical expressions like the following one, as few parentheses as possible are necessary:

- -
1 + 1 == 2 && 10 * 10 > 50
- -

The last logical operator I will discuss is not unary, not binary, but ternary, operating on three values. It is written with a question mark and a colon, like this:

- -
console.log(true ? 1 : 2);
-// → 1
-console.log(false ? 1 : 2);
-// → 2
- -

This one is called the conditional operator (or sometimes just the ternary operator since it is the only such operator in the language). The value on the left of the question mark “picks” which of the other two values will come out. When it is true, it chooses the middle value, and when it is false, it chooses the value on the right.

- -

Empty values

- -

There are two special values, written null and undefined, that are used to denote the absence of a meaningful value. They are themselves values, but they carry no information.

- -

Many operations in the language that don’t produce a meaningful value (you’ll see some later) yield undefined simply because they have to yield some value.

- -

The difference in meaning between undefined and null is an accident of JavaScript’s design, and it doesn’t matter most of the time. In cases where you actually have to concern yourself with these values, I recommend treating them as mostly interchangeable.

- -

Automatic type conversion

- -

In the Introduction, I mentioned that JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions:

- -
console.log(8 * null)
-// → 0
-console.log("5" - 1)
-// → 4
-console.log("5" + 1)
-// → 51
-console.log("five" * 2)
-// → NaN
-console.log(false == 0)
-// → true
- -

When an operator is applied to the “wrong” type of value, JavaScript will quietly convert that value to the type it needs, using a set of rules that often aren’t what you want or expect. This is called type coercion. The null in the first expression becomes 0, and the "5" in the second expression becomes 5 (from string to number). Yet in the third expression, + tries string concatenation before numeric addition, so the 1 is converted to "1" (from number to string).

- -

When something that doesn’t map to a number in an obvious way (such as "five" or undefined) is converted to a number, you get the value NaN. Further arithmetic operations on NaN keep producing NaN, so if you find yourself getting one of those in an unexpected place, look for accidental type conversions.

- -

When comparing values of the same type using ==, the outcome is easy to predict: you should get true when both values are the same, except in the case of NaN. But when the types differ, JavaScript uses a complicated and confusing set of rules to determine what to do. In most cases, it just tries to convert one of the values to the other value’s type. However, when null or undefined occurs on either side of the operator, it produces true only if both sides are one of null or undefined.

- -
console.log(null == undefined);
-// → true
-console.log(null == 0);
-// → false
- -

That behavior is often useful. When you want to test whether a value has a real value instead of null or undefined, you can compare it to null with the == (or !=) operator.

- -

But what if you want to test whether something refers to the precise value false? Expressions like 0 == false and "" == false are also true because of automatic type conversion. When you do not want any type conversions to happen, there are two additional operators: === and !==. The first tests whether a value is precisely equal to the other, and the second tests whether it is not precisely equal. So "" === false is false as expected.

- -

I recommend using the three-character comparison operators defensively to prevent unexpected type conversions from tripping you up. But when you’re certain the types on both sides will be the same, there is no problem with using the shorter operators.

- -

Short-circuiting of logical operators

- -

The logical operators && and || handle values of different types in a peculiar way. They will convert the value on their left side to Boolean type in order to decide what to do, but depending on the operator and the result of that conversion, they will return either the original left-hand value or the right-hand value.

- -

The || operator, for example, will return the value to its left when that can be converted to true and will return the value on its right otherwise. This has the expected effect when the values are Boolean and does something analogous for values of other types.

- -
console.log(null || "user")
-// → user
-console.log("Agnes" || "user")
-// → Agnes
- -

We can use this functionality as a way to fall back on a default value. If you have a value that might be empty, you can put || after it with a replacement value. If the initial value can be converted to false, you’ll get the replacement instead. The rules for converting strings and numbers to Boolean values state that 0, NaN, and the empty string ("") count as false, while all the other values count as true. So 0 || -1 produces -1, and "" || "!?" yields "!?".

- -

The && operator works similarly but the other way around. When the value to its left is something that converts to false, it returns that value, and otherwise it returns the value on its right.

- -

Another important property of these two operators is that the part to their right is evaluated only when necessary. In the case of true || X, no matter what X is—even if it’s a piece of program that does something terrible—the result will be true, and X is never evaluated. The same goes for false && X, which is false and will ignore X. This is called short-circuit evaluation.

- -

The conditional operator works in a similar way. Of the second and third values, only the one that is selected is evaluated.

- -

Summary

- -

We looked at four types of JavaScript values in this chapter: numbers, strings, Booleans, and undefined values.

- -

Such values are created by typing in their name (true, null) or value (13, "abc"). You can combine and transform values with operators. We saw binary operators for arithmetic (+, -, *, /, and %), string concatenation (+), comparison (==, !=, ===, !==, <, >, <=, >=), and logic (&&, ||), as well as several unary operators (- to negate a number, ! to negate logically, and typeof to find a value’s type) and a ternary operator (?:) to pick one of two values based on a third value.

- -

This gives you enough information to use JavaScript as a pocket calculator but not much more. The next chapter will start tying these expressions together into basic programs.

-
diff --git a/docs/02_program_structure.html b/docs/02_program_structure.html deleted file mode 100644 index 9b2bfd233..000000000 --- a/docs/02_program_structure.html +++ /dev/null @@ -1,515 +0,0 @@ - - - - - Program Structure :: Eloquent JavaScript - - - - - - -
- - -

Chapter 2Program Structure

- -
- -

And my heart glows bright red under my filmy, translucent skin and they have to administer 10cc of JavaScript to get me to come back. (I respond well to toxins in the blood.) Man, that stuff will kick the peaches right out your gills!

- -
_why, Why's (Poignant) Guide to Ruby
- -
Picture of tentacles holding objects
- -

In this chapter, we will start to do things that can actually be called programming. We will expand our command of the JavaScript language beyond the nouns and sentence fragments we’ve seen so far, to the point where we can express meaningful prose.

- -

Expressions and statements

- -

In Chapter 1, we made values and applied operators to them to get new values. Creating values like this is the main substance of any JavaScript program. But that substance has to be framed in a larger structure to be useful. So that’s what we’ll cover next.

- -

A fragment of code that produces a value is called an expression. Every value that is written literally (such as 22 or "psychoanalysis") is an expression. An expression between parentheses is also an expression, as is a binary operator applied to two expressions or a unary operator applied to one.

- -

This shows part of the beauty of a language-based interface. Expressions can contain other expressions in a way similar to how subsentences in human languages are nested—a subsentence can contain its own subsentences, and so on. This allows us to build expressions that describe arbitrarily complex computations.

- -

If an expression corresponds to a sentence fragment, a JavaScript statement corresponds to a full sentence. A program is a list of statements.

- -

The simplest kind of statement is an expression with a semicolon after it. This is a program:

- -
1;
-!false;
- -

It is a useless program, though. An expression can be content to just produce a value, which can then be used by the enclosing code. A statement stands on its own, so it amounts to something only if it affects the world. It could display something on the screen—that counts as changing the world—or it could change the internal state of the machine in a way that will affect the statements that come after it. These changes are called side effects. The statements in the previous example just produce the values 1 and true and then immediately throw them away. This leaves no impression on the world at all. When you run this program, nothing observable happens.

- -

In some cases, JavaScript allows you to omit the semicolon at the end of a statement. In other cases, it has to be there, or the next line will be treated as part of the same statement. The rules for when it can be safely omitted are somewhat complex and error-prone. So in this book, every statement that needs a semicolon will always get one. I recommend you do the same, at least until you’ve learned more about the subtleties of missing semicolons.

- -

Bindings

- -

How does a program keep an internal state? How does it remember things? We have seen how to produce new values from old values, but this does not change the old values, and the new value has to be immediately used or it will dissipate again. To catch and hold values, JavaScript provides a thing called a binding, or variable:

- -
let caught = 5 * 5;
- -

That’s a second kind of statement. The special word (keyword) let indicates that this sentence is going to define a binding. It is followed by the name of the binding and, if we want to immediately give it a value, by an = operator and an expression.

- -

The previous statement creates a binding called caught and uses it to grab hold of the number that is produced by multiplying 5 by 5.

- -

After a binding has been defined, its name can be used as an expression. The value of such an expression is the value the binding currently holds. Here’s an example:

- -
let ten = 10;
-console.log(ten * ten);
-// → 100
- -

When a binding points at a value, that does not mean it is tied to that value forever. The = operator can be used at any time on existing bindings to disconnect them from their current value and have them point to a new one.

- -
let mood = "light";
-console.log(mood);
-// → light
-mood = "dark";
-console.log(mood);
-// → dark
- -

You should imagine bindings as tentacles, rather than boxes. They do not contain values; they grasp them—two bindings can refer to the same value. A program can access only the values that it still has a reference to. When you need to remember something, you grow a tentacle to hold on to it or you reattach one of your existing tentacles to it.

- -

Let’s look at another example. To remember the number of dollars that Luigi still owes you, you create a binding. And then when he pays back $35, you give this binding a new value.

- -
let luigisDebt = 140;
-luigisDebt = luigisDebt - 35;
-console.log(luigisDebt);
-// → 105
- -

When you define a binding without giving it a value, the tentacle has nothing to grasp, so it ends in thin air. If you ask for the value of an empty binding, you’ll get the value undefined.

- -

A single let statement may define multiple bindings. The definitions must be separated by commas.

- -
let one = 1, two = 2;
-console.log(one + two);
-// → 3
- -

The words var and const can also be used to create bindings, in a way similar to let.

- -
var name = "Ayda";
-const greeting = "Hello ";
-console.log(greeting + name);
-// → Hello Ayda
- -

The first, var (short for “variable”), is the way bindings were declared in pre-2015 JavaScript. I’ll get back to the precise way it differs from let in the next chapter. For now, remember that it mostly does the same thing, but we’ll rarely use it in this book because it has some confusing properties.

- -

The word const stands for constant. It defines a constant binding, which points at the same value for as long as it lives. This is useful for bindings that give a name to a value so that you can easily refer to it later.

- -

Binding names

- -

Binding names can be any word. Digits can be part of binding names—catch22 is a valid name, for example—but the name must not start with a digit. A binding name may include dollar signs ($) or underscores (_) but no other punctuation or special characters.

- -

Words with a special meaning, such as let, are keywords, and they may not be used as binding names. There are also a number of words that are “reserved for use” in future versions of JavaScript, which also can’t be used as binding names. The full list of keywords and reserved words is rather long.

- -
break case catch class const continue debugger default
-delete do else enum export extends false finally for
-function if implements import interface in instanceof let
-new package private protected public return static super
-switch this throw true try typeof var void while with yield
- -

Don’t worry about memorizing this list. When creating a binding produces an unexpected syntax error, see whether you’re trying to define a reserved word.

- -

The environment

- -

The collection of bindings and their values that exist at a given time is called the environment. When a program starts up, this environment is not empty. It always contains bindings that are part of the language standard, and most of the time, it also has bindings that provide ways to interact with the surrounding system. For example, in a browser, there are functions to interact with the currently loaded website and to read mouse and keyboard input.

- -

Functions

- -

A lot of the values provided in the default environment have the type function. A function is a piece of program wrapped in a value. Such values can be applied in order to run the wrapped program. For example, in a browser environment, the binding prompt holds a function that shows a little dialog box asking for user input. It is used like this:

- -
prompt("Enter passcode");
A prompt dialog
- -

Executing a function is called invoking, calling, or applying it. You can call a function by putting parentheses after an expression that produces a function value. Usually you’ll directly use the name of the binding that holds the function. The values between the parentheses are given to the program inside the function. In the example, the prompt function uses the string that we give it as the text to show in the dialog box. Values given to functions are called arguments. Different functions might need a different number or different types of arguments.

- -

The prompt function isn’t used much in modern web programming, mostly because you have no control over the way the resulting dialog looks, but can be helpful in toy programs and experiments.

- -

The console.log function

- -

In the examples, I used console.log to output values. Most JavaScript systems (including all modern web browsers and Node.js) provide a console.log function that writes out its arguments to some text output device. In browsers, the output lands in the JavaScript -console. This part of the browser interface is hidden by default, but most browsers open it when you press F12 or, on a Mac, command-option-I. If that does not work, search through the menus for an item named Developer Tools or similar.

- -

When running the examples (or your own code) on the pages of this book, console.log output will be shown after the example, instead of in the browser’s JavaScript console.

- -
let x = 30;
-console.log("the value of x is", x);
-// → the value of x is 30
- -

Though binding names cannot contain period characters, console.log does have one. This is because console.log isn’t a simple binding. It is actually an expression that retrieves the log property from the value held by the console binding. We’ll find out exactly what this means in Chapter 4.

- -

Return values

- -

Showing a dialog box or writing text to the screen is a side -effect. A lot of functions are useful because of the side effects they produce. Functions may also produce values, in which case they don’t need to have a side effect to be useful. For example, the function Math.max takes any amount of number arguments and gives back the greatest.

- -
console.log(Math.max(2, 4));
-// → 4
- -

When a function produces a value, it is said to return that value. Anything that produces a value is an expression in JavaScript, which means function calls can be used within larger expressions. Here a call to Math.min, which is the opposite of Math.max, is used as part of a plus expression:

- -
console.log(Math.min(2, 4) + 100);
-// → 102
- -

The next chapter explains how to write your own functions.

- -

Control flow

- -

When your program contains more than one statement, the statements are executed as if they are a story, from top to bottom. This example program has two statements. The first one asks the user for a number, and the second, which is executed after the first, shows the square of that number.

- -
let theNumber = Number(prompt("Pick a number"));
-console.log("Your number is the square root of " +
-            theNumber * theNumber);
- -

The function Number converts a value to a number. We need that conversion because the result of prompt is a string value, and we want a number. There are similar functions called String and Boolean that convert values to those types.

- -

Here is the rather trivial schematic representation of straight-line control flow:

Trivial control flow
- -

Conditional execution

- -

Not all programs are straight roads. We may, for example, want to create a branching road, where the program takes the proper branch based on the situation at hand. This is called conditional -execution.

Conditional control flow
- -

Conditional execution is created with the if keyword in JavaScript. In the simple case, we want some code to be executed if, and only if, a certain condition holds. We might, for example, want to show the square of the input only if the input is actually a number.

- -
let theNumber = Number(prompt("Pick a number"));
-if (!Number.isNaN(theNumber)) {
-  console.log("Your number is the square root of " +
-              theNumber * theNumber);
-}
- -

With this modification, if you enter “parrot”, no output is shown.

- -

The if keyword executes or skips a statement depending on the value of a Boolean expression. The deciding expression is written after the keyword, between parentheses, followed by the statement to execute.

- -

The Number.isNaN function is a standard JavaScript function that returns true only if the argument it is given is NaN. The Number function happens to return NaN when you give it a string that doesn’t represent a valid number. Thus, the condition translates to “unless theNumber is not-a-number, do this”.

- -

The statement after the if is wrapped in braces ({ and }) in this example. The braces can be used to group any number of statements into a single statement, called a block. You could also have omitted them in this case, since they hold only a single statement, but to avoid having to think about whether they are needed, most JavaScript programmers use them in every wrapped statement like this. We’ll mostly follow that convention in this book, except for the occasional one-liner.

- -
if (1 + 1 == 2) console.log("It's true");
-// → It's true
- -

You often won’t just have code that executes when a condition holds true, but also code that handles the other case. This alternate path is represented by the second arrow in the diagram. You can use the else keyword, together with if, to create two separate, alternative execution paths.

- -
let theNumber = Number(prompt("Pick a number"));
-if (!Number.isNaN(theNumber)) {
-  console.log("Your number is the square root of " +
-              theNumber * theNumber);
-} else {
-  console.log("Hey. Why didn't you give me a number?");
-}
- -

If you have more than two paths to choose from, you can “chain” multiple if/else pairs together. Here’s an example:

- -
let num = Number(prompt("Pick a number"));
-
-if (num < 10) {
-  console.log("Small");
-} else if (num < 100) {
-  console.log("Medium");
-} else {
-  console.log("Large");
-}
- -

The program will first check whether num is less than 10. If it is, it chooses that branch, shows "Small", and is done. If it isn’t, it takes the else branch, which itself contains a second if. If the second condition (< 100) holds, that means the number is between 10 and 100, and "Medium" is shown. If it doesn’t, the second and last else branch is chosen.

- -

The schema for this program looks something like this:

Nested if control flow
- -

while and do loops

- -

Consider a program that outputs all even numbers from 0 to 12. One way to write this is as follows:

- -
console.log(0);
-console.log(2);
-console.log(4);
-console.log(6);
-console.log(8);
-console.log(10);
-console.log(12);
- -

That works, but the idea of writing a program is to make something less work, not more. If we needed all even numbers less than 1,000, this approach would be unworkable. What we need is a way to run a piece of code multiple times. This form of control flow is called a loop.

Loop control flow
- -

Looping control flow allows us to go back to some point in the program where we were before and repeat it with our current program state. If we combine this with a binding that counts, we can do something like this:

- -
let number = 0;
-while (number <= 12) {
-  console.log(number);
-  number = number + 2;
-}
-// → 0
-// → 2
-//   … etcetera
- -

A statement starting with the keyword while creates a loop. The word while is followed by an expression in parentheses and then a statement, much like if. The loop keeps entering that statement as long as the expression produces a value that gives true when converted to Boolean.

- -

The number binding demonstrates the way a binding can track the progress of a program. Every time the loop repeats, number gets a value that is 2 more than its previous value. At the beginning of every repetition, it is compared with the number 12 to decide whether the program’s work is finished.

- -

As an example that actually does something useful, we can now write a program that calculates and shows the value of 210 (2 to the 10th power). We use two bindings: one to keep track of our result and one to count how often we have multiplied this result by 2. The loop tests whether the second binding has reached 10 yet and, if not, updates both bindings.

- -
let result = 1;
-let counter = 0;
-while (counter < 10) {
-  result = result * 2;
-  counter = counter + 1;
-}
-console.log(result);
-// → 1024
- -

The counter could also have started at 1 and checked for <= 10, but for reasons that will become apparent in Chapter 4, it is a good idea to get used to counting from 0.

- -

A do loop is a control structure similar to a while loop. It differs only on one point: a do loop always executes its body at least once, and it starts testing whether it should stop only after that first execution. To reflect this, the test appears after the body of the loop.

- -
let yourName;
-do {
-  yourName = prompt("Who are you?");
-} while (!yourName);
-console.log(yourName);
- -

This program will force you to enter a name. It will ask again and again until it gets something that is not an empty string. Applying the ! operator will convert a value to Boolean type before negating it, and all strings except "" convert to true. This means the loop continues going round until you provide a non-empty name.

- -

Indenting Code

- -

In the examples, I’ve been adding spaces in front of statements that are part of some larger statement. These spaces are not required—the computer will accept the program just fine without them. In fact, even the line breaks in programs are optional. You could write a program as a single long line if you felt like it.

- -

The role of this indentation inside blocks is to make the structure of the code stand out. In code where new blocks are opened inside other blocks, it can become hard to see where one block ends and another begins. With proper indentation, the visual shape of a program corresponds to the shape of the blocks inside it. I like to use two spaces for every open block, but tastes differ—some people use four spaces, and some people use tab characters. The important thing is that each new block adds the same amount of space.

- -
if (false != true) {
-  console.log("That makes sense.");
-  if (1 < 2) {
-    console.log("No surprise there.");
-  }
-}
- -

Most code editor programs (including the one in this book) will help by automatically indenting new lines the proper amount.

- -

for loops

- -

Many loops follow the pattern shown in the while examples. First a “counter” binding is created to track the progress of the loop. Then comes a while loop, usually with a test expression that checks whether the counter has reached its end value. At the end of the loop body, the counter is updated to track progress.

- -

Because this pattern is so common, JavaScript and similar languages provide a slightly shorter and more comprehensive form, the for loop.

- -
for (let number = 0; number <= 12; number = number + 2) {
-  console.log(number);
-}
-// → 0
-// → 2
-//   … etcetera
- -

This program is exactly equivalent to the earlier even-number-printing example. The only change is that all the statements that are related to the “state” of the loop are grouped together after for.

- -

The parentheses after a for keyword must contain two semicolons. The part before the first semicolon initializes the loop, usually by defining a binding. The second part is the expression that checks whether the loop must continue. The final part updates the state of the loop after every iteration. In most cases, this is shorter and clearer than a while construct.

- -

This is the code that computes 210 using for instead of while:

- -
let result = 1;
-for (let counter = 0; counter < 10; counter = counter + 1) {
-  result = result * 2;
-}
-console.log(result);
-// → 1024
- -

Breaking Out of a Loop

- -

Having the looping condition produce false is not the only way a loop can finish. There is a special statement called break that has the effect of immediately jumping out of the enclosing loop.

- -

This program illustrates the break statement. It finds the first number that is both greater than or equal to 20 and divisible by 7.

- -
for (let current = 20; ; current = current + 1) {
-  if (current % 7 == 0) {
-    console.log(current);
-    break;
-  }
-}
-// → 21
- -

Using the remainder (%) operator is an easy way to test whether a number is divisible by another number. If it is, the remainder of their division is zero.

- -

The for construct in the example does not have a part that checks for the end of the loop. This means that the loop will never stop unless the break statement inside is executed.

- -

If you were to remove that break statement or you accidentally write an end condition that always produces true, your program would get stuck in an infinite loop. A program stuck in an infinite loop will never finish running, which is usually a bad thing.

- -

If you create an infinite loop in one of the examples on these pages, you’ll usually be asked whether you want to stop the script after a few seconds. If that fails, you will have to close the tab that you’re working in, or on some browsers close your whole browser, to recover.

- -

The continue keyword is similar to break, in that it influences the progress of a loop. When continue is encountered in a loop body, control jumps out of the body and continues with the loop’s next iteration.

- -

Updating bindings succinctly

- -

Especially when looping, a program often needs to “update” a binding to hold a value based on that binding’s previous value.

- -
counter = counter + 1;
- -

JavaScript provides a shortcut for this.

- -
counter += 1;
- -

Similar shortcuts work for many other operators, such as result *= 2 to double result or counter -= 1 to count downward.

- -

This allows us to shorten our counting example a little more.

- -
for (let number = 0; number <= 12; number += 2) {
-  console.log(number);
-}
- -

For counter += 1 and counter -= 1, there are even shorter equivalents: counter++ and counter--.

- -

Dispatching on a value with switch

- -

It is not uncommon for code to look like this:

- -
if (x == "value1") action1();
-else if (x == "value2") action2();
-else if (x == "value3") action3();
-else defaultAction();
- -

There is a construct called switch that is intended to express such a “dispatch” in a more direct way. Unfortunately, the syntax JavaScript uses for this (which it inherited from the C/Java line of programming languages) is somewhat awkward—a chain of if statements may look better. Here is an example:

- -
switch (prompt("What is the weather like?")) {
-  case "rainy":
-    console.log("Remember to bring an umbrella.");
-    break;
-  case "sunny":
-    console.log("Dress lightly.");
-  case "cloudy":
-    console.log("Go outside.");
-    break;
-  default:
-    console.log("Unknown weather type!");
-    break;
-}
- -

You may put any number of case labels inside the block opened by switch. The program will start executing at the label that corresponds to the value that switch was given, or at default if no matching value is found. It will continue executing, even across other labels, until it reaches a break statement. In some cases, such as the "sunny" case in the example, this can be used to share some code between cases (it recommends going outside for both sunny and cloudy weather). But be careful—it is easy to forget such a break, which will cause the program to execute code you do not want executed.

- -

Capitalization

- -

Binding names may not contain spaces, yet it is often helpful to use multiple words to clearly describe what the binding represents. These are pretty much your choices for writing a binding name with several words in it:

- -
fuzzylittleturtle
-fuzzy_little_turtle
-FuzzyLittleTurtle
-fuzzyLittleTurtle
- -

The first style can be hard to read. I rather like the look of the underscores, though that style is a little painful to type. The standard JavaScript functions, and most JavaScript programmers, follow the bottom style—they capitalize every word except the first. It is not hard to get used to little things like that, and code with mixed naming styles can be jarring to read, so we follow this convention.

- -

In a few cases, such as the Number function, the first letter of a binding is also capitalized. This was done to mark this function as a constructor. What a constructor is will become clear in Chapter 6. For now, the important thing is not to be bothered by this apparent lack of consistency.

- -

Comments

- -

Often, raw code does not convey all the information you want a program to convey to human readers, or it conveys it in such a cryptic way that people might not understand it. At other times, you might just want to include some related thoughts as part of your program. This is what comments are for.

- -

A comment is a piece of text that is part of a program but is completely ignored by the computer. JavaScript has two ways of writing comments. To write a single-line comment, you can use two slash characters (//) and then the comment text after it.

- -
let accountBalance = calculateBalance(account);
-// It's a green hollow where a river sings
-accountBalance.adjust();
-// Madly catching white tatters in the grass.
-let report = new Report();
-// Where the sun on the proud mountain rings:
-addToReport(accountBalance, report);
-// It's a little valley, foaming like light in a glass.
- -

A // comment goes only to the end of the line. A section of text between /* and */ will be ignored in its entirety, regardless of whether it contains line breaks. This is useful for adding blocks of information about a file or a chunk of program.

- -
/*
-  I first found this number scrawled on the back of an old notebook.
-  Since then, it has often dropped by, showing up in phone numbers
-  and the serial numbers of products that I've bought. It obviously
-  likes me, so I've decided to keep it.
-*/
-const myNumber = 11213;
- -

Summary

- -

You now know that a program is built out of statements, which themselves sometimes contain more statements. Statements tend to contain expressions, which themselves can be built out of smaller expressions.

- -

Putting statements after one another gives you a program that is executed from top to bottom. You can introduce disturbances in the flow of control by using conditional (if, else, and switch) and looping (while, do, and for) statements.

- -

Bindings can be used to file pieces of data under a name, and they are useful for tracking state in your program. The environment is the set of bindings that are defined. JavaScript systems always put a number of useful standard bindings into your environment.

- -

Functions are special values that encapsulate a piece of program. You can invoke them by writing functionName(argument1, argument2). Such a function call is an expression and may produce a value.

- -

Exercises

- -

If you are unsure how to test your solutions to the exercises, refer to the Introduction.

- -

Each exercise starts with a problem description. Read this description and try to solve the exercise. If you run into problems, consider reading the hints after the exercise. Full solutions to the exercises are not included in this book, but you can find them online at https://eloquentjavascript.net/code. If you want to learn something from the exercises, I recommend looking at the solutions only after you’ve solved the exercise, or at least after you’ve attacked it long and hard enough to have a slight headache.

- -

Looping a triangle

- -

Write a loop that makes seven calls to console.log to output the following triangle:

- -
#
-##
-###
-####
-#####
-######
-#######
- -

It may be useful to know that you can find the length of a string by writing .length after it.

- -
let abc = "abc";
-console.log(abc.length);
-// → 3
- -

Most exercises contain a piece of code that you can modify to solve the exercise. Remember that you can click code blocks to edit them.

- -
// Your code here.
- -
- -

You can start with a program that prints out the numbers 1 to 7, which you can derive by making a few modifications to the even number printing example given earlier in the chapter, where the for loop was introduced.

- -

Now consider the equivalence between numbers and strings of hash characters. You can go from 1 to 2 by adding 1 (+= 1). You can go from "#" to "##" by adding a character (+= "#"). Thus, your solution can closely follow the number-printing program.

- -
- -

FizzBuzz

- -

Write a program that uses console.log to print all the numbers from 1 to 100, with two exceptions. For numbers divisible by 3, print "Fizz" instead of the number, and for numbers divisible by 5 (and not 3), print "Buzz" instead.

- -

When you have that working, modify your program to print "FizzBuzz" for numbers that are divisible by both 3 and 5 (and still print "Fizz" or "Buzz" for numbers divisible by only one of those).

- -

(This is actually an interview question that has been claimed to weed out a significant percentage of programmer candidates. So if you solved it, your labor market value just went up.)

- -
// Your code here.
- -
- -

Going over the numbers is clearly a looping job, and selecting what to print is a matter of conditional execution. Remember the trick of using the remainder (%) operator for checking whether a number is divisible by another number (has a remainder of zero).

- -

In the first version, there are three possible outcomes for every number, so you’ll have to create an if/else if/else chain.

- -

The second version of the program has a straightforward solution and a clever one. The simple solution is to add another conditional “branch” to precisely test the given condition. For the clever solution, build up a string containing the word or words to output and print either this word or the number if there is no word, potentially by making good use of the || operator.

- -
- -

Chessboard

- -

Write a program that creates a string that represents an 8×8 grid, using newline characters to separate lines. At each position of the grid there is either a space or a "#" character. The characters should form a chessboard.

- -

Passing this string to console.log should show something like this:

- -
 # # # #
-# # # # 
- # # # #
-# # # # 
- # # # #
-# # # # 
- # # # #
-# # # #
- -

When you have a program that generates this pattern, define a binding size = 8 and change the program so that it works for any size, outputting a grid of the given width and height.

- -
// Your code here.
- -
- -

You can build the string by starting with an empty one ("") and repeatedly adding characters. A newline character is written "\n".

- -

To work with two dimensions, you will need a loop inside of a loop. Put braces around the bodies of both loops to make it easy to see where they start and end. Try to properly indent these bodies. The order of the loops must follow the order in which we build up the string (line by line, left to right, top to bottom). So the outer loop handles the lines, and the inner loop handles the characters on a line.

- -

You’ll need two bindings to track your progress. To know whether to put a space or a hash sign at a given position, you could test whether the sum of the two counters is even (% 2).

- -

Terminating a line by adding a newline character must happen after the line has been built up, so do this after the inner loop but inside the outer loop.

- -
-
diff --git a/docs/03_functions.html b/docs/03_functions.html deleted file mode 100644 index 405fdfed2..000000000 --- a/docs/03_functions.html +++ /dev/null @@ -1,581 +0,0 @@ - - - - - Functions :: Eloquent JavaScript - - - - - - -
- - -

Chapter 3Functions

- -
- -

People think that computer science is the art of geniuses but the actual reality is the opposite, just many people doing things that build on each other, like a wall of mini stones.

- -
Donald Knuth
- -
Picture of fern leaves with a fractal shape
- -

Functions are the bread and butter of JavaScript programming. The concept of wrapping a piece of program in a value has many uses. It gives us a way to structure larger programs, to reduce repetition, to associate names with subprograms, and to isolate these subprograms from each other.

- -

The most obvious application of functions is defining new vocabulary. Creating new words in prose is usually bad style. But in programming, it is indispensable.

- -

Typical adult English speakers have some 20,000 words in their vocabulary. Few programming languages come with 20,000 commands built in. And the vocabulary that is available tends to be more precisely defined, and thus less flexible, than in human language. Therefore, we usually have to introduce new concepts to avoid repeating ourselves too much.

- -

Defining a function

- -

A function definition is a regular binding where the value of the binding is a function. For example, this code defines square to refer to a function that produces the square of a given number:

- -
const square = function(x) {
-  return x * x;
-};
-
-console.log(square(12));
-// → 144
- -

A function is created with an expression that starts with the keyword function. Functions have a set of parameters (in this case, only x) and a body, which contains the statements that are to be executed when the function is called. The function body of a function created this way must always be wrapped in braces, even when it consists of only a single statement.

- -

A function can have multiple parameters or no parameters at all. In the following example, makeNoise does not list any parameter names, whereas power lists two:

- -
const makeNoise = function() {
-  console.log("Pling!");
-};
-
-makeNoise();
-// → Pling!
-
-const power = function(base, exponent) {
-  let result = 1;
-  for (let count = 0; count < exponent; count++) {
-    result *= base;
-  }
-  return result;
-};
-
-console.log(power(2, 10));
-// → 1024
- -

Some functions produce a value, such as power and square, and some don’t, such as makeNoise, whose only result is a side effect. A return statement determines the value the function returns. When control comes across such a statement, it immediately jumps out of the current function and gives the returned value to the code that called the function. A return keyword without an expression after it will cause the function to return undefined. Functions that don’t have a return statement at all, such as makeNoise, similarly return undefined.

- -

Parameters to a function behave like regular bindings, but their initial values are given by the caller of the function, not the code in the function itself.

- -

Bindings and scopes

- -

Each binding has a scope, which is the part of the program in which the binding is visible. For bindings defined outside of any function or block, the scope is the whole program—you can refer to such bindings wherever you want. These are called global.

- -

But bindings created for function parameters or declared inside a function can be referenced only in that function, so they are known as local bindings. Every time the function is called, new instances of these bindings are created. This provides some isolation between functions—each function call acts in its own little world (its local environment) and can often be understood without knowing a lot about what’s going on in the global environment.

- -

Bindings declared with let and const are in fact local to the block that they are declared in, so if you create one of those inside of a loop, the code before and after the loop cannot “see” it. In pre-2015 JavaScript, only functions created new scopes, so old-style bindings, created with the var keyword, are visible throughout the whole function that they appear in—or throughout the global scope, if they are not in a function.

- -
let x = 10;
-if (true) {
-  let y = 20;
-  var z = 30;
-  console.log(x + y + z);
-  // → 60
-}
-// y is not visible here
-console.log(x + z);
-// → 40
- -

Each scope can “look out” into the scope around it, so x is visible inside the block in the example. The exception is when multiple bindings have the same name—in that case, code can see only the innermost one. For example, when the code inside the halve function refers to n, it is seeing its own n, not the global n.

- -
const halve = function(n) {
-  return n / 2;
-};
-
-let n = 10;
-console.log(halve(100));
-// → 50
-console.log(n);
-// → 10
- -

Nested scope

- -

JavaScript distinguishes not just global and local bindings. Blocks and functions can be created inside other blocks and functions, producing multiple degrees of locality.

- -

For example, this function—which outputs the ingredients needed to make a batch of hummus—has another function inside it:

- -
const hummus = function(factor) {
-  const ingredient = function(amount, unit, name) {
-    let ingredientAmount = amount * factor;
-    if (ingredientAmount > 1) {
-      unit += "s";
-    }
-    console.log(`${ingredientAmount} ${unit} ${name}`);
-  };
-  ingredient(1, "can", "chickpeas");
-  ingredient(0.25, "cup", "tahini");
-  ingredient(0.25, "cup", "lemon juice");
-  ingredient(1, "clove", "garlic");
-  ingredient(2, "tablespoon", "olive oil");
-  ingredient(0.5, "teaspoon", "cumin");
-};
- -

The code inside the ingredient function can see the factor binding from the outer function. But its local bindings, such as unit or ingredientAmount, are not visible in the outer function.

- -

The set of bindings visible inside a block is determined by the place of that block in the program text. Each local scope can also see all the local scopes that contain it, and all scopes can see the global scope. This approach to binding visibility is called lexical scoping.

- -

Functions as values

- -

A function binding usually simply acts as a name for a specific piece of the program. Such a binding is defined once and never changed. This makes it easy to confuse the function and its name.

- -

But the two are different. A function value can do all the things that other values can do—you can use it in arbitrary expressions, not just call it. It is possible to store a function value in a new binding, pass it as an argument to a function, and so on. Similarly, a binding that holds a function is still just a regular binding and can, if not constant, be assigned a new value, like so:

- -
let launchMissiles = function() {
-  missileSystem.launch("now");
-};
-if (safeMode) {
-  launchMissiles = function() {/* do nothing */};
-}
- -

In Chapter 5, we will discuss the interesting things that can be done by passing around function values to other functions.

- -

Declaration notation

- -

There is a slightly shorter way to create a function binding. When the function keyword is used at the start of a statement, it works differently.

- -
function square(x) {
-  return x * x;
-}
- -

This is a function declaration. The statement defines the binding square and points it at the given function. It is slightly easier to write and doesn’t require a semicolon after the function.

- -

There is one subtlety with this form of function definition.

- -
console.log("The future says:", future());
-
-function future() {
-  return "You'll never have flying cars";
-}
- -

The preceding code works, even though the function is defined below the code that uses it. Function declarations are not part of the regular top-to-bottom flow of control. They are conceptually moved to the top of their scope and can be used by all the code in that scope. This is sometimes useful because it offers the freedom to order code in a way that seems meaningful, without worrying about having to define all functions before they are used.

- -

Arrow functions

- -

There’s a third notation for functions, which looks very different from the others. Instead of the function keyword, it uses an arrow (=>) made up of an equal sign and a greater-than character (not to be confused with the greater-than-or-equal operator, which is written >=).

- -
const power = (base, exponent) => {
-  let result = 1;
-  for (let count = 0; count < exponent; count++) {
-    result *= base;
-  }
-  return result;
-};
- -

The arrow comes after the list of parameters and is followed by the function’s body. It expresses something like “this input (the parameters) produces this result (the body)”.

- -

When there is only one parameter name, you can omit the parentheses around the parameter list. If the body is a single expression, rather than a block in braces, that expression will be returned from the function. So, these two definitions of square do the same thing:

- -
const square1 = (x) => { return x * x; };
-const square2 = x => x * x;
- -

When an arrow function has no parameters at all, its parameter list is just an empty set of parentheses.

- -
const horn = () => {
-  console.log("Toot");
-};
- -

There’s no deep reason to have both arrow functions and function expressions in the language. Apart from a minor detail, which we’ll discuss in Chapter 6, they do the same thing. Arrow functions were added in 2015, mostly to make it possible to write small function expressions in a less verbose way. We’ll be using them a lot in Chapter 5.

- -

The call stack

- -

The way control flows through functions is somewhat involved. Let’s take a closer look at it. Here is a simple program that makes a few function calls:

- -
function greet(who) {
-  console.log("Hello " + who);
-}
-greet("Harry");
-console.log("Bye");
- -

A run through this program goes roughly like this: the call to greet causes control to jump to the start of that function (line 2). The function calls console.log, which takes control, does its job, and then returns control to line 2. There it reaches the end of the greet function, so it returns to the place that called it, which is line 4. The line after that calls console.log again. After that returns, the program reaches its end.

- -

We could show the flow of control schematically like this:

- -
not in function
-   in greet
-        in console.log
-   in greet
-not in function
-   in console.log
-not in function
- -

Because a function has to jump back to the place that called it when it returns, the computer must remember the context from which the call happened. In one case, console.log has to return to the greet function when it is done. In the other case, it returns to the end of the program.

- -

The place where the computer stores this context is the call -stack. Every time a function is called, the current context is stored on top of this stack. When a function returns, it removes the top context from the stack and uses that context to continue execution.

- -

Storing this stack requires space in the computer’s memory. When the stack grows too big, the computer will fail with a message like “out of stack space” or “too much recursion”. The following code illustrates this by asking the computer a really hard question that causes an infinite back-and-forth between two functions. Rather, it would be infinite, if the computer had an infinite stack. As it is, we will run out of space, or “blow the stack”.

- -
function chicken() {
-  return egg();
-}
-function egg() {
-  return chicken();
-}
-console.log(chicken() + " came first.");
-// → ??
- -

Optional Arguments

- -

The following code is allowed and executes without any problem:

- -
function square(x) { return x * x; }
-console.log(square(4, true, "hedgehog"));
-// → 16
- -

We defined square with only one parameter. Yet when we call it with three, the language doesn’t complain. It ignores the extra arguments and computes the square of the first one.

- -

JavaScript is extremely broad-minded about the number of arguments you pass to a function. If you pass too many, the extra ones are ignored. If you pass too few, the missing parameters get assigned the value undefined.

- -

The downside of this is that it is possible—likely, even—that you’ll accidentally pass the wrong number of arguments to functions. And no one will tell you about it.

- -

The upside is that this behavior can be used to allow a function to be called with different numbers of arguments. For example, this minus function tries to imitate the - operator by acting on either one or two arguments:

- -
function minus(a, b) {
-  if (b === undefined) return -a;
-  else return a - b;
-}
-
-console.log(minus(10));
-// → -10
-console.log(minus(10, 5));
-// → 5
- -

If you write an = operator after a parameter, followed by an expression, the value of that expression will replace the argument when it is not given.

- -

For example, this version of power makes its second argument optional. If you don’t provide it or pass the value undefined, it will default to two, and the function will behave like square.

- -
function power(base, exponent = 2) {
-  let result = 1;
-  for (let count = 0; count < exponent; count++) {
-    result *= base;
-  }
-  return result;
-}
-
-console.log(power(4));
-// → 16
-console.log(power(2, 6));
-// → 64
- -

In the next chapter, we will see a way in which a function body can get at the whole list of arguments it was passed. This is helpful because it makes it possible for a function to accept any number of arguments. For example, console.log does this—it outputs all of the values it is given.

- -
console.log("C", "O", 2);
-// → C O 2
- -

Closure

- -

The ability to treat functions as values, combined with the fact that local bindings are re-created every time a function is called, brings up an interesting question. What happens to local bindings when the function call that created them is no longer active?

- -

The following code shows an example of this. It defines a function, wrapValue, that creates a local binding. It then returns a function that accesses and returns this local binding.

- -
function wrapValue(n) {
-  let local = n;
-  return () => local;
-}
-
-let wrap1 = wrapValue(1);
-let wrap2 = wrapValue(2);
-console.log(wrap1());
-// → 1
-console.log(wrap2());
-// → 2
- -

This is allowed and works as you’d hope—both instances of the binding can still be accessed. This situation is a good demonstration of the fact that local bindings are created anew for every call, and different calls can’t trample on one another’s local bindings.

- -

This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called closure. A function that references bindings from local scopes around it is called a closure. This behavior not only frees you from having to worry about lifetimes of bindings but also makes it possible to use function values in some creative ways.

- -

With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount.

- -
function multiplier(factor) {
-  return number => number * factor;
-}
-
-let twice = multiplier(2);
-console.log(twice(5));
-// → 10
- -

The explicit local binding from the wrapValue example isn’t really needed since a parameter is itself a local binding.

- -

Thinking about programs like this takes some practice. A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees the environment in which it was created, not the environment in which it is called.

- -

In the example, multiplier is called and creates an environment in which its factor parameter is bound to 2. The function value it returns, which is stored in twice, remembers this environment. So when that is called, it multiplies its argument by 2.

- -

Recursion

- -

It is perfectly okay for a function to call itself, as long as it doesn’t do it so often that it overflows the stack. A function that calls itself is called recursive. Recursion allows some functions to be written in a different style. Take, for example, this alternative implementation of power:

- -
function power(base, exponent) {
-  if (exponent == 0) {
-    return 1;
-  } else {
-    return base * power(base, exponent - 1);
-  }
-}
-
-console.log(power(2, 3));
-// → 8
- -

This is rather close to the way mathematicians define exponentiation and arguably describes the concept more clearly than the looping variant. The function calls itself multiple times with ever smaller exponents to achieve the repeated multiplication.

- -

But this implementation has one problem: in typical JavaScript implementations, it’s about three times slower than the looping version. Running through a simple loop is generally cheaper than calling a function multiple times.

- -

The dilemma of speed versus elegance is an interesting one. You can see it as a kind of continuum between human-friendliness and machine-friendliness. Almost any program can be made faster by making it bigger and more convoluted. The programmer has to decide on an appropriate balance.

- -

In the case of the power function, the inelegant (looping) version is still fairly simple and easy to read. It doesn’t make much sense to replace it with the recursive version. Often, though, a program deals with such complex concepts that giving up some efficiency in order to make the program more straightforward is helpful.

- -

Worrying about efficiency can be a distraction. It’s yet another factor that complicates program design, and when you’re doing something that’s already difficult, that extra thing to worry about can be paralyzing.

- -

Therefore, always start by writing something that’s correct and easy to understand. If you’re worried that it’s too slow—which it usually isn’t since most code simply isn’t executed often enough to take any significant amount of time—you can measure afterward and improve it if necessary.

- -

Recursion is not always just an inefficient alternative to looping. Some problems really are easier to solve with recursion than with loops. Most often these are problems that require exploring or processing several “branches”, each of which might branch out again into even more branches.

- -

Consider this puzzle: by starting from the number 1 and repeatedly either adding 5 or multiplying by 3, an infinite set of numbers can be produced. How would you write a function that, given a number, tries to find a sequence of such additions and multiplications that produces that number?

- -

For example, the number 13 could be reached by first multiplying by 3 and then adding 5 twice, whereas the number 15 cannot be reached at all.

- -

Here is a recursive solution:

- -
function findSolution(target) {
-  function find(current, history) {
-    if (current == target) {
-      return history;
-    } else if (current > target) {
-      return null;
-    } else {
-      return find(current + 5, `(${history} + 5)`) ||
-             find(current * 3, `(${history} * 3)`);
-    }
-  }
-  return find(1, "1");
-}
-
-console.log(findSolution(24));
-// → (((1 * 3) + 5) * 3)
- -

Note that this program doesn’t necessarily find the shortest sequence of operations. It is satisfied when it finds any sequence at all.

- -

It is okay if you don’t see how it works right away. Let’s work through it, since it makes for a great exercise in recursive thinking.

- -

The inner function find does the actual recursing. It takes two arguments: the current number and a string that records how we reached this number. If it finds a solution, it returns a string that shows how to get to the target. If no solution can be found starting from this number, it returns null.

- -

To do this, the function performs one of three actions. If the current number is the target number, the current history is a way to reach that target, so it is returned. If the current number is greater than the target, there’s no sense in further exploring this branch because both adding and multiplying will only make the number bigger, so it returns null. Finally, if we’re still below the target number, the function tries both possible paths that start from the current number by calling itself twice, once for addition and once for multiplication. If the first call returns something that is not null, it is returned. Otherwise, the second call is returned, regardless of whether it produces a string or null.

- -

To better understand how this function produces the effect we’re looking for, let’s look at all the calls to find that are made when searching for a solution for the number 13.

- -
find(1, "1")
-  find(6, "(1 + 5)")
-    find(11, "((1 + 5) + 5)")
-      find(16, "(((1 + 5) + 5) + 5)")
-        too big
-      find(33, "(((1 + 5) + 5) * 3)")
-        too big
-    find(18, "((1 + 5) * 3)")
-      too big
-  find(3, "(1 * 3)")
-    find(8, "((1 * 3) + 5)")
-      find(13, "(((1 * 3) + 5) + 5)")
-        found!
- -

The indentation indicates the depth of the call stack. The first time find is called, it starts by calling itself to explore the solution that starts with (1 + 5). That call will further recurse to explore every continued solution that yields a number less than or equal to the target number. Since it doesn’t find one that hits the target, it returns null back to the first call. There the || operator causes the call that explores (1 * 3) to happen. This search has more luck—its first recursive call, through yet another recursive call, hits upon the target number. That innermost call returns a string, and each of the || operators in the intermediate calls passes that string along, ultimately returning the solution.

- -

Growing functions

- -

There are two more or less natural ways for functions to be introduced into programs.

- -

The first is that you find yourself writing similar code multiple times. You’d prefer not to do that. Having more code means more space for mistakes to hide and more material to read for people trying to understand the program. So you take the repeated functionality, find a good name for it, and put it into a function.

- -

The second way is that you find you need some functionality that you haven’t written yet and that sounds like it deserves its own function. You’ll start by naming the function, and then you’ll write its body. You might even start writing code that uses the function before you actually define the function itself.

- -

How difficult it is to find a good name for a function is a good indication of how clear a concept it is that you’re trying to wrap. Let’s go through an example.

- -

We want to write a program that prints two numbers: the numbers of cows and chickens on a farm, with the words Cows and Chickens after them and zeros padded before both numbers so that they are always three digits long.

- -
007 Cows
-011 Chickens
- -

This asks for a function of two arguments—the number of cows and the number of chickens. Let’s get coding.

- -
function printFarmInventory(cows, chickens) {
-  let cowString = String(cows);
-  while (cowString.length < 3) {
-    cowString = "0" + cowString;
-  }
-  console.log(`${cowString} Cows`);
-  let chickenString = String(chickens);
-  while (chickenString.length < 3) {
-    chickenString = "0" + chickenString;
-  }
-  console.log(`${chickenString} Chickens`);
-}
-printFarmInventory(7, 11);
- -

Writing .length after a string expression will give us the length of that string. Thus, the while loops keep adding zeros in front of the number strings until they are at least three characters long.

- -

Mission accomplished! But just as we are about to send the farmer the code (along with a hefty invoice), she calls and tells us she’s also started keeping pigs, and couldn’t we please extend the software to also print pigs?

- -

We sure can. But just as we’re in the process of copying and pasting those four lines one more time, we stop and reconsider. There has to be a better way. Here’s a first attempt:

- -
function printZeroPaddedWithLabel(number, label) {
-  let numberString = String(number);
-  while (numberString.length < 3) {
-    numberString = "0" + numberString;
-  }
-  console.log(`${numberString} ${label}`);
-}
-
-function printFarmInventory(cows, chickens, pigs) {
-  printZeroPaddedWithLabel(cows, "Cows");
-  printZeroPaddedWithLabel(chickens, "Chickens");
-  printZeroPaddedWithLabel(pigs, "Pigs");
-}
-
-printFarmInventory(7, 11, 3);
- -

It works! But that name, printZeroPaddedWithLabel, is a little awkward. It conflates three things—printing, zero-padding, and adding a label—into a single function.

- -

Instead of lifting out the repeated part of our program wholesale, let’s try to pick out a single concept.

- -
function zeroPad(number, width) {
-  let string = String(number);
-  while (string.length < width) {
-    string = "0" + string;
-  }
-  return string;
-}
-
-function printFarmInventory(cows, chickens, pigs) {
-  console.log(`${zeroPad(cows, 3)} Cows`);
-  console.log(`${zeroPad(chickens, 3)} Chickens`);
-  console.log(`${zeroPad(pigs, 3)} Pigs`);
-}
-
-printFarmInventory(7, 16, 3);
- -

A function with a nice, obvious name like zeroPad makes it easier for someone who reads the code to figure out what it does. And such a function is useful in more situations than just this specific program. For example, you could use it to help print nicely aligned tables of numbers.

- -

How smart and versatile should our function be? We could write anything, from a terribly simple function that can only pad a number to be three characters wide to a complicated generalized number-formatting system that handles fractional numbers, negative numbers, alignment of decimal dots, padding with different characters, and so on.

- -

A useful principle is to not add cleverness unless you are absolutely sure you’re going to need it. It can be tempting to write general “frameworks” for every bit of functionality you come across. Resist that urge. You won’t get any real work done—you’ll just be writing code that you never use.

- -

Functions and side effects

- -

Functions can be roughly divided into those that are called for their side effects and those that are called for their return value. (Though it is definitely also possible to both have side effects and return a value.)

- -

The first helper function in the farm example, printZeroPaddedWithLabel, is called for its side effect: it prints a line. The second version, zeroPad, is called for its return value. It is no coincidence that the second is useful in more situations than the first. Functions that create values are easier to combine in new ways than functions that directly perform side effects.

- -

A pure function is a specific kind of value-producing function that not only has no side effects but also doesn’t rely on side effects from other code—for example, it doesn’t read global bindings whose value might change. A pure function has the pleasant property that, when called with the same arguments, it always produces the same value (and doesn’t do anything else). A call to such a function can be substituted by its return value without changing the meaning of the code. When you are not sure that a pure function is working correctly, you can test it by simply calling it and know that if it works in that context, it will work in any context. Nonpure functions tend to require more scaffolding to test.

- -

Still, there’s no need to feel bad when writing functions that are not pure or to wage a holy war to purge them from your code. Side effects are often useful. There’d be no way to write a pure version of console.log, for example, and console.log is good to have. Some operations are also easier to express in an efficient way when we use side effects, so computing speed can be a reason to avoid purity.

- -

Summary

- -

This chapter taught you how to write your own functions. The function keyword, when used as an expression, can create a function value. When used as a statement, it can be used to declare a binding and give it a function as its value. Arrow functions are yet another way to create functions.

- -
// Define f to hold a function value
-const f = function(a) {
-  console.log(a + 2);
-};
-
-// Declare g to be a function
-function g(a, b) {
-  return a * b * 3.5;
-}
-
-// A less verbose function value
-let h = a => a % 3;
- -

A key aspect in understanding functions is understanding scopes. Each block creates a new scope. Parameters and bindings declared in a given scope are local and not visible from the outside. Bindings declared with var behave differently—they end up in the nearest function scope or the global scope.

- -

Separating the tasks your program performs into different functions is helpful. You won’t have to repeat yourself as much, and functions can help organize a program by grouping code into pieces that do specific things.

- -

Exercises

- -

Minimum

- -

The previous chapter introduced the standard function Math.min that returns its smallest argument. We can build something like that now. Write a function min that takes two arguments and returns their minimum.

- -
// Your code here.
-
-console.log(min(0, 10));
-// → 0
-console.log(min(0, -10));
-// → -10
- -
- -

If you have trouble putting braces and parentheses in the right place to get a valid function definition, start by copying one of the examples in this chapter and modifying it.

- -

A function may contain multiple return statements.

- -
- -

Recursion

- -

We’ve seen that % (the remainder operator) can be used to test whether a number is even or odd by using % 2 to see whether it’s divisible by two. Here’s another way to define whether a positive whole number is even or odd:

- -
    - -
  • - -

    Zero is even.

  • - -
  • - -

    One is odd.

  • - -
  • - -

    For any other number N, its evenness is the same as N - 2.

- -

Define a recursive function isEven corresponding to this description. The function should accept a single parameter (a positive, whole number) and return a Boolean.

- -

Test it on 50 and 75. See how it behaves on -1. Why? Can you think of a way to fix this?

- -
// Your code here.
-
-console.log(isEven(50));
-// → true
-console.log(isEven(75));
-// → false
-console.log(isEven(-1));
-// → ??
- -
- -

Your function will likely look somewhat similar to the inner find function in the recursive findSolution example in this chapter, with an if/else if/else chain that tests which of the three cases applies. The final else, corresponding to the third case, makes the recursive call. Each of the branches should contain a return statement or in some other way arrange for a specific value to be returned.

- -

When given a negative number, the function will recurse again and again, passing itself an ever more negative number, thus getting further and further away from returning a result. It will eventually run out of stack space and abort.

- -
- -

Bean counting

- -

You can get the Nth character, or letter, from a string by writing "string"[N]. The returned value will be a string containing only one character (for example, "b"). The first character has position 0, which causes the last one to be found at position string.length - 1. In other words, a two-character string has length 2, and its characters have positions 0 and 1.

- -

Write a function countBs that takes a string as its only argument and returns a number that indicates how many uppercase “B” characters there are in the string.

- -

Next, write a function called countChar that behaves like countBs, except it takes a second argument that indicates the character that is to be counted (rather than counting only uppercase “B” characters). Rewrite countBs to make use of this new function.

- -
// Your code here.
-
-console.log(countBs("BBC"));
-// → 2
-console.log(countChar("kakkerlak", "k"));
-// → 4
- -
- -

Your function will need a loop that looks at every character in the string. It can run an index from zero to one below its length (< string.length). If the character at the current position is the same as the one the function is looking for, it adds 1 to a counter variable. Once the loop has finished, the counter can be returned.

- -

Take care to make all the bindings used in the function local to the function by properly declaring them with the let or const keyword.

- -
-
diff --git a/docs/04_data.html b/docs/04_data.html deleted file mode 100644 index 2d2e07ffc..000000000 --- a/docs/04_data.html +++ /dev/null @@ -1,794 +0,0 @@ - - - - - Data Structures: Objects and Arrays :: Eloquent JavaScript - - - - - - -
- - -

Chapter 4Data Structures: Objects and Arrays

- -
- -

On two occasions I have been asked, ‘Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?’ [...] I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.

- -
Charles Babbage, Passages from the Life of a Philosopher (1864)
- -
Picture of a weresquirrel
- -

Numbers, Booleans, and strings are the atoms that data structures are built from. Many types of information require more than one atom, though. Objects allow us to group values—including other objects—to build more complex structures.

- -

The programs we have built so far have been limited by the fact that they were operating only on simple data types. This chapter will introduce basic data structures. By the end of it, you’ll know enough to start writing useful programs.

- -

The chapter will work through a more or less realistic programming example, introducing concepts as they apply to the problem at hand. The example code will often build on functions and bindings that were introduced earlier in the text.

- -

The weresquirrel

- -

Every now and then, usually between 8 p.m. and 10 p.m., Jacques finds himself transforming into a small furry rodent with a bushy tail.

- -

On one hand, Jacques is quite glad that he doesn’t have classic lycanthropy. Turning into a squirrel does cause fewer problems than turning into a wolf. Instead of having to worry about accidentally eating the neighbor (that would be awkward), he worries about being eaten by the neighbor’s cat. After two occasions where he woke up on a precariously thin branch in the crown of an oak, naked and disoriented, he has taken to locking the doors and windows of his room at night and putting a few walnuts on the floor to keep himself busy.

- -

That takes care of the cat and tree problems. But Jacques would prefer to get rid of his condition entirely. The irregular occurrences of the transformation make him suspect that they might be triggered by something. For a while, he believed that it happened only on days when he had been near oak trees. But avoiding oak trees did not stop the problem.

- -

Switching to a more scientific approach, Jacques has started keeping a daily log of everything he does on a given day and whether he changed form. With this data he hopes to narrow down the conditions that trigger the transformations.

- -

The first thing he needs is a data structure to store this information.

- -

Data sets

- -

To work with a chunk of digital data, we’ll first have to find a way to represent it in our machine’s memory. Say, for example, that we want to represent a collection of the numbers 2, 3, 5, 7, and 11.

- -

We could get creative with strings—after all, strings can have any length, so we can put a lot of data into them—and use "2 3 5 7 11" as our representation. But this is awkward. You’d have to somehow extract the digits and convert them back to numbers to access them.

- -

Fortunately, JavaScript provides a data type specifically for storing sequences of values. It is called an array and is written as a list of values between square brackets, separated by commas.

- -
let listOfNumbers = [2, 3, 5, 7, 11];
-console.log(listOfNumbers[2]);
-// → 5
-console.log(listOfNumbers[0]);
-// → 2
-console.log(listOfNumbers[2 - 1]);
-// → 3
- -

The notation for getting at the elements inside an array also uses square brackets. A pair of square brackets immediately after an expression, with another expression inside of them, will look up the element in the left-hand expression that corresponds to the index given by the expression in the brackets.

- -

The first index of an array is zero, not one. So the first element is retrieved with listOfNumbers[0]. Zero-based counting has a long tradition in technology and in certain ways makes a lot of sense, but it takes some getting used to. Think of the index as the amount of items to skip, counting from the start of the array.

- -

Properties

- -

We’ve seen a few suspicious-looking expressions like myString.length (to get the length of a string) and Math.max (the maximum function) in past chapters. These are expressions that access a property of some value. In the first case, we access the length property of the value in myString. In the second, we access the property named max in the Math object (which is a collection of mathematics-related constants and functions).

- -

Almost all JavaScript values have properties. The exceptions are null and undefined. If you try to access a property on one of these nonvalues, you get an error.

- -
null.length;
-// → TypeError: null has no properties
- -

The two main ways to access properties in JavaScript are with a dot and with square brackets. Both value.x and value[x] access a property on value—but not necessarily the same property. The difference is in how x is interpreted. When using a dot, the word after the dot is the literal name of the property. When using square brackets, the expression between the brackets is evaluated to get the property name. Whereas value.x fetches the property of value named “x”, value[x] tries to evaluate the expression x and uses the result, converted to a string, as the property name.

- -

So if you know that the property you are interested in is called color, you say value.color. If you want to extract the property named by the value held in the binding i, you say value[i]. Property names are strings. They can be any string, but the dot notation works only with names that look like valid binding names. So if you want to access a property named 2 or John Doe, you must use square brackets: value[2] or value["John Doe"].

- -

The elements in an array are stored as the array’s properties, using numbers as property names. Because you can’t use the dot notation with numbers and usually want to use a binding that holds the index anyway, you have to use the bracket notation to get at them.

- -

The length property of an array tells us how many elements it has. This property name is a valid binding name, and we know its name in advance, so to find the length of an array, you typically write array.length because that’s easier to write than array["length"].

- -

Methods

- -

Both string and array objects contain, in addition to the length property, a number of properties that hold function values.

- -
let doh = "Doh";
-console.log(typeof doh.toUpperCase);
-// → function
-console.log(doh.toUpperCase());
-// → DOH
- -

Every string has a toUpperCase property. When called, it will return a copy of the string in which all letters have been converted to uppercase. There is also toLowerCase, going the other way.

- -

Interestingly, even though the call to toUpperCase does not pass any arguments, the function somehow has access to the string "Doh", the value whose property we called. How this works is described in Chapter 6.

- -

Properties that contain functions are generally called methods of the value they belong to, as in “toUpperCase is a method of a string”.

- -

This example demonstrates two methods you can use to manipulate arrays:

- -
let sequence = [1, 2, 3];
-sequence.push(4);
-sequence.push(5);
-console.log(sequence);
-// → [1, 2, 3, 4, 5]
-console.log(sequence.pop());
-// → 5
-console.log(sequence);
-// → [1, 2, 3, 4]
- -

The push method adds values to the end of an array, and the pop method does the opposite, removing the last value in the array and returning it.

- -

These somewhat silly names are the traditional terms for operations on a stack. A stack, in programming, is a data structure that allows you to push values into it and pop them out again in the opposite order so that the thing that was added last is removed first. These are common in programming—you might remember the function call -stack from the previous chapter, which is an instance of the same idea.

- -

Objects

- -

Back to the weresquirrel. A set of daily log entries can be represented as an array. But the entries do not consist of just a number or a string—each entry needs to store a list of activities and a Boolean value that indicates whether Jacques turned into a squirrel or not. Ideally, we would like to group these together into a single value and then put those grouped values into an array of log entries.

- -

Values of the type object are arbitrary collections of properties. One way to create an object is by using braces as an expression.

- -
let day1 = {
-  squirrel: false,
-  events: ["work", "touched tree", "pizza", "running"]
-};
-console.log(day1.squirrel);
-// → false
-console.log(day1.wolf);
-// → undefined
-day1.wolf = false;
-console.log(day1.wolf);
-// → false
- -

Inside the braces, there is a list of properties separated by commas. Each property has a name followed by a colon and a value. When an object is written over multiple lines, indenting it like in the example helps with readability. Properties whose names aren’t valid binding names or valid numbers have to be quoted.

- -
let descriptions = {
-  work: "Went to work",
-  "touched tree": "Touched a tree"
-};
- -

This means that braces have two meanings in JavaScript. At the start of a statement, they start a block of statements. In any other position, they describe an object. Fortunately, it is rarely useful to start a statement with an object in braces, so the ambiguity between these two is not much of a problem.

- -

Reading a property that doesn’t exist will give you the value undefined.

- -

It is possible to assign a value to a property expression with the = operator. This will replace the property’s value if it already existed or create a new property on the object if it didn’t.

- -

To briefly return to our tentacle model of bindings—property bindings are similar. They grasp values, but other bindings and properties might be holding onto those same values. You may think of objects as octopuses with any number of tentacles, each of which has a name tattooed on it.

- -

The delete operator cuts off a tentacle from such an octopus. It is a unary operator that, when applied to an object property, will remove the named property from the object. This is not a common thing to do, but it is possible.

- -
let anObject = {left: 1, right: 2};
-console.log(anObject.left);
-// → 1
-delete anObject.left;
-console.log(anObject.left);
-// → undefined
-console.log("left" in anObject);
-// → false
-console.log("right" in anObject);
-// → true
- -

The binary in operator, when applied to a string and an object, tells you whether that object has a property with that name. The difference between setting a property to undefined and actually deleting it is that, in the first case, the object still has the property (it just doesn’t have a very interesting value), whereas in the second case the property is no longer present and in will return false.

- -

To find out what properties an object has, you can use the Object.keys function. You give it an object, and it returns an array of strings—the object’s property names.

- -
console.log(Object.keys({x: 0, y: 0, z: 2}));
-// → ["x", "y", "z"]
- -

There’s an Object.assign function that copies all properties from one object into another.

- -
let objectA = {a: 1, b: 2};
-Object.assign(objectA, {b: 3, c: 4});
-console.log(objectA);
-// → {a: 1, b: 3, c: 4}
- -

Arrays, then, are just a kind of object specialized for storing sequences of things. If you evaluate typeof [], it produces "object". You can see them as long, flat octopuses with all their tentacles in a neat row, labeled with numbers.

- -

We will represent the journal that Jacques keeps as an array of objects.

- -
let journal = [
-  {events: ["work", "touched tree", "pizza",
-            "running", "television"],
-   squirrel: false},
-  {events: ["work", "ice cream", "cauliflower",
-            "lasagna", "touched tree", "brushed teeth"],
-   squirrel: false},
-  {events: ["weekend", "cycling", "break", "peanuts",
-            "beer"],
-   squirrel: true},
-  /* and so on... */
-];
- -

Mutability

- -

We will get to actual programming real soon now. First there’s one more piece of theory to understand.

- -

We saw that object values can be modified. The types of values discussed in earlier chapters, such as numbers, strings, and Booleans, are all immutable—it is impossible to change values of those types. You can combine them and derive new values from them, but when you take a specific string value, that value will always remain the same. The text inside it cannot be changed. If you have a string that contains "cat", it is not possible for other code to change a character in your string to make it spell "rat".

- -

Objects work differently. You can change their properties, causing a single object value to have different content at different times.

- -

When we have two numbers, 120 and 120, we can consider them precisely the same number, whether or not they refer to the same physical bits. With objects, there is a difference between having two references to the same object and having two different objects that contain the same properties. Consider the following code:

- -
let object1 = {value: 10};
-let object2 = object1;
-let object3 = {value: 10};
-
-console.log(object1 == object2);
-// → true
-console.log(object1 == object3);
-// → false
-
-object1.value = 15;
-console.log(object2.value);
-// → 15
-console.log(object3.value);
-// → 10
- -

The object1 and object2 bindings grasp the same object, which is why changing object1 also changes the value of object2. They are said to have the same identity. The binding object3 points to a different object, which initially contains the same properties as object1 but lives a separate life.

- -

Bindings can also be changeable or constant, but this is separate from the way their values behave. Even though number values don’t change, you can use a let binding to keep track of a changing number by changing the value the binding points at. Similarly, though a const binding to an object can itself not be changed and will continue to point at the same object, the contents of that object might change.

- -
const score = {visitors: 0, home: 0};
-// This is okay
-score.visitors = 1;
-// This isn't allowed
-score = {visitors: 1, home: 1};
- -

When you compare objects with JavaScript’s == operator, it compares by identity: it will produce true only if both objects are precisely the same value. Comparing different objects will return false, even if they have identical properties. There is no “deep” comparison operation built into JavaScript, which compares objects by contents, but it is possible to write it yourself (which is one of the exercises at the end of this chapter).

- -

The lycanthrope’s log

- -

So, Jacques starts up his JavaScript interpreter and sets up the environment he needs to keep his journal.

- -
let journal = [];
-
-function addEntry(events, squirrel) {
-  journal.push({events, squirrel});
-}
- -

Note that the object added to the journal looks a little odd. Instead of declaring properties like events: events, it just gives a property name. This is shorthand that means the same thing—if a property name in brace notation isn’t followed by a value, its value is taken from the binding with the same name.

- -

So then, every evening at 10 p.m.—or sometimes the next morning, after climbing down from the top shelf of his bookcase—Jacques records the day.

- -
addEntry(["work", "touched tree", "pizza", "running",
-          "television"], false);
-addEntry(["work", "ice cream", "cauliflower", "lasagna",
-          "touched tree", "brushed teeth"], false);
-addEntry(["weekend", "cycling", "break", "peanuts",
-          "beer"], true);
- -

Once he has enough data points, he intends to use statistics to find out which of these events may be related to the squirrelifications.

- -

Correlation is a measure of dependence between statistical variables. A statistical variable is not quite the same as a programming variable. In statistics you typically have a set of measurements, and each variable is measured for every measurement. Correlation between variables is usually expressed as a value that ranges from -1 to 1. Zero correlation means the variables are not related. A correlation of one indicates that the two are perfectly related—if you know one, you also know the other. Negative one also means that the variables are perfectly related but that they are opposites—when one is true, the other is false.

- -

To compute the measure of correlation between two Boolean variables, we can use the phi coefficient (ϕ). This is a formula whose input is a frequency table containing the number of times the different combinations of the variables were observed. The output of the formula is a number between -1 and 1 that describes the correlation.

- -

We could take the event of eating pizza and put that in a frequency table like this, where each number indicates the amount of times that combination occurred in our measurements:

Eating pizza versus turning into a squirrel
- -

If we call that table n, we can compute ϕ using the following formula:

- - - -
ϕ = -
n11n00 − - n10n01
-
- n1•n0•n•1n•0 -
-
-
- - -

(If at this point you’re putting the book down to focus on a terrible flashback to 10th grade math class—hold on! I do not intend to torture you with endless pages of cryptic notation—it’s just this one formula for now. And even with this one, all we do is turn it into JavaScript.)

- -

The notation n01 indicates the number of measurements where the first variable (squirrelness) is false (0) and the second variable (pizza) is true (1). In the pizza table, n01 is 9.

- -

The value n1• refers to the sum of all measurements where the first variable is true, which is 5 in the example table. Likewise, n•0 refers to the sum of the measurements where the second variable is false.

- -

So for the pizza table, the part above the division line (the dividend) would be 1×76−4×9 = 40, and the part below it (the divisor) would be the square root of 5×85×10×80, or √340000. This comes out to ϕ ≈ 0.069, which is tiny. Eating pizza does not appear to have influence on the transformations.

- -

Computing correlation

- -

We can represent a two-by-two table in JavaScript with a four-element array ([76, 9, 4, 1]). We could also use other representations, such as an array containing two two-element arrays ([[76, 9], [4, 1]]) or an object with property names like "11" and "01", but the flat array is simple and makes the expressions that access the table pleasantly short. We’ll interpret the indices to the array as two-bit binary numbers, where the leftmost (most significant) digit refers to the squirrel variable and the rightmost (least significant) digit refers to the event variable. For example, the binary number 10 refers to the case where Jacques did turn into a squirrel, but the event (say, “pizza”) didn’t occur. This happened four times. And since binary 10 is 2 in decimal notation, we will store this number at index 2 of the array.

- -

This is the function that computes the ϕ coefficient from such an array:

- -
function phi(table) {
-  return (table[3] * table[0] - table[2] * table[1]) /
-    Math.sqrt((table[2] + table[3]) *
-              (table[0] + table[1]) *
-              (table[1] + table[3]) *
-              (table[0] + table[2]));
-}
-
-console.log(phi([76, 9, 4, 1]));
-// → 0.068599434
- -

This is a direct translation of the ϕ formula into JavaScript. Math.sqrt is the square root function, as provided by the Math object in a standard JavaScript environment. We have to add two fields from the table to get fields like n1• because the sums of rows or columns are not stored directly in our data structure.

- -

Jacques kept his journal for three months. The resulting data set is available in the coding sandbox for this chapter, where it is stored in the JOURNAL binding and in a downloadable file.

- -

To extract a two-by-two table for a specific event from the journal, we must loop over all the entries and tally how many times the event occurs in relation to squirrel transformations.

- -
function tableFor(event, journal) {
-  let table = [0, 0, 0, 0];
-  for (let i = 0; i < journal.length; i++) {
-    let entry = journal[i], index = 0;
-    if (entry.events.includes(event)) index += 1;
-    if (entry.squirrel) index += 2;
-    table[index] += 1;
-  }
-  return table;
-}
-
-console.log(tableFor("pizza", JOURNAL));
-// → [76, 9, 4, 1]
- -

Arrays have an includes method that checks whether a given value exists in the array. The function uses that to determine whether the event name it is interested in is part of the event list for a given day.

- -

The body of the loop in tableFor figures out which box in the table each journal entry falls into by checking whether the entry contains the specific event it’s interested in and whether the event happens alongside a squirrel incident. The loop then adds one to the correct box in the table.

- -

We now have the tools we need to compute individual correlations. The only step remaining is to find a correlation for every type of event that was recorded and see whether anything stands out.

- -

Array loops

- -

In the tableFor function, there’s a loop like this:

- -
for (let i = 0; i < JOURNAL.length; i++) {
-  let entry = JOURNAL[i];
-  // Do something with entry
-}
- -

This kind of loop is common in classical JavaScript—going over arrays one element at a time is something that comes up a lot, and to do that you’d run a counter over the length of the array and pick out each element in turn.

- -

There is a simpler way to write such loops in modern JavaScript.

- -
for (let entry of JOURNAL) {
-  console.log(`${entry.events.length} events.`);
-}
- -

When a for loop looks like this, with the word of after a variable definition, it will loop over the elements of the value given after of. This works not only for arrays but also for strings and some other data structures. We’ll discuss how it works in Chapter 6.

- -

The final analysis

- -

We need to compute a correlation for every type of event that occurs in the data set. To do that, we first need to find every type of event.

- -
function journalEvents(journal) {
-  let events = [];
-  for (let entry of journal) {
-    for (let event of entry.events) {
-      if (!events.includes(event)) {
-        events.push(event);
-      }
-    }
-  }
-  return events;
-}
-
-console.log(journalEvents(JOURNAL));
-// → ["carrot", "exercise", "weekend", "bread", …]
- -

By going over all the events and adding those that aren’t already in there to the events array, the function collects every type of event.

- -

Using that, we can see all the correlations.

- -
for (let event of journalEvents(JOURNAL)) {
-  console.log(event + ":", phi(tableFor(event, JOURNAL)));
-}
-// → carrot:   0.0140970969
-// → exercise: 0.0685994341
-// → weekend:  0.1371988681
-// → bread:   -0.0757554019
-// → pudding: -0.0648203724
-// and so on...
- -

Most correlations seem to lie close to zero. Eating carrots, bread, or pudding apparently does not trigger squirrel-lycanthropy. It does seem to occur somewhat more often on weekends. Let’s filter the results to show only correlations greater than 0.1 or less than -0.1.

- -
for (let event of journalEvents(JOURNAL)) {
-  let correlation = phi(tableFor(event, JOURNAL));
-  if (correlation > 0.1 || correlation < -0.1) {
-    console.log(event + ":", correlation);
-  }
-}
-// → weekend:        0.1371988681
-// → brushed teeth: -0.3805211953
-// → candy:          0.1296407447
-// → work:          -0.1371988681
-// → spaghetti:      0.2425356250
-// → reading:        0.1106828054
-// → peanuts:        0.5902679812
- -

Aha! There are two factors with a correlation that’s clearly stronger than the others. Eating peanuts has a strong positive effect on the chance of turning into a squirrel, whereas brushing his teeth has a significant negative effect.

- -

Interesting. Let’s try something.

- -
for (let entry of JOURNAL) {
-  if (entry.events.includes("peanuts") &&
-     !entry.events.includes("brushed teeth")) {
-    entry.events.push("peanut teeth");
-  }
-}
-console.log(phi(tableFor("peanut teeth", JOURNAL)));
-// → 1
- -

That’s a strong result. The phenomenon occurs precisely when Jacques eats peanuts and fails to brush his teeth. If only he weren’t such a slob about dental hygiene, he’d have never even noticed his affliction.

- -

Knowing this, Jacques stops eating peanuts altogether and finds that his transformations don’t come back.

- -

For a few years, things go great for Jacques. But at some point he loses his job. Because he lives in a nasty country where having no job means having no medical services, he is forced to take employment with a circus where he performs as The Incredible Squirrelman, stuffing his mouth with peanut butter before every show.

- -

One day, fed up with this pitiful existence, Jacques fails to change back into his human form, hops through a crack in the circus tent, and vanishes into the forest. He is never seen again.

- -

Further arrayology

- -

Before finishing the chapter, I want to introduce you to a few more object-related concepts. I’ll start by introducing some generally useful array methods.

- -

We saw push and pop, which add and remove elements at the end of an array, earlier in this chapter. The corresponding methods for adding and removing things at the start of an array are called unshift and shift.

- -
let todoList = [];
-function remember(task) {
-  todoList.push(task);
-}
-function getTask() {
-  return todoList.shift();
-}
-function rememberUrgently(task) {
-  todoList.unshift(task);
-}
- -

That program manages a queue of tasks. You add tasks to the end of the queue by calling remember("groceries"), and when you’re ready to do something, you call getTask() to get (and remove) the front item from the queue. The rememberUrgently function also adds a task but adds it to the front instead of the back of the queue.

- -

To search for a specific value, arrays provide an indexOf method. The method searches through the array from the start to the end and returns the index at which the requested value was found—or -1 if it wasn’t found. To search from the end instead of the start, there’s a similar method called lastIndexOf.

- -
console.log([1, 2, 3, 2, 1].indexOf(2));
-// → 1
-console.log([1, 2, 3, 2, 1].lastIndexOf(2));
-// → 3
- -

Both indexOf and lastIndexOf take an optional second argument that indicates where to start searching.

- -

Another fundamental array method is slice, which takes start and end indices and returns an array that has only the elements between them. The start index is inclusive, the end index exclusive.

- -
console.log([0, 1, 2, 3, 4].slice(2, 4));
-// → [2, 3]
-console.log([0, 1, 2, 3, 4].slice(2));
-// → [2, 3, 4]
- -

When the end index is not given, slice will take all of the elements after the start index. You can also omit the start index to copy the entire array.

- -

The concat method can be used to glue arrays together to create a new array, similar to what the + operator does for strings.

- -

The following example shows both concat and slice in action. It takes an array and an index, and it returns a new array that is a copy of the original array with the element at the given index removed.

- -
function remove(array, index) {
-  return array.slice(0, index)
-    .concat(array.slice(index + 1));
-}
-console.log(remove(["a", "b", "c", "d", "e"], 2));
-// → ["a", "b", "d", "e"]
- -

If you pass concat an argument that is not an array, that value will be added to the new array as if it were a one-element array.

- -

Strings and their properties

- -

We can read properties like length and toUpperCase from string values. But if you try to add a new property, it doesn’t stick.

- -
let kim = "Kim";
-kim.age = 88;
-console.log(kim.age);
-// → undefined
- -

Values of type string, number, and Boolean are not objects, and though the language doesn’t complain if you try to set new properties on them, it doesn’t actually store those properties. As mentioned earlier, such values are immutable and cannot be changed.

- -

But these types do have built-in properties. Every string value has a number of methods. Some very useful ones are slice and indexOf, which resemble the array methods of the same name.

- -
console.log("coconuts".slice(4, 7));
-// → nut
-console.log("coconut".indexOf("u"));
-// → 5
- -

One difference is that a string’s indexOf can search for a string containing more than one character, whereas the corresponding array method looks only for a single element.

- -
console.log("one two three".indexOf("ee"));
-// → 11
- -

The trim method removes whitespace (spaces, newlines, tabs, and similar characters) from the start and end of a string.

- -
console.log("  okay \n ".trim());
-// → okay
- -

The zeroPad function from the previous chapter also exists as a method. It is called padStart and takes the desired length and padding character as arguments.

- -
console.log(String(6).padStart(3, "0"));
-// → 006
- -

You can split a string on every occurrence of another string with split and join it again with join.

- -
let sentence = "Secretarybirds specialize in stomping";
-let words = sentence.split(" ");
-console.log(words);
-// → ["Secretarybirds", "specialize", "in", "stomping"]
-console.log(words.join(". "));
-// → Secretarybirds. specialize. in. stomping
- -

A string can be repeated with the repeat method, which creates a new string containing multiple copies of the original string, glued together.

- -
console.log("LA".repeat(3));
-// → LALALA
- -

We have already seen the string type’s length property. Accessing the individual characters in a string looks like accessing array elements (with a caveat that we’ll discuss in Chapter 5).

- -
let string = "abc";
-console.log(string.length);
-// → 3
-console.log(string[1]);
-// → b
- -

Rest parameters

- -

It can be useful for a function to accept any number of arguments. For example, Math.max computes the maximum of all the arguments it is given.

- -

To write such a function, you put three dots before the function’s last parameter, like this:

- -
function max(...numbers) {
-  let result = -Infinity;
-  for (let number of numbers) {
-    if (number > result) result = number;
-  }
-  return result;
-}
-console.log(max(4, 1, 9, -2));
-// → 9
- -

When such a function is called, the rest parameter is bound to an array containing all further arguments. If there are other parameters before it, their values aren’t part of that array. When, as in max, it is the only parameter, it will hold all arguments.

- -

You can use a similar three-dot notation to call a function with an array of arguments.

- -
let numbers = [5, 1, 7];
-console.log(max(...numbers));
-// → 7
- -

This “spreads” out the array into the function call, passing its elements as separate arguments. It is possible to include an array like that along with other arguments, as in max(9, ...numbers, 2).

- -

Square bracket array notation similarly allows the triple-dot operator to spread another array into the new array.

- -
let words = ["never", "fully"];
-console.log(["will", ...words, "understand"]);
-// → ["will", "never", "fully", "understand"]
- -

The Math object

- -

As we’ve seen, Math is a grab bag of number-related utility functions, such as Math.max (maximum), Math.min (minimum), and Math.sqrt (square root).

- -

The Math object is used as a container to group a bunch of related functionality. There is only one Math object, and it is almost never useful as a value. Rather, it provides a namespace so that all these functions and values do not have to be global bindings.

- -

Having too many global bindings “pollutes” the namespace. The more names have been taken, the more likely you are to accidentally overwrite the value of some existing binding. For example, it’s not unlikely to want to name something max in one of your programs. Since JavaScript’s built-in max function is tucked safely inside the Math object, we don’t have to worry about overwriting it.

- -

Many languages will stop you, or at least warn you, when you are defining a binding with a name that is already taken. JavaScript does this for bindings you declared with let or const but—perversely—not for standard bindings nor for bindings declared with var or function.

- -

Back to the Math object. If you need to do trigonometry, Math can help. It contains cos (cosine), sin (sine), and tan (tangent), as well as their inverse functions, acos, asin, and atan, respectively. The number π (pi)—or at least the closest approximation that fits in a JavaScript number—is available as Math.PI. There is an old programming tradition of writing the names of constant values in all caps.

- -
function randomPointOnCircle(radius) {
-  let angle = Math.random() * 2 * Math.PI;
-  return {x: radius * Math.cos(angle),
-          y: radius * Math.sin(angle)};
-}
-console.log(randomPointOnCircle(2));
-// → {x: 0.3667, y: 1.966}
- -

If sines and cosines are not something you are familiar with, don’t worry. When they are used in this book, in Chapter 14, I’ll explain them.

- -

The previous example used Math.random. This is a function that returns a new pseudorandom number between zero (inclusive) and one (exclusive) every time you call it.

- -
console.log(Math.random());
-// → 0.36993729369714856
-console.log(Math.random());
-// → 0.727367032552138
-console.log(Math.random());
-// → 0.40180766698904335
- -

Though computers are deterministic machines—they always react the same way if given the same input—it is possible to have them produce numbers that appear random. To do that, the machine keeps some hidden value, and whenever you ask for a new random number, it performs complicated computations on this hidden value to create a new value. It stores a new value and returns some number derived from it. That way, it can produce ever new, hard-to-predict numbers in a way that seems random.

- -

If we want a whole random number instead of a fractional one, we can use Math.floor (which rounds down to the nearest whole number) on the result of Math.random.

- -
console.log(Math.floor(Math.random() * 10));
-// → 2
- -

Multiplying the random number by 10 gives us a number greater than or equal to 0 and below 10. Since Math.floor rounds down, this expression will produce, with equal chance, any number from 0 through 9.

- -

There are also the functions Math.ceil (for “ceiling”, which rounds up to a whole number), Math.round (to the nearest whole number), and Math.abs, which takes the absolute value of a number, meaning it negates negative values but leaves positive ones as they are.

- -

Destructuring

- -

Let’s go back to the phi function for a moment.

- -
function phi(table) {
-  return (table[3] * table[0] - table[2] * table[1]) /
-    Math.sqrt((table[2] + table[3]) *
-              (table[0] + table[1]) *
-              (table[1] + table[3]) *
-              (table[0] + table[2]));
-}
- -

One of the reasons this function is awkward to read is that we have a binding pointing at our array, but we’d much prefer to have bindings for the elements of the array, that is, let n00 = table[0] and so on. Fortunately, there is a succinct way to do this in JavaScript.

- -
function phi([n00, n01, n10, n11]) {
-  return (n11 * n00 - n10 * n01) /
-    Math.sqrt((n10 + n11) * (n00 + n01) *
-              (n01 + n11) * (n00 + n10));
-}
- -

This also works for bindings created with let, var, or const. If you know the value you are binding is an array, you can use square brackets to “look inside” of the value, binding its contents.

- -

A similar trick works for objects, using braces instead of square brackets.

- -
let {name} = {name: "Faraji", age: 23};
-console.log(name);
-// → Faraji
- -

Note that if you try to destructure null or undefined, you get an error, much as you would if you directly try to access a property of those values.

- -

JSON

- -

Because properties only grasp their value, rather than contain it, objects and arrays are stored in the computer’s memory as sequences of bits holding the addresses—the place in memory—of their contents. So an array with another array inside of it consists of (at least) one memory region for the inner array, and another for the outer array, containing (among other things) a binary number that represents the position of the inner array.

- -

If you want to save data in a file for later or send it to another computer over the network, you have to somehow convert these tangles of memory addresses to a description that can be stored or sent. You could send over your entire computer memory along with the address of the value you’re interested in, I suppose, but that doesn’t seem like the best approach.

- -

What we can do is serialize the data. That means it is converted into a flat description. A popular serialization format is called JSON (pronounced “Jason”), which stands for JavaScript Object Notation. It is widely used as a data storage and communication format on the Web, even in languages other than JavaScript.

- -

JSON looks similar to JavaScript’s way of writing arrays and objects, with a few restrictions. All property names have to be surrounded by double quotes, and only simple data expressions are allowed—no function calls, bindings, or anything that involves actual computation. Comments are not allowed in JSON.

- -

A journal entry might look like this when represented as JSON data:

- -
{
-  "squirrel": false,
-  "events": ["work", "touched tree", "pizza", "running"]
-}
- -

JavaScript gives us the functions JSON.stringify and JSON.parse to convert data to and from this format. The first takes a JavaScript value and returns a JSON-encoded string. The second takes such a string and converts it to the value it encodes.

- -
let string = JSON.stringify({squirrel: false,
-                             events: ["weekend"]});
-console.log(string);
-// → {"squirrel":false,"events":["weekend"]}
-console.log(JSON.parse(string).events);
-// → ["weekend"]
- -

Summary

- -

Objects and arrays (which are a specific kind of object) provide ways to group several values into a single value. Conceptually, this allows us to put a bunch of related things in a bag and run around with the bag, instead of wrapping our arms around all of the individual things and trying to hold on to them separately.

- -

Most values in JavaScript have properties, the exceptions being null and undefined. Properties are accessed using value.prop or value["prop"]. Objects tend to use names for their properties and store more or less a fixed set of them. Arrays, on the other hand, usually contain varying amounts of conceptually identical values and use numbers (starting from 0) as the names of their properties.

- -

There are some named properties in arrays, such as length and a number of methods. Methods are functions that live in properties and (usually) act on the value they are a property of.

- -

You can iterate over arrays using a special kind of for loop—for (let element of array).

- -

Exercises

- -

The sum of a range

- -

The introduction of this book alluded to the following as a nice way to compute the sum of a range of numbers:

- -
console.log(sum(range(1, 10)));
- -

Write a range function that takes two arguments, start and end, and returns an array containing all the numbers from start up to (and including) end.

- -

Next, write a sum function that takes an array of numbers and returns the sum of these numbers. Run the example program and see whether it does indeed return 55.

- -

As a bonus assignment, modify your range function to take an optional third argument that indicates the “step” value used when building the array. If no step is given, the elements go up by increments of one, corresponding to the old behavior. The function call range(1, 10, 2) should return [1, 3, 5, 7, 9]. Make sure it also works with negative step values so that range(5, 2, -1) produces [5, 4, 3, 2].

- -
// Your code here.
-
-console.log(range(1, 10));
-// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-console.log(range(5, 2, -1));
-// → [5, 4, 3, 2]
-console.log(sum(range(1, 10)));
-// → 55
- -
- -

Building up an array is most easily done by first initializing a binding to [] (a fresh, empty array) and repeatedly calling its push method to add a value. Don’t forget to return the array at the end of the function.

- -

Since the end boundary is inclusive, you’ll need to use the <= operator rather than < to check for the end of your loop.

- -

The step parameter can be an optional parameter that defaults (using the = operator) to 1.

- -

Having range understand negative step values is probably best done by writing two separate loops—one for counting up and one for counting down—because the comparison that checks whether the loop is finished needs to be >= rather than <= when counting downward.

- -

It might also be worthwhile to use a different default step, namely, -1, when the end of the range is smaller than the start. That way, range(5, 2) returns something meaningful, rather than getting stuck in an infinite loop. It is possible to refer to previous parameters in the default value of a parameter.

- -
- -

Reversing an array

- -

Arrays have a reverse method that changes the array by inverting the order in which its elements appear. For this exercise, write two functions, reverseArray and reverseArrayInPlace. The first, reverseArray, takes an array as argument and produces a new array that has the same elements in the inverse order. The second, reverseArrayInPlace, does what the reverse method does: it modifies the array given as argument by reversing its elements. Neither may use the standard reverse method.

- -

Thinking back to the notes about side effects and pure functions in the previous chapter, which variant do you expect to be useful in more situations? Which one runs faster?

- -
// Your code here.
-
-console.log(reverseArray(["A", "B", "C"]));
-// → ["C", "B", "A"];
-let arrayValue = [1, 2, 3, 4, 5];
-reverseArrayInPlace(arrayValue);
-console.log(arrayValue);
-// → [5, 4, 3, 2, 1]
- -
- -

There are two obvious ways to implement reverseArray. The first is to simply go over the input array from front to back and use the unshift method on the new array to insert each element at its start. The second is to loop over the input array backwards and use the push method. Iterating over an array backwards requires a (somewhat awkward) for specification, like (let i = array.length - 1; i >= 0; i--).

- -

Reversing the array in place is harder. You have to be careful not to overwrite elements that you will later need. Using reverseArray or otherwise copying the whole array (array.slice(0) is a good way to copy an array) works but is cheating.

- -

The trick is to swap the first and last elements, then the second and second-to-last, and so on. You can do this by looping over half the length of the array (use Math.floor to round down—you don’t need to touch the middle element in an array with an odd number of elements) and swapping the element at position i with the one at position array.length - 1 - i. You can use a local binding to briefly hold on to one of the elements, overwrite that one with its mirror image, and then put the value from the local binding in the place where the mirror image used to be.

- -
- -

A list

- -

Objects, as generic blobs of values, can be used to build all sorts of data structures. A common data structure is the list (not to be confused with array). A list is a nested set of objects, with the first object holding a reference to the second, the second to the third, and so on.

- -
let list = {
-  value: 1,
-  rest: {
-    value: 2,
-    rest: {
-      value: 3,
-      rest: null
-    }
-  }
-};
- -

The resulting objects form a chain, like this:

A linked list
- -

A nice thing about lists is that they can share parts of their structure. For example, if I create two new values {value: 0, rest: list} and {value: -1, rest: list} (with list referring to the binding defined earlier), they are both independent lists, but they share the structure that makes up their last three elements. The original list is also still a valid three-element list.

- -

Write a function arrayToList that builds up a list structure like the one shown when given [1, 2, 3] as argument. Also write a listToArray function that produces an array from a list. Then add a helper function prepend, which takes an element and a list and creates a new list that adds the element to the front of the input list, and nth, which takes a list and a number and returns the element at the given position in the list (with zero referring to the first element) or undefined when there is no such element.

- -

If you haven’t already, also write a recursive version of nth.

- -
// Your code here.
-
-console.log(arrayToList([10, 20]));
-// → {value: 10, rest: {value: 20, rest: null}}
-console.log(listToArray(arrayToList([10, 20, 30])));
-// → [10, 20, 30]
-console.log(prepend(10, prepend(20, null)));
-// → {value: 10, rest: {value: 20, rest: null}}
-console.log(nth(arrayToList([10, 20, 30]), 1));
-// → 20
- -
- -

Building up a list is easier when done back to front. So arrayToList could iterate over the array backwards (see the previous exercise) and, for each element, add an object to the list. You can use a local binding to hold the part of the list that was built so far and use an assignment like list = {value: X, rest: list} to add an element.

- -

To run over a list (in listToArray and nth), a for loop specification like this can be used:

- -
for (let node = list; node; node = node.rest) {}
- -

Can you see how that works? Every iteration of the loop, node points to the current sublist, and the body can read its value property to get the current element. At the end of an iteration, node moves to the next sublist. When that is null, we have reached the end of the list, and the loop is finished.

- -

The recursive version of nth will, similarly, look at an ever smaller part of the “tail” of the list and at the same time count down the index until it reaches zero, at which point it can return the value property of the node it is looking at. To get the zeroth element of a list, you simply take the value property of its head node. To get element N + 1, you take the Nth element of the list that’s in this list’s rest property.

- -
- -

Deep comparison

- -

The == operator compares objects by identity. But sometimes you’d prefer to compare the values of their actual properties.

- -

Write a function deepEqual that takes two values and returns true only if they are the same value or are objects with the same properties, where the values of the properties are equal when compared with a recursive call to deepEqual.

- -

To find out whether values should be compared directly (use the === operator for that) or have their properties compared, you can use the typeof operator. If it produces "object" for both values, you should do a deep comparison. But you have to take one silly exception into account: because of a historical accident, typeof null also produces "object".

- -

The Object.keys function will be useful when you need to go over the properties of objects to compare them.

- -
// Your code here.
-
-let obj = {here: {is: "an"}, object: 2};
-console.log(deepEqual(obj, obj));
-// → true
-console.log(deepEqual(obj, {here: 1, object: 2}));
-// → false
-console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
-// → true
- -
- -

Your test for whether you are dealing with a real object will look something like typeof x == "object" && x != null. Be careful to compare properties only when both arguments are objects. In all other cases you can just immediately return the result of applying ===.

- -

Use Object.keys to go over the properties. You need to test whether both objects have the same set of property names and whether those properties have identical values. One way to do that is to ensure that both objects have the same number of properties (the lengths of the property lists are the same). And then, when looping over one of the object’s properties to compare them, always first make sure the other actually has a property by that name. If they have the same number of properties and all properties in one also exist in the other, they have the same set of property names.

- -

Returning the correct value from the function is best done by immediately returning false when a mismatch is found and returning true at the end of the function.

- -
-
diff --git a/docs/05_higher_order.html b/docs/05_higher_order.html deleted file mode 100644 index 966bd9b43..000000000 --- a/docs/05_higher_order.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - - Higher-Order Functions :: Eloquent JavaScript - - - - - - -
- - -

Chapter 5Higher-Order Functions

- -
- -

Tzu-li and Tzu-ssu were boasting about the size of their latest programs. ‘Two-hundred thousand lines,’ said Tzu-li, ‘not counting comments!’ Tzu-ssu responded, ‘Pssh, mine is almost a million lines already.’ Master Yuan-Ma said, ‘My best program has five hundred lines.’ Hearing this, Tzu-li and Tzu-ssu were enlightened.

- -
Master Yuan-Ma, The Book of Programming
- -
- -
- -

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.

- -
C.A.R. Hoare, 1980 ACM Turing Award Lecture
- -
Letters from different scripts
- -

A large program is a costly program, and not just because of the time it takes to build. Size almost always involves complexity, and complexity confuses programmers. Confused programmers, in turn, introduce mistakes (bugs) into programs. A large program then provides a lot of space for these bugs to hide, making them hard to find.

- -

Let’s briefly go back to the final two example programs in the introduction. The first is self-contained and six lines long.

- -
let total = 0, count = 1;
-while (count <= 10) {
-  total += count;
-  count += 1;
-}
-console.log(total);
- -

The second relies on two external functions and is one line long.

- -
console.log(sum(range(1, 10)));
- -

Which one is more likely to contain a bug?

- -

If we count the size of the definitions of sum and range, the second program is also big—even bigger than the first. But still, I’d argue that it is more likely to be correct.

- -

It is more likely to be correct because the solution is expressed in a vocabulary that corresponds to the problem being solved. Summing a range of numbers isn’t about loops and counters. It is about ranges and sums.

- -

The definitions of this vocabulary (the functions sum and range) will still involve loops, counters, and other incidental details. But because they are expressing simpler concepts than the program as a whole, they are easier to get right.

- -

Abstraction

- -

In the context of programming, these kinds of vocabularies are usually called abstractions. Abstractions hide details and give us the ability to talk about problems at a higher (or more abstract) level.

- -

As an analogy, compare these two recipes for pea soup. The first one goes like this:

- -
- -

Put 1 cup of dried peas per person into a container. Add water until the peas are well covered. Leave the peas in water for at least 12 hours. Take the peas out of the water and put them in a cooking pan. Add 4 cups of water per person. Cover the pan and keep the peas simmering for two hours. Take half an onion per person. Cut it into pieces with a knife. Add it to the peas. Take a stalk of celery per person. Cut it into pieces with a knife. Add it to the peas. Take a carrot per person. Cut it into pieces. With a knife! Add it to the peas. Cook for 10 more minutes.

- -
- -

And this is the second recipe:

- -
- -

Per person: 1 cup dried split peas, half a chopped onion, a stalk of celery, and a carrot.

- -

Soak peas for 12 hours. Simmer for 2 hours in 4 cups of water (per person). Chop and add vegetables. Cook for 10 more minutes.

- -
- -

The second is shorter and easier to interpret. But you do need to understand a few more cooking-related words such as soak, simmer, chop, and, I guess, vegetable.

- -

When programming, we can’t rely on all the words we need to be waiting for us in the dictionary. Thus, we might fall into the pattern of the first recipe—work out the precise steps the computer has to perform, one by one, blind to the higher-level concepts that they express.

- -

It is a useful skill, in programming, to notice when you are working at too low a level of abstraction.

- -

Abstracting repetition

- -

Plain functions, as we’ve seen them so far, are a good way to build abstractions. But sometimes they fall short.

- -

It is common for a program to do something a given number of times. You can write a for loop for that, like this:

- -
for (let i = 0; i < 10; i++) {
-  console.log(i);
-}
- -

Can we abstract “doing something N times” as a function? Well, it’s easy to write a function that calls console.log N times.

- -
function repeatLog(n) {
-  for (let i = 0; i < n; i++) {
-    console.log(i);
-  }
-}
- -

But what if we want to do something other than logging the numbers? Since “doing something” can be represented as a function and functions are just values, we can pass our action as a function value.

- -
function repeat(n, action) {
-  for (let i = 0; i < n; i++) {
-    action(i);
-  }
-}
-
-repeat(3, console.log);
-// → 0
-// → 1
-// → 2
- -

We don’t have to pass a predefined function to repeat. Often, it is easier to create a function value on the spot instead.

- -
let labels = [];
-repeat(5, i => {
-  labels.push(`Unit ${i + 1}`);
-});
-console.log(labels);
-// → ["Unit 1", "Unit 2", "Unit 3", "Unit 4", "Unit 5"]
- -

This is structured a little like a for loop—it first describes the kind of loop and then provides a body. However, the body is now written as a function value, which is wrapped in the parentheses of the call to repeat. This is why it has to be closed with the closing brace and closing parenthesis. In cases like this example, where the body is a single small expression, you could also omit the braces and write the loop on a single line.

- -

Higher-order functions

- -

Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions. Since we have already seen that functions are regular values, there is nothing particularly remarkable about the fact that such functions exist. The term comes from mathematics, where the distinction between functions and other values is taken more seriously.

- -

Higher-order functions allow us to abstract over actions, not just values. They come in several forms. For example, we can have functions that create new functions.

- -
function greaterThan(n) {
-  return m => m > n;
-}
-let greaterThan10 = greaterThan(10);
-console.log(greaterThan10(11));
-// → true
- -

And we can have functions that change other functions.

- -
function noisy(f) {
-  return (...args) => {
-    console.log("calling with", args);
-    let result = f(...args);
-    console.log("called with", args, ", returned", result);
-    return result;
-  };
-}
-noisy(Math.min)(3, 2, 1);
-// → calling with [3, 2, 1]
-// → called with [3, 2, 1] , returned 1
- -

We can even write functions that provide new types of control -flow.

- -
function unless(test, then) {
-  if (!test) then();
-}
-
-repeat(3, n => {
-  unless(n % 2 == 1, () => {
-    console.log(n, "is even");
-  });
-});
-// → 0 is even
-// → 2 is even
- -

There is a built-in array method, forEach, that provides something like a for/of loop as a higher-order function.

- -
["A", "B"].forEach(l => console.log(l));
-// → A
-// → B
- -

Script data set

- -

One area where higher-order functions shine is data processing. To process data, we’ll need some actual data. This chapter will use a data set about scripts—writing systems such as Latin, Cyrillic, or Arabic.

- -

Remember Unicode from Chapter 1, the system that assigns a number to each character in written language? Most of these characters are associated with a specific script. The standard contains 140 different scripts—81 are still in use today, and 59 are historic.

- -

Though I can fluently read only Latin characters, I appreciate the fact that people are writing texts in at least 80 other writing systems, many of which I wouldn’t even recognize. For example, here’s a sample of Tamil handwriting:

Tamil handwriting
- -

The example data set contains some pieces of information about the 140 scripts defined in Unicode. It is available in the coding sandbox for this chapter as the SCRIPTS binding. The binding contains an array of objects, each of which describes a script.

- -
{
-  name: "Coptic",
-  ranges: [[994, 1008], [11392, 11508], [11513, 11520]],
-  direction: "ltr",
-  year: -200,
-  living: false,
-  link: "https://en.wikipedia.org/wiki/Coptic_alphabet"
-}
- -

Such an object tells us the name of the script, the Unicode ranges assigned to it, the direction in which it is written, the (approximate) origin time, whether it is still in use, and a link to more information. The direction may be "ltr" for left to right, "rtl" for right to left (the way Arabic and Hebrew text are written), or "ttb" for top to bottom (as with Mongolian writing).

- -

The ranges property contains an array of Unicode character ranges, each of which is a two-element array containing a lower bound and an upper bound. Any character codes within these ranges are assigned to the script. The lower bound is inclusive (code 994 is a Coptic character), and the upper bound is non-inclusive (code 1008 isn’t).

- -

Filtering arrays

- -

To find the scripts in the data set that are still in use, the following function might be helpful. It filters out the elements in an array that don’t pass a test.

- -
function filter(array, test) {
-  let passed = [];
-  for (let element of array) {
-    if (test(element)) {
-      passed.push(element);
-    }
-  }
-  return passed;
-}
-
-console.log(filter(SCRIPTS, script => script.living));
-// → [{name: "Adlam", …}, …]
- -

The function uses the argument named test, a function value, to fill a “gap” in the computation—the process of deciding which elements to collect.

- -

Note how the filter function, rather than deleting elements from the existing array, builds up a new array with only the elements that pass the test. This function is pure. It does not modify the array it is given.

- -

Like forEach, filter is a standard array method. The example defined the function only to show what it does internally. From now on, we’ll use it like this instead:

- -
console.log(SCRIPTS.filter(s => s.direction == "ttb"));
-// → [{name: "Mongolian", …}, …]
- -

Transforming with map

- -

Say we have an array of objects representing scripts, produced by filtering the SCRIPTS array somehow. But we want an array of names, which is easier to inspect.

- -

The map method transforms an array by applying a function to all of its elements and building a new array from the returned values. The new array will have the same length as the input array, but its content will have been mapped to a new form by the function.

- -
function map(array, transform) {
-  let mapped = [];
-  for (let element of array) {
-    mapped.push(transform(element));
-  }
-  return mapped;
-}
-
-let rtlScripts = SCRIPTS.filter(s => s.direction == "rtl");
-console.log(map(rtlScripts, s => s.name));
-// → ["Adlam", "Arabic", "Imperial Aramaic", …]
- -

Like forEach and filter, map is a standard array method.

- -

Summarizing with reduce

- -

Another common thing to do with arrays is to compute a single value from them. Our recurring example, summing a collection of numbers, is an instance of this. Another example is finding the script with the most characters.

- -

The higher-order operation that represents this pattern is called reduce (sometimes also called fold). It builds a value by repeatedly taking a single element from the array and combining it with the current value. When summing numbers, you’d start with the number zero and, for each element, add that to the sum.

- -

The parameters to reduce are, apart from the array, a combining function and a start value. This function is a little less straightforward than filter and map, so take a close look at it:

- -
function reduce(array, combine, start) {
-  let current = start;
-  for (let element of array) {
-    current = combine(current, element);
-  }
-  return current;
-}
-
-console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0));
-// → 10
- -

The standard array method reduce, which of course corresponds to this function, has an added convenience. If your array contains at least one element, you are allowed to leave off the start argument. The method will take the first element of the array as its start value and start reducing at the second element.

- -
console.log([1, 2, 3, 4].reduce((a, b) => a + b));
-// → 10
- -

To use reduce (twice) to find the script with the most characters, we can write something like this:

- -
function characterCount(script) {
-  return script.ranges.reduce((count, [from, to]) => {
-    return count + (to - from);
-  }, 0);
-}
-
-console.log(SCRIPTS.reduce((a, b) => {
-  return characterCount(a) < characterCount(b) ? b : a;
-}));
-// → {name: "Han", …}
- -

The characterCount function reduces the ranges assigned to a script by summing their sizes. Note the use of destructuring in the parameter list of the reducer function. The second call to reduce then uses this to find the largest script by repeatedly comparing two scripts and returning the larger one.

- -

The Han script has more than 89,000 characters assigned to it in the Unicode standard, making it by far the biggest writing system in the data set. Han is a script (sometimes) used for Chinese, Japanese, and Korean text. Those languages share a lot of characters, though they tend to write them differently. The (U.S.-based) Unicode Consortium decided to treat them as a single writing system to save character codes. This is called Han unification and still makes some people very angry.

- -

Composability

- -

Consider how we would have written the previous example (finding the biggest script) without higher-order functions. The code is not that much worse.

- -
let biggest = null;
-for (let script of SCRIPTS) {
-  if (biggest == null ||
-      characterCount(biggest) < characterCount(script)) {
-    biggest = script;
-  }
-}
-console.log(biggest);
-// → {name: "Han", …}
- -

There are a few more bindings, and the program is four lines longer. But it is still very readable.

- -

Higher-order functions start to shine when you need to compose operations. As an example, let’s write code that finds the average year of origin for living and dead scripts in the data set.

- -
function average(array) {
-  return array.reduce((a, b) => a + b) / array.length;
-}
-
-console.log(Math.round(average(
-  SCRIPTS.filter(s => s.living).map(s => s.year))));
-// → 1165
-console.log(Math.round(average(
-  SCRIPTS.filter(s => !s.living).map(s => s.year))));
-// → 204
- -

So the dead scripts in Unicode are, on average, older than the living ones. This is not a terribly meaningful or surprising statistic. But I hope you’ll agree that the code used to compute it isn’t hard to read. You can see it as a pipeline: we start with all scripts, filter out the living (or dead) ones, take the years from those, average them, and round the result.

- -

You could definitely also write this computation as one big loop.

- -
let total = 0, count = 0;
-for (let script of SCRIPTS) {
-  if (script.living) {
-    total += script.year;
-    count += 1;
-  }
-}
-console.log(Math.round(total / count));
-// → 1165
- -

But it is harder to see what was being computed and how. And because intermediate results aren’t represented as coherent values, it’d be a lot more work to extract something like average into a separate function.

- -

In terms of what the computer is actually doing, these two approaches are also quite different. The first will build up new arrays when running filter and map, whereas the second computes only some numbers, doing less work. You can usually afford the readable approach, but if you’re processing huge arrays, and doing so many times, the less abstract style might be worth the extra speed.

- -

Strings and character codes

- -

One use of the data set would be figuring out what script a piece of text is using. Let’s go through a program that does this.

- -

Remember that each script has an array of character code ranges associated with it. So given a character code, we could use a function like this to find the corresponding script (if any):

- -
function characterScript(code) {
-  for (let script of SCRIPTS) {
-    if (script.ranges.some(([from, to]) => {
-      return code >= from && code < to;
-    })) {
-      return script;
-    }
-  }
-  return null;
-}
-
-console.log(characterScript(121));
-// → {name: "Latin", …}
- -

The some method is another higher-order function. It takes a test function and tells you whether that function returns true for any of the elements in the array.

- -

But how do we get the character codes in a string?

- -

In Chapter 1 I mentioned that JavaScript strings are encoded as a sequence of 16-bit numbers. These are called code -units. A Unicode character code was initially supposed to fit within such a unit (which gives you a little over 65,000 characters). When it became clear that wasn’t going to be enough, many people balked at the need to use more memory per character. To address these concerns, UTF-16, the format used by JavaScript strings, was invented. It describes most common characters using a single 16-bit code unit but uses a pair of two such units for others.

- -

UTF-16 is generally considered a bad idea today. It seems almost intentionally designed to invite mistakes. It’s easy to write programs that pretend code units and characters are the same thing. And if your language doesn’t use two-unit characters, that will appear to work just fine. But as soon as someone tries to use such a program with some less common Chinese characters, it breaks. Fortunately, with the advent of emoji, everybody has started using two-unit characters, and the burden of dealing with such problems is more fairly distributed.

- -

Unfortunately, obvious operations on JavaScript strings, such as getting their length through the length property and accessing their content using square brackets, deal only with code units.

- -
// Two emoji characters, horse and shoe
-let horseShoe = "🐴👟";
-console.log(horseShoe.length);
-// → 4
-console.log(horseShoe[0]);
-// → (Invalid half-character)
-console.log(horseShoe.charCodeAt(0));
-// → 55357 (Code of the half-character)
-console.log(horseShoe.codePointAt(0));
-// → 128052 (Actual code for horse emoji)
- -

JavaScript’s charCodeAt method gives you a code unit, not a full character code. The codePointAt method, added later, does give a full Unicode character. So we could use that to get characters from a string. But the argument passed to codePointAt is still an index into the sequence of code units. So to run over all characters in a string, we’d still need to deal with the question of whether a character takes up one or two code units.

- -

In the previous chapter, I mentioned that a for/of loop can also be used on strings. Like codePointAt, this type of loop was introduced at a time where people were acutely aware of the problems with UTF-16. When you use it to loop over a string, it gives you real characters, not code units.

- -
let roseDragon = "🌹🐉";
-for (let char of roseDragon) {
-  console.log(char);
-}
-// → 🌹
-// → 🐉
- -

If you have a character (which will be a string of one or two code units), you can use codePointAt(0) to get its code.

- -

Recognizing text

- -

We have a characterScript function and a way to correctly loop over characters. The next step is to count the characters that belong to each script. The following counting abstraction will be useful there:

- -
function countBy(items, groupName) {
-  let counts = [];
-  for (let item of items) {
-    let name = groupName(item);
-    let known = counts.findIndex(c => c.name == name);
-    if (known == -1) {
-      counts.push({name, count: 1});
-    } else {
-      counts[known].count++;
-    }
-  }
-  return counts;
-}
-
-console.log(countBy([1, 2, 3, 4, 5], n => n > 2));
-// → [{name: false, count: 2}, {name: true, count: 3}]
- -

The countBy function expects a collection (anything that we can loop over with for/of) and a function that computes a group name for a given element. It returns an array of objects, each of which names a group and tells you the number of elements that were found in that group.

- -

It uses another array method—findIndex. This method is somewhat like indexOf, but instead of looking for a specific value, it finds the first value for which the given function returns true. Like indexOf, it returns -1 when no such element is found.

- -

Using countBy, we can write the function that tells us which scripts are used in a piece of text.

- -
function textScripts(text) {
-  let scripts = countBy(text, char => {
-    let script = characterScript(char.codePointAt(0));
-    return script ? script.name : "none";
-  }).filter(({name}) => name != "none");
-
-  let total = scripts.reduce((n, {count}) => n + count, 0);
-  if (total == 0) return "No scripts found";
-
-  return scripts.map(({name, count}) => {
-    return `${Math.round(count * 100 / total)}% ${name}`;
-  }).join(", ");
-}
-
-console.log(textScripts('英国的狗说"woof", 俄罗斯的狗说"тяв"'));
-// → 61% Han, 22% Latin, 17% Cyrillic
- -

The function first counts the characters by name, using characterScript to assign them a name and falling back to the string "none" for characters that aren’t part of any script. The filter call drops the entry for "none" from the resulting array since we aren’t interested in those characters.

- -

To be able to compute percentages, we first need the total number of characters that belong to a script, which we can compute with reduce. If no such characters are found, the function returns a specific string. Otherwise, it transforms the counting entries into readable strings with map and then combines them with join.

- -

Summary

- -

Being able to pass function values to other functions is a deeply useful aspect of JavaScript. It allows us to write functions that model computations with “gaps” in them. The code that calls these functions can fill in the gaps by providing function values.

- -

Arrays provide a number of useful higher-order methods. You can use forEach to loop over the elements in an array. The filter method returns a new array containing only the elements that pass the predicate function. Transforming an array by putting each element through a function is done with map. You can use reduce to combine all the elements in an array into a single value. The some method tests whether any element matches a given predicate function. And findIndex finds the position of the first element that matches a predicate.

- -

Exercises

- -

Flattening

- -

Use the reduce method in combination with the concat method to “flatten” an array of arrays into a single array that has all the elements of the original arrays.

- -
let arrays = [[1, 2, 3], [4, 5], [6]];
-// Your code here.
-// → [1, 2, 3, 4, 5, 6]
- -

Your own loop

- -

Write a higher-order function loop that provides something like a for loop statement. It takes a value, a test function, an update function, and a body function. Each iteration, it first runs the test function on the current loop value and stops if that returns false. Then it calls the body function, giving it the current value. Finally, it calls the update function to create a new value and starts from the beginning.

- -

When defining the function, you can use a regular loop to do the actual looping.

- -
// Your code here.
-
-loop(3, n => n > 0, n => n - 1, console.log);
-// → 3
-// → 2
-// → 1
- -

Everything

- -

Analogous to the some method, arrays also have an every method. This one returns true when the given function returns true for every element in the array. In a way, some is a version of the || operator that acts on arrays, and every is like the && operator.

- -

Implement every as a function that takes an array and a predicate function as parameters. Write two versions, one using a loop and one using the some method.

- -
function every(array, test) {
-  // Your code here.
-}
-
-console.log(every([1, 3, 5], n => n < 10));
-// → true
-console.log(every([2, 4, 16], n => n < 10));
-// → false
-console.log(every([], n => n < 10));
-// → true
- -
- -

Like the && operator, the every method can stop evaluating further elements as soon as it has found one that doesn’t match. So the loop-based version can jump out of the loop—with break or return—as soon as it runs into an element for which the predicate function returns false. If the loop runs to its end without finding such an element, we know that all elements matched and we should return true.

- -

To build every on top of some, we can apply De Morgan’s -laws, which state that a && b equals !(!a || !b). This can be generalized to arrays, where all elements in the array match if there is no element in the array that does not match.

- -
- -

Dominant writing direction

- -

Write a function that computes the dominant writing direction in a string of text. Remember that each script object has a direction property that can be "ltr" (left to right), "rtl" (right to left), or "ttb" (top to bottom).

- -

The dominant direction is the direction of a majority of the characters that have a script associated with them. The characterScript and countBy functions defined earlier in the chapter are probably useful here.

- -
function dominantDirection(text) {
-  // Your code here.
-}
-
-console.log(dominantDirection("Hello!"));
-// → ltr
-console.log(dominantDirection("Hey, مساء الخير"));
-// → rtl
- -
- -

Your solution might look a lot like the first half of the textScripts example. You again have to count characters by a criterion based on characterScript and then filter out the part of the result that refers to uninteresting (script-less) characters.

- -

Finding the direction with the highest character count can be done with reduce. If it’s not clear how, refer to the example earlier in the chapter, where reduce was used to find the script with the most characters.

- -
-
diff --git a/docs/06_object.html b/docs/06_object.html deleted file mode 100644 index 6d63632f3..000000000 --- a/docs/06_object.html +++ /dev/null @@ -1,660 +0,0 @@ - - - - - The Secret Life of Objects :: Eloquent JavaScript - - - - - - -
- - -

Chapter 6The Secret Life of Objects

- -
- -

An abstract data type is realized by writing a special kind of program […] which defines the type in terms of the operations which can be performed on it.

- -
Barbara Liskov, Programming with Abstract Data Types
- -
Picture of a rabbit with its proto-rabbit
- -

Chapter 4 introduced JavaScript’s objects. In programming culture, we have a thing called object-oriented programming, a set of techniques that use objects (and related concepts) as the central principle of program organization.

- -

Though no one really agrees on its precise definition, object-oriented programming has shaped the design of many programming languages, including JavaScript. This chapter will describe the way these ideas can be applied in JavaScript.

- -

Encapsulation

- -

The core idea in object-oriented programming is to divide programs into smaller pieces and make each piece responsible for managing its own state.

- -

This way, some knowledge about the way a piece of the program works can be kept local to that piece. Someone working on the rest of the program does not have to remember or even be aware of that knowledge. Whenever these local details change, only the code directly around it needs to be updated.

- -

Different pieces of such a program interact with each other through interfaces, limited sets of functions or bindings that provide useful functionality at a more abstract level, hiding their precise implementation.

- -

Such program pieces are modeled using objects. Their interface consists of a specific set of methods and properties. Properties that are part of the interface are called public. The others, which outside code should not be touching, are called private.

- -

Many languages provide a way to distinguish public and private properties and prevent outside code from accessing the private ones altogether. JavaScript, once again taking the minimalist approach, does not—not yet at least. There is work underway to add this to the language.

- -

Even though the language doesn’t have this distinction built in, JavaScript programmers are successfully using this idea. Typically, the available interface is described in documentation or comments. It is also common to put an underscore (_) character at the start of property names to indicate that those properties are private.

- -

Separating interface from implementation is a great idea. It is usually called encapsulation.

- -

Methods

- -

Methods are nothing more than properties that hold function values. This is a simple method:

- -
let rabbit = {};
-rabbit.speak = function(line) {
-  console.log(`The rabbit says '${line}'`);
-};
-
-rabbit.speak("I'm alive.");
-// → The rabbit says 'I'm alive.'
- -

Usually a method needs to do something with the object it was called on. When a function is called as a method—looked up as a property and immediately called, as in object.method()—the binding called this in its body automatically points at the object that it was called on.

- -
function speak(line) {
-  console.log(`The ${this.type} rabbit says '${line}'`);
-}
-let whiteRabbit = {type: "white", speak};
-let hungryRabbit = {type: "hungry", speak};
-
-whiteRabbit.speak("Oh my ears and whiskers, " +
-                  "how late it's getting!");
-// → The white rabbit says 'Oh my ears and whiskers, how
-//   late it's getting!'
-hungryRabbit.speak("I could use a carrot right now.");
-// → The hungry rabbit says 'I could use a carrot right now.'
- -

You can think of this as an extra parameter that is passed in a different way. If you want to pass it explicitly, you can use a function’s call method, which takes the this value as its first argument and treats further arguments as normal parameters.

- -
speak.call(hungryRabbit, "Burp!");
-// → The hungry rabbit says 'Burp!'
- -

Since each function has its own this binding, whose value depends on the way it is called, you cannot refer to the this of the wrapping scope in a regular function defined with the function keyword.

- -

Arrow functions are different—they do not bind their own this but can see the this binding of the scope around them. Thus, you can do something like the following code, which references this from inside a local function:

- -
function normalize() {
-  console.log(this.coords.map(n => n / this.length));
-}
-normalize.call({coords: [0, 2, 3], length: 5});
-// → [0, 0.4, 0.6]
- -

If I had written the argument to map using the function keyword, the code wouldn’t work.

- -

Prototypes

- -

Watch closely.

- -
let empty = {};
-console.log(empty.toString);
-// → function toString(){…}
-console.log(empty.toString());
-// → [object Object]
- -

I pulled a property out of an empty object. Magic!

- -

Well, not really. I have simply been withholding information about the way JavaScript objects work. In addition to their set of properties, most objects also have a prototype. A prototype is another object that is used as a fallback source of properties. When an object gets a request for a property that it does not have, its prototype will be searched for the property, then the prototype’s prototype, and so on.

- -

So who is the prototype of that empty object? It is the great ancestral prototype, the entity behind almost all objects, Object.prototype.

- -
console.log(Object.getPrototypeOf({}) ==
-            Object.prototype);
-// → true
-console.log(Object.getPrototypeOf(Object.prototype));
-// → null
- -

As you guess, Object.getPrototypeOf returns the prototype of an object.

- -

The prototype relations of JavaScript objects form a tree-shaped structure, and at the root of this structure sits Object.prototype. It provides a few methods that show up in all objects, such as toString, which converts an object to a string representation.

- -

Many objects don’t directly have Object.prototype as their prototype but instead have another object that provides a different set of default properties. Functions derive from Function.prototype, and arrays derive from Array.prototype.

- -
console.log(Object.getPrototypeOf(Math.max) ==
-            Function.prototype);
-// → true
-console.log(Object.getPrototypeOf([]) ==
-            Array.prototype);
-// → true
- -

Such a prototype object will itself have a prototype, often Object.prototype, so that it still indirectly provides methods like toString.

- -

You can use Object.create to create an object with a specific prototype.

- -
let protoRabbit = {
-  speak(line) {
-    console.log(`The ${this.type} rabbit says '${line}'`);
-  }
-};
-let killerRabbit = Object.create(protoRabbit);
-killerRabbit.type = "killer";
-killerRabbit.speak("SKREEEE!");
-// → The killer rabbit says 'SKREEEE!'
- -

A property like speak(line) in an object expression is a shorthand way of defining a method. It creates a property called speak and gives it a function as its value.

- -

The “proto” rabbit acts as a container for the properties that are shared by all rabbits. An individual rabbit object, like the killer rabbit, contains properties that apply only to itself—in this case its type—and derives shared properties from its prototype.

- -

Classes

- -

JavaScript’s prototype system can be interpreted as a somewhat informal take on an object-oriented concept called classes. A class defines the shape of a type of object—what methods and properties it has. Such an object is called an instance of the class.

- -

Prototypes are useful for defining properties for which all instances of a class share the same value, such as methods. Properties that differ per instance, such as our rabbits’ type property, need to be stored directly in the objects themselves.

- -

So to create an instance of a given class, you have to make an object that derives from the proper prototype, but you also have to make sure it, itself, has the properties that instances of this class are supposed to have. This is what a constructor function does.

- -
function makeRabbit(type) {
-  let rabbit = Object.create(protoRabbit);
-  rabbit.type = type;
-  return rabbit;
-}
- -

JavaScript provides a way to make defining this type of function easier. If you put the keyword new in front of a function call, the function is treated as a constructor. This means that an object with the right prototype is automatically created, bound to this in the function, and returned at the end of the function.

- -

The prototype object used when constructing objects is found by taking the prototype property of the constructor function.

- -
function Rabbit(type) {
-  this.type = type;
-}
-Rabbit.prototype.speak = function(line) {
-  console.log(`The ${this.type} rabbit says '${line}'`);
-};
-
-let weirdRabbit = new Rabbit("weird");
- -

Constructors (all functions, in fact) automatically get a property named prototype, which by default holds a plain, empty object that derives from Object.prototype. You can overwrite it with a new object if you want. Or you can add properties to the existing object, as the example does.

- -

By convention, the names of constructors are capitalized so that they can easily be distinguished from other functions.

- -

It is important to understand the distinction between the way a prototype is associated with a constructor (through its prototype property) and the way objects have a prototype (which can be found with Object.getPrototypeOf). The actual prototype of a constructor is Function.prototype since constructors are functions. Its prototype property holds the prototype used for instances created through it.

- -
console.log(Object.getPrototypeOf(Rabbit) ==
-            Function.prototype);
-// → true
-console.log(Object.getPrototypeOf(weirdRabbit) ==
-            Rabbit.prototype);
-// → true
- -

Class notation

- -

So JavaScript classes are constructor functions with a prototype property. That is how they work, and until 2015, that was how you had to write them. These days, we have a less awkward notation.

- -
class Rabbit {
-  constructor(type) {
-    this.type = type;
-  }
-  speak(line) {
-    console.log(`The ${this.type} rabbit says '${line}'`);
-  }
-}
-
-let killerRabbit = new Rabbit("killer");
-let blackRabbit = new Rabbit("black");
- -

The class keyword starts a class declaration, which allows us to define a constructor and a set of methods all in a single place. Any number of methods may be written inside the declaration’s braces. The one named constructor is treated specially. It provides the actual constructor function, which will be bound to the name Rabbit. The others are packaged into that constructor’s prototype. Thus, the earlier class declaration is equivalent to the constructor definition from the previous section. It just looks nicer.

- -

Class declarations currently allow only methods—properties that hold functions—to be added to the prototype. This can be somewhat inconvenient when you want to save a non-function value in there. The next version of the language will probably improve this. For now, you can create such properties by directly manipulating the prototype after you’ve defined the class.

- -

Like function, class can be used both in statements and in expressions. When used as an expression, it doesn’t define a binding but just produces the constructor as a value. You are allowed to omit the class name in a class expression.

- -
let object = new class { getWord() { return "hello"; } };
-console.log(object.getWord());
-// → hello
- -

Overriding derived properties

- -

When you add a property to an object, whether it is present in the prototype or not, the property is added to the object itself. If there was already a property with the same name in the prototype, this property will no longer affect the object, as it is now hidden behind the object’s own property.

- -
Rabbit.prototype.teeth = "small";
-console.log(killerRabbit.teeth);
-// → small
-killerRabbit.teeth = "long, sharp, and bloody";
-console.log(killerRabbit.teeth);
-// → long, sharp, and bloody
-console.log(blackRabbit.teeth);
-// → small
-console.log(Rabbit.prototype.teeth);
-// → small
- -

The following diagram sketches the situation after this code has run. The Rabbit and Object prototypes lie behind killerRabbit as a kind of backdrop, where properties that are not found in the object itself can be looked up.

Rabbit object prototype schema
- -

Overriding properties that exist in a prototype can be a useful thing to do. As the rabbit teeth example shows, overriding can be used to express exceptional properties in instances of a more generic class of objects, while letting the nonexceptional objects take a standard value from their prototype.

- -

Overriding is also used to give the standard function and array prototypes a different toString method than the basic object prototype.

- -
console.log(Array.prototype.toString ==
-            Object.prototype.toString);
-// → false
-console.log([1, 2].toString());
-// → 1,2
- -

Calling toString on an array gives a result similar to calling .join(",") on it—it puts commas between the values in the array. Directly calling Object.prototype.toString with an array produces a different string. That function doesn’t know about arrays, so it simply puts the word object and the name of the type between square brackets.

- -
console.log(Object.prototype.toString.call([1, 2]));
-// → [object Array]
- -

Maps

- -

We saw the word map used in the previous chapter for an operation that transforms a data structure by applying a function to its elements. Confusing as it is, in programming the same word is also used for a related but rather different thing.

- -

A map (noun) is a data structure that associates values (the keys) with other values. For example, you might want to map names to ages. It is possible to use objects for this.

- -
let ages = {
-  Boris: 39,
-  Liang: 22,
-  Júlia: 62
-};
-
-console.log(`Júlia is ${ages["Júlia"]}`);
-// → Júlia is 62
-console.log("Is Jack's age known?", "Jack" in ages);
-// → Is Jack's age known? false
-console.log("Is toString's age known?", "toString" in ages);
-// → Is toString's age known? true
- -

Here, the object’s property names are the people’s names, and the property values are their ages. But we certainly didn’t list anybody named toString in our map. Yet, because plain objects derive from Object.prototype, it looks like the property is there.

- -

As such, using plain objects as maps is dangerous. There are several possible ways to avoid this problem. First, it is possible to create objects with no prototype. If you pass null to Object.create, the resulting object will not derive from Object.prototype and can safely be used as a map.

- -
console.log("toString" in Object.create(null));
-// → false
- -

Object property names must be strings. If you need a map whose keys can’t easily be converted to strings—such as objects—you cannot use an object as your map.

- -

Fortunately, JavaScript comes with a class called Map that is written for this exact purpose. It stores a mapping and allows any type of keys.

- -
let ages = new Map();
-ages.set("Boris", 39);
-ages.set("Liang", 22);
-ages.set("Júlia", 62);
-
-console.log(`Júlia is ${ages.get("Júlia")}`);
-// → Júlia is 62
-console.log("Is Jack's age known?", ages.has("Jack"));
-// → Is Jack's age known? false
-console.log(ages.has("toString"));
-// → false
- -

The methods set, get, and has are part of the interface of the Map object. Writing a data structure that can quickly update and search a large set of values isn’t easy, but we don’t have to worry about that. Someone else did it for us, and we can go through this simple interface to use their work.

- -

If you do have a plain object that you need to treat as a map for some reason, it is useful to know that Object.keys returns only an object’s own keys, not those in the prototype. As an alternative to the in operator, you can use the hasOwnProperty method, which ignores the object’s prototype.

- -
console.log({x: 1}.hasOwnProperty("x"));
-// → true
-console.log({x: 1}.hasOwnProperty("toString"));
-// → false
- -

Polymorphism

- -

When you call the String function (which converts a value to a string) on an object, it will call the toString method on that object to try to create a meaningful string from it. I mentioned that some of the standard prototypes define their own version of toString so they can create a string that contains more useful information than "[object Object]". You can also do that yourself.

- -
Rabbit.prototype.toString = function() {
-  return `a ${this.type} rabbit`;
-};
-
-console.log(String(blackRabbit));
-// → a black rabbit
- -

This is a simple instance of a powerful idea. When a piece of code is written to work with objects that have a certain interface—in this case, a toString method—any kind of object that happens to support this interface can be plugged into the code, and it will just work.

- -

This technique is called polymorphism. Polymorphic code can work with values of different shapes, as long as they support the interface it expects.

- -

I mentioned in Chapter 4 that a for/of loop can loop over several kinds of data structures. This is another case of polymorphism—such loops expect the data structure to expose a specific interface, which arrays and strings do. And we can also add this interface to your own objects! But before we can do that, we need to know what symbols are.

- -

Symbols

- -

It is possible for multiple interfaces to use the same property name for different things. For example, I could define an interface in which the toString method is supposed to convert the object into a piece of yarn. It would not be possible for an object to conform to both that interface and the standard use of toString.

- -

That would be a bad idea, and this problem isn’t that common. Most JavaScript programmers simply don’t think about it. But the language designers, whose job it is to think about this stuff, have provided us with a solution anyway.

- -

When I claimed that property names are strings, that wasn’t entirely accurate. They usually are, but they can also be symbols. Symbols are values created with the Symbol function. Unlike strings, newly created symbols are unique—you cannot create the same symbol twice.

- -
let sym = Symbol("name");
-console.log(sym == Symbol("name"));
-// → false
-Rabbit.prototype[sym] = 55;
-console.log(blackRabbit[sym]);
-// → 55
- -

The string you pass to Symbol is included when you convert it to a string and can make it easier to recognize a symbol when, for example, showing it in the console. But it has no meaning beyond that—multiple symbols may have the same name.

- -

Being both unique and usable as property names makes symbols suitable for defining interfaces that can peacefully live alongside other properties, no matter what their names are.

- -
const toStringSymbol = Symbol("toString");
-Array.prototype[toStringSymbol] = function() {
-  return `${this.length} cm of blue yarn`;
-};
-
-console.log([1, 2].toString());
-// → 1,2
-console.log([1, 2][toStringSymbol]());
-// → 2 cm of blue yarn
- -

It is possible to include symbol properties in object expressions and classes by using square brackets around the property name. That causes the property name to be evaluated, much like the square bracket property access notation, which allows us to refer to a binding that holds the symbol.

- -
let stringObject = {
-  [toStringSymbol]() { return "a jute rope"; }
-};
-console.log(stringObject[toStringSymbol]());
-// → a jute rope
- -

The iterator interface

- -

The object given to a for/of loop is expected to be iterable. This means it has a method named with the Symbol.iterator symbol (a symbol value defined by the language, stored as a property of the Symbol function).

- -

When called, that method should return an object that provides a second interface, iterator. This is the actual thing that iterates. It has a next method that returns the next result. That result should be an object with a value property that provides the next value, if there is one, and a done property, which should be true when there are no more results and false otherwise.

- -

Note that the next, value, and done property names are plain strings, not symbols. Only Symbol.iterator, which is likely to be added to a lot of different objects, is an actual symbol.

- -

We can directly use this interface ourselves.

- -
let okIterator = "OK"[Symbol.iterator]();
-console.log(okIterator.next());
-// → {value: "O", done: false}
-console.log(okIterator.next());
-// → {value: "K", done: false}
-console.log(okIterator.next());
-// → {value: undefined, done: true}
- -

Let’s implement an iterable data structure. We’ll build a matrix class, acting as a two-dimensional array.

- -
class Matrix {
-  constructor(width, height, element = (x, y) => undefined) {
-    this.width = width;
-    this.height = height;
-    this.content = [];
-
-    for (let y = 0; y < height; y++) {
-      for (let x = 0; x < width; x++) {
-        this.content[y * width + x] = element(x, y);
-      }
-    }
-  }
-
-  get(x, y) {
-    return this.content[y * this.width + x];
-  }
-  set(x, y, value) {
-    this.content[y * this.width + x] = value;
-  }
-}
- -

The class stores its content in a single array of width × height elements. The elements are stored row by row, so, for example, the third element in the fifth row is (using zero-based indexing) stored at position 4 × width + 2.

- -

The constructor function takes a width, a height, and an optional element function that will be used to fill in the initial values. There are get and set methods to retrieve and update elements in the matrix.

- -

When looping over a matrix, you are usually interested in the position of the elements as well as the elements themselves, so we’ll have our iterator produce objects with x, y, and value properties.

- -
class MatrixIterator {
-  constructor(matrix) {
-    this.x = 0;
-    this.y = 0;
-    this.matrix = matrix;
-  }
-
-  next() {
-    if (this.y == this.matrix.height) return {done: true};
-
-    let value = {x: this.x,
-                 y: this.y,
-                 value: this.matrix.get(this.x, this.y)};
-    this.x++;
-    if (this.x == this.matrix.width) {
-      this.x = 0;
-      this.y++;
-    }
-    return {value, done: false};
-  }
-}
- -

The class tracks the progress of iterating over a matrix in its x and y properties. The next method starts by checking whether the bottom of the matrix has been reached. If it hasn’t, it first creates the object holding the current value and then updates its position, moving to the next row if necessary.

- -

Let’s set up the Matrix class to be iterable. Throughout this book, I’ll occasionally use after-the-fact prototype manipulation to add methods to classes so that the individual pieces of code remain small and self-contained. In a regular program, where there is no need to split the code into small pieces, you’d declare these methods directly in the class instead.

- -
Matrix.prototype[Symbol.iterator] = function() {
-  return new MatrixIterator(this);
-};
- -

We can now loop over a matrix with for/of.

- -
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
-for (let {x, y, value} of matrix) {
-  console.log(x, y, value);
-}
-// → 0 0 value 0,0
-// → 1 0 value 1,0
-// → 0 1 value 0,1
-// → 1 1 value 1,1
- -

Getters, setters, and statics

- -

Interfaces often consist mostly of methods, but it is also okay to include properties that hold non-function values. For example, Map objects have a size property that tells you how many keys are stored in them.

- -

It is not even necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called getters, and they are defined by writing get in front of the method name in an object expression or class declaration.

- -
let varyingSize = {
-  get size() {
-    return Math.floor(Math.random() * 100);
-  }
-};
-
-console.log(varyingSize.size);
-// → 73
-console.log(varyingSize.size);
-// → 49
- -

Whenever someone reads from this object’s size property, the associated method is called. You can do a similar thing when a property is written to, using a setter.

- -
class Temperature {
-  constructor(celsius) {
-    this.celsius = celsius;
-  }
-  get fahrenheit() {
-    return this.celsius * 1.8 + 32;
-  }
-  set fahrenheit(value) {
-    this.celsius = (value - 32) / 1.8;
-  }
-
-  static fromFahrenheit(value) {
-    return new Temperature((value - 32) / 1.8);
-  }
-}
-
-let temp = new Temperature(22);
-console.log(temp.fahrenheit);
-// → 71.6
-temp.fahrenheit = 86;
-console.log(temp.celsius);
-// → 30
- -

The Temperature class allows you to read and write the temperature in either degrees Celsius or degrees Fahrenheit, but internally it stores only Celsius and automatically converts to and from Celsius in the fahrenheit getter and setter.

- -

Sometimes you want to attach some properties directly to your constructor function, rather than to the prototype. Such methods won’t have access to a class instance but can, for example, be used to provide additional ways to create instances.

- -

Inside a class declaration, methods that have static written before their name are stored on the constructor. So the Temperature class allows you to write Temperature.fromFahrenheit(100) to create a temperature using degrees Fahrenheit.

- -

Inheritance

- -

Some matrices are known to be symmetric. If you mirror a symmetric matrix around its top-left-to-bottom-right diagonal, it stays the same. In other words, the value stored at x,y is always the same as that at y,x.

- -

Imagine we need a data structure like Matrix but one that enforces the fact that the matrix is and remains symmetrical. We could write it from scratch, but that would involve repeating some code very similar to what we already wrote.

- -

JavaScript’s prototype system makes it possible to create a new class, much like the old class, but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the set method.

- -

In object-oriented programming terms, this is called inheritance. The new class inherits properties and behavior from the old class.

- -
class SymmetricMatrix extends Matrix {
-  constructor(size, element = (x, y) => undefined) {
-    super(size, size, (x, y) => {
-      if (x < y) return element(y, x);
-      else return element(x, y);
-    });
-  }
-
-  set(x, y, value) {
-    super.set(x, y, value);
-    if (x != y) {
-      super.set(y, x, value);
-    }
-  }
-}
-
-let matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`);
-console.log(matrix.get(2, 3));
-// → 3,2
- -

The use of the word extends indicates that this class shouldn’t be directly based on the default Object prototype but on some other class. This is called the superclass. The derived class is the subclass.

- -

To initialize a SymmetricMatrix instance, the constructor calls its superclass’s constructor through the super keyword. This is necessary because if this new object is to behave (roughly) like a Matrix, it is going to need the instance properties that matrices have. To ensure the matrix is symmetrical, the constructor wraps the element function to swap the coordinates for values below the diagonal.

- -

The set method again uses super but this time not to call the constructor but to call a specific method from the superclass’s set of methods. We are redefining set but do want to use the original behavior. Because this.set refers to the new set method, calling that wouldn’t work. Inside class methods, super provides a way to call methods as they were defined in the superclass.

- -

Inheritance allows us to build slightly different data types from existing data types with relatively little work. It is a fundamental part of the object-oriented tradition, alongside encapsulation and polymorphism. But while the latter two are now generally regarded as wonderful ideas, inheritance is more controversial.

- -

Whereas encapsulation and polymorphism can be used to separate pieces of code from each other, reducing the tangledness of the overall program, inheritance fundamentally ties classes together, creating more tangle. When inheriting from a class, you usually have to know more about how it works than when simply using it. Inheritance can be a useful tool, and I use it now and then in my own programs, but it shouldn’t be the first tool you reach for, and you probably shouldn’t actively go looking for opportunities to construct class hierarchies (family trees of classes).

- -

The instanceof operator

- -

It is occasionally useful to know whether an object was derived from a specific class. For this, JavaScript provides a binary operator called instanceof.

- -
console.log(
-  new SymmetricMatrix(2) instanceof SymmetricMatrix);
-// → true
-console.log(new SymmetricMatrix(2) instanceof Matrix);
-// → true
-console.log(new Matrix(2, 2) instanceof SymmetricMatrix);
-// → false
-console.log([1] instanceof Array);
-// → true
- -

The operator will see through inherited types, so a SymmetricMatrix is an instance of Matrix. The operator can also be applied to standard constructors like Array. Almost every object is an instance of Object.

- -

Summary

- -

So objects do more than just hold their own properties. They have prototypes, which are other objects. They’ll act as if they have properties they don’t have as long as their prototype has that property. Simple objects have Object.prototype as their prototype.

- -

Constructors, which are functions whose names usually start with a capital letter, can be used with the new operator to create new objects. The new object’s prototype will be the object found in the prototype property of the constructor. You can make good use of this by putting the properties that all values of a given type share into their prototype. There’s a class notation that provides a clear way to define a constructor and its prototype.

- -

You can define getters and setters to secretly call methods every time an object’s property is accessed. Static methods are methods stored in a class’s constructor, rather than its prototype.

- -

The instanceof operator can, given an object and a constructor, tell you whether that object is an instance of that constructor.

- -

One useful thing to do with objects is to specify an interface for them and tell everybody that they are supposed to talk to your object only through that interface. The rest of the details that make up your object are now encapsulated, hidden behind the interface.

- -

More than one type may implement the same interface. Code written to use an interface automatically knows how to work with any number of different objects that provide the interface. This is called polymorphism.

- -

When implementing multiple classes that differ in only some details, it can be helpful to write the new classes as subclasses of an existing class, inheriting part of its behavior.

- -

Exercises

- -

A vector type

- -

Write a class Vec that represents a vector in two-dimensional space. It takes x and y parameters (numbers), which it should save to properties of the same name.

- -

Give the Vec prototype two methods, plus and minus, that take another vector as a parameter and return a new vector that has the sum or difference of the two vectors’ (this and the parameter) x and y values.

- -

Add a getter property length to the prototype that computes the length of the vector—that is, the distance of the point (x, y) from the origin (0, 0).

- -
// Your code here.
-
-console.log(new Vec(1, 2).plus(new Vec(2, 3)));
-// → Vec{x: 3, y: 5}
-console.log(new Vec(1, 2).minus(new Vec(2, 3)));
-// → Vec{x: -1, y: -1}
-console.log(new Vec(3, 4).length);
-// → 5
- -
- -

Look back to the Rabbit class example if you’re unsure how class declarations look.

- -

Adding a getter property to the constructor can be done by putting the word get before the method name. To compute the distance from (0, 0) to (x, y), you can use the Pythagorean theorem, which says that the square of the distance we are looking for is equal to the square of the x-coordinate plus the square of the y-coordinate. Thus, √(x2 + y2) is the number you want, and Math.sqrt is the way you compute a square root in JavaScript.

- -
- -

Groups

- -

The standard JavaScript environment provides another data structure called Set. Like an instance of Map, a set holds a collection of values. Unlike Map, it does not associate other values with those—it just tracks which values are part of the set. A value can be part of a set only once—adding it again doesn’t have any effect.

- -

Write a class called Group (since Set is already taken). Like Set, it has add, delete, and has methods. Its constructor creates an empty group, add adds a value to the group (but only if it isn’t already a member), delete removes its argument from the group (if it was a member), and has returns a Boolean value indicating whether its argument is a member of the group.

- -

Use the === operator, or something equivalent such as indexOf, to determine whether two values are the same.

- -

Give the class a static from method that takes an iterable object as argument and creates a group that contains all the values produced by iterating over it.

- -
class Group {
-  // Your code here.
-}
-
-let group = Group.from([10, 20]);
-console.log(group.has(10));
-// → true
-console.log(group.has(30));
-// → false
-group.add(10);
-group.delete(10);
-console.log(group.has(10));
-// → false
- -
- -

The easiest way to do this is to store an array of group members in an instance property. The includes or indexOf methods can be used to check whether a given value is in the array.

- -

Your class’s constructor can set the member collection to an empty array. When add is called, it must check whether the given value is in the array or add it, for example with push, otherwise.

- -

Deleting an element from an array, in delete, is less straightforward, but you can use filter to create a new array without the value. Don’t forget to overwrite the property holding the members with the newly filtered version of the array.

- -

The from method can use a for/of loop to get the values out of the iterable object and call add to put them into a newly created group.

- -
- -

Iterable groups

- -

Make the Group class from the previous exercise iterable. Refer to the section about the iterator interface earlier in the chapter if you aren’t clear on the exact form of the interface anymore.

- -

If you used an array to represent the group’s members, don’t just return the iterator created by calling the Symbol.iterator method on the array. That would work, but it defeats the purpose of this exercise.

- -

It is okay if your iterator behaves strangely when the group is modified during iteration.

- -
// Your code here (and the code from the previous exercise)
-
-for (let value of Group.from(["a", "b", "c"])) {
-  console.log(value);
-}
-// → a
-// → b
-// → c
- -
- -

It is probably worthwhile to define a new class GroupIterator. Iterator instances should have a property that tracks the current position in the group. Every time next is called, it checks whether it is done and, if not, moves past the current value and returns it.

- -

The Group class itself gets a method named by Symbol.iterator that, when called, returns a new instance of the iterator class for that group.

- -
- -

Borrowing a method

- -

Earlier in the chapter I mentioned that an object’s hasOwnProperty can be used as a more robust alternative to the in operator when you want to ignore the prototype’s properties. But what if your map needs to include the word "hasOwnProperty"? You won’t be able to call that method anymore because the object’s own property hides the method value.

- -

Can you think of a way to call hasOwnProperty on an object that has its own property by that name?

- -
let map = {one: true, two: true, hasOwnProperty: true};
-
-// Fix this call
-console.log(map.hasOwnProperty("one"));
-// → true
- -
- -

Remember that methods that exist on plain objects come from Object.prototype.

- -

Also remember that you can call a function with a specific this binding by using its call method.

- -
-
diff --git a/docs/07_robot.html b/docs/07_robot.html deleted file mode 100644 index 115bc414b..000000000 --- a/docs/07_robot.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - Project: A Robot :: Eloquent JavaScript - - - - - - -
- - -

Chapter 7Project: A Robot

- -
- -

[...] the question of whether Machines Can Think [...] is about as relevant as the question of whether Submarines Can Swim.

- -
Edsger Dijkstra, The Threats to Computing Science
- -
Picture of a package-delivery robot
- -

In “project” chapters, I’ll stop pummeling you with new theory for a brief moment, and instead we’ll work through a program together. Theory is necessary to learn to program, but reading and understanding actual programs is just as important.

- -

Our project in this chapter is to build an automaton, a little program that performs a task in a virtual world. Our automaton will be a mail-delivery robot picking up and dropping off parcels.

- -

Meadowfield

- -

The village of Meadowfield isn’t very big. It consists of 11 places with 14 roads between them. It can be described with this array of roads:

- -
const roads = [
-  "Alice's House-Bob's House",   "Alice's House-Cabin",
-  "Alice's House-Post Office",   "Bob's House-Town Hall",
-  "Daria's House-Ernie's House", "Daria's House-Town Hall",
-  "Ernie's House-Grete's House", "Grete's House-Farm",
-  "Grete's House-Shop",          "Marketplace-Farm",
-  "Marketplace-Post Office",     "Marketplace-Shop",
-  "Marketplace-Town Hall",       "Shop-Town Hall"
-];
The village of Meadowfield
- -

The network of roads in the village forms a graph. A graph is a collection of points (places in the village) with lines between them (roads). This graph will be the world that our robot moves through.

- -

The array of strings isn’t very easy to work with. What we’re interested in is the destinations that we can reach from a given place. Let’s convert the list of roads to a data structure that, for each place, tells us what can be reached from there.

- -
function buildGraph(edges) {
-  let graph = Object.create(null);
-  function addEdge(from, to) {
-    if (graph[from] == null) {
-      graph[from] = [to];
-    } else {
-      graph[from].push(to);
-    }
-  }
-  for (let [from, to] of edges.map(r => r.split("-"))) {
-    addEdge(from, to);
-    addEdge(to, from);
-  }
-  return graph;
-}
-
-const roadGraph = buildGraph(roads);
- -

Given an array of edges, buildGraph creates a map object that, for each node, stores an array of connected nodes.

- -

It uses the split method to go from the road strings, which have the form "Start-End", to two-element arrays containing the start and end as separate strings.

- -

The task

- -

Our robot will be moving around the village. There are parcels in various places, each addressed to some other place. The robot picks up parcels when it comes to them and delivers them when it arrives at their destinations.

- -

The automaton must decide, at each point, where to go next. It has finished its task when all parcels have been delivered.

- -

To be able to simulate this process, we must define a virtual world that can describe it. This model tells us where the robot is and where the parcels are. When the robot has decided to move somewhere, we need to update the model to reflect the new situation.

- -

If you’re thinking in terms of object-oriented programming, your first impulse might be to start defining objects for the various elements in the world: a class for the robot, one for a parcel, maybe one for places. These could then hold properties that describe their current state, such as the pile of parcels at a location, which we could change when updating the world.

- -

This is wrong.

- -

At least, it usually is. The fact that something sounds like an object does not automatically mean that it should be an object in your program. Reflexively writing classes for every concept in your application tends to leave you with a collection of interconnected objects that each have their own internal, changing state. Such programs are often hard to understand and thus easy to break.

- -

Instead, let’s condense the village’s state down to the minimal set of values that define it. There’s the robot’s current location and the collection of undelivered parcels, each of which has a current location and a destination address. That’s it.

- -

And while we’re at it, let’s make it so that we don’t change this state when the robot moves but rather compute a new state for the situation after the move.

- -
class VillageState {
-  constructor(place, parcels) {
-    this.place = place;
-    this.parcels = parcels;
-  }
-
-  move(destination) {
-    if (!roadGraph[this.place].includes(destination)) {
-      return this;
-    } else {
-      let parcels = this.parcels.map(p => {
-        if (p.place != this.place) return p;
-        return {place: destination, address: p.address};
-      }).filter(p => p.place != p.address);
-      return new VillageState(destination, parcels);
-    }
-  }
-}
- -

The move method is where the action happens. It first checks whether there is a road going from the current place to the destination, and if not, it returns the old state since this is not a valid move.

- -

Then it creates a new state with the destination as the robot’s new place. But it also needs to create a new set of parcels—parcels that the robot is carrying (that are at the robot’s current place) need to be moved along to the new place. And parcels that are addressed to the new place need to be delivered—that is, they need to be removed from the set of undelivered parcels. The call to map takes care of the moving, and the call to filter does the delivering.

- -

Parcel objects aren’t changed when they are moved but re-created. The move method gives us a new village state but leaves the old one entirely intact.

- -
let first = new VillageState(
-  "Post Office",
-  [{place: "Post Office", address: "Alice's House"}]
-);
-let next = first.move("Alice's House");
-
-console.log(next.place);
-// → Alice's House
-console.log(next.parcels);
-// → []
-console.log(first.place);
-// → Post Office
- -

The move causes the parcel to be delivered, and this is reflected in the next state. But the initial state still describes the situation where the robot is at the post office and the parcel is undelivered.

- -

Persistent data

- -

Data structures that don’t change are called immutable or persistent. They behave a lot like strings and numbers in that they are who they are and stay that way, rather than containing different things at different times.

- -

In JavaScript, just about everything can be changed, so working with values that are supposed to be persistent requires some restraint. There is a function called Object.freeze that changes an object so that writing to its properties is ignored. You could use that to make sure your objects aren’t changed, if you want to be careful. Freezing does require the computer to do some extra work, and having updates ignored is just about as likely to confuse someone as having them do the wrong thing. So I usually prefer to just tell people that a given object shouldn’t be messed with and hope they remember it.

- -
let object = Object.freeze({value: 5});
-object.value = 10;
-console.log(object.value);
-// → 5
- -

Why am I going out of my way to not change objects when the language is obviously expecting me to?

- -

Because it helps me understand my programs. This is about complexity management again. When the objects in my system are fixed, stable things, I can consider operations on them in isolation—moving to Alice’s house from a given start state always produces the same new state. When objects change over time, that adds a whole new dimension of complexity to this kind of reasoning.

- -

For a small system like the one we are building in this chapter, we could handle that bit of extra complexity. But the most important limit on what kind of systems we can build is how much we can understand. Anything that makes your code easier to understand makes it possible to build a more ambitious system.

- -

Unfortunately, although understanding a system built on persistent data structures is easier, designing one, especially when your programming language isn’t helping, can be a little harder. We’ll look for opportunities to use persistent data structures in this book, but we’ll also be using changeable ones.

- -

Simulation

- -

A delivery robot looks at the world and decides in which direction it wants to move. As such, we could say that a robot is a function that takes a VillageState object and returns the name of a nearby place.

- -

Because we want robots to be able to remember things, so that they can make and execute plans, we also pass them their memory and allow them to return a new memory. Thus, the thing a robot returns is an object containing both the direction it wants to move in and a memory value that will be given back to it the next time it is called.

- -
function runRobot(state, robot, memory) {
-  for (let turn = 0;; turn++) {
-    if (state.parcels.length == 0) {
-      console.log(`Done in ${turn} turns`);
-      break;
-    }
-    let action = robot(state, memory);
-    state = state.move(action.direction);
-    memory = action.memory;
-    console.log(`Moved to ${action.direction}`);
-  }
-}
- -

Consider what a robot has to do to “solve” a given state. It must pick up all parcels by visiting every location that has a parcel and deliver them by visiting every location that a parcel is addressed to, but only after picking up the parcel.

- -

What is the dumbest strategy that could possibly work? The robot could just walk in a random direction every turn. That means, with great likelihood, it will eventually run into all parcels and then also at some point reach the place where they should be delivered.

- -

Here’s what that could look like:

- -
function randomPick(array) {
-  let choice = Math.floor(Math.random() * array.length);
-  return array[choice];
-}
-
-function randomRobot(state) {
-  return {direction: randomPick(roadGraph[state.place])};
-}
- -

Remember that Math.random() returns a number between zero and one—but always below one. Multiplying such a number by the length of an array and then applying Math.floor to it gives us a random index for the array.

- -

Since this robot does not need to remember anything, it ignores its second argument (remember that JavaScript functions can be called with extra arguments without ill effects) and omits the memory property in its returned object.

- -

To put this sophisticated robot to work, we’ll first need a way to create a new state with some parcels. A static method (written here by directly adding a property to the constructor) is a good place to put that functionality.

- -
VillageState.random = function(parcelCount = 5) {
-  let parcels = [];
-  for (let i = 0; i < parcelCount; i++) {
-    let address = randomPick(Object.keys(roadGraph));
-    let place;
-    do {
-      place = randomPick(Object.keys(roadGraph));
-    } while (place == address);
-    parcels.push({place, address});
-  }
-  return new VillageState("Post Office", parcels);
-};
- -

We don’t want any parcels that are sent from the same place that they are addressed to. For this reason, the do loop keeps picking new places when it gets one that’s equal to the address.

- -

Let’s start up a virtual world.

- -
runRobot(VillageState.random(), randomRobot);
-// → Moved to Marketplace
-// → Moved to Town Hall
-// → …
-// → Done in 63 turns
- -

It takes the robot a lot of turns to deliver the parcels because it isn’t planning ahead very well. We’ll address that soon.

- -

For a more pleasant perspective on the simulation, you can use the runRobotAnimation function that’s available in this chapter’s programming environment. This runs the simulation, but instead of outputting text, it shows you the robot moving around the village map.

- -
runRobotAnimation(VillageState.random(), randomRobot);
- -

The way runRobotAnimation is implemented will remain a mystery for now, but after you’ve read the later chapters of this book, which discuss JavaScript integration in web browsers, you’ll be able to guess how it works.

- -

The mail truck’s route

- -

We should be able to do a lot better than the random robot. An easy improvement would be to take a hint from the way real-world mail delivery works. If we find a route that passes all places in the village, the robot could run that route twice, at which point it is guaranteed to be done. Here is one such route (starting from the post office):

- -
const mailRoute = [
-  "Alice's House", "Cabin", "Alice's House", "Bob's House",
-  "Town Hall", "Daria's House", "Ernie's House",
-  "Grete's House", "Shop", "Grete's House", "Farm",
-  "Marketplace", "Post Office"
-];
- -

To implement the route-following robot, we’ll need to make use of robot memory. The robot keeps the rest of its route in its memory and drops the first element every turn.

- -
function routeRobot(state, memory) {
-  if (memory.length == 0) {
-    memory = mailRoute;
-  }
-  return {direction: memory[0], memory: memory.slice(1)};
-}
- -

This robot is a lot faster already. It’ll take a maximum of 26 turns (twice the 13-step route) but usually less.

- -
runRobotAnimation(VillageState.random(), routeRobot, []);
- -

Pathfinding

- -

Still, I wouldn’t really call blindly following a fixed route intelligent behavior. The robot could work more efficiently if it adjusted its behavior to the actual work that needs to be done.

- -

To do that, it has to be able to deliberately move toward a given parcel or toward the location where a parcel has to be delivered. Doing that, even when the goal is more than one move away, will require some kind of route-finding function.

- -

The problem of finding a route through a graph is a typical search problem. We can tell whether a given solution (a route) is a valid solution, but we can’t directly compute the solution the way we could for 2 + 2. Instead, we have to keep creating potential solutions until we find one that works.

- -

The number of possible routes through a graph is infinite. But when searching for a route from A to B, we are interested only in the ones that start at A. We also don’t care about routes that visit the same place twice—those are definitely not the most efficient route anywhere. So that cuts down on the number of routes that the route finder has to consider.

- -

In fact, we are mostly interested in the shortest route. So we want to make sure we look at short routes before we look at longer ones. A good approach would be to “grow” routes from the starting point, exploring every reachable place that hasn’t been visited yet, until a route reaches the goal. That way, we’ll only explore routes that are potentially interesting, and we’ll find the shortest route (or one of the shortest routes, if there are more than one) to the goal.

- -

Here is a function that does this:

- -
function findRoute(graph, from, to) {
-  let work = [{at: from, route: []}];
-  for (let i = 0; i < work.length; i++) {
-    let {at, route} = work[i];
-    for (let place of graph[at]) {
-      if (place == to) return route.concat(place);
-      if (!work.some(w => w.at == place)) {
-        work.push({at: place, route: route.concat(place)});
-      }
-    }
-  }
-}
- -

The exploring has to be done in the right order—the places that were reached first have to be explored first. We can’t immediately explore a place as soon as we reach it because that would mean places reached from there would also be explored immediately, and so on, even though there may be other, shorter paths that haven’t yet been explored.

- -

Therefore, the function keeps a work list. This is an array of places that should be explored next, along with the route that got us there. It starts with just the start position and an empty route.

- -

The search then operates by taking the next item in the list and exploring that, which means all roads going from that place are looked at. If one of them is the goal, a finished route can be returned. Otherwise, if we haven’t looked at this place before, a new item is added to the list. If we have looked at it before, since we are looking at short routes first, we’ve found either a longer route to that place or one precisely as long as the existing one, and we don’t need to explore it.

- -

You can visually imagine this as a web of known routes crawling out from the start location, growing evenly on all sides (but never tangling back into itself). As soon as the first thread reaches the goal location, that thread is traced back to the start, giving us our route.

- -

Our code doesn’t handle the situation where there are no more work items on the work list because we know that our graph is connected, meaning that every location can be reached from all other locations. We’ll always be able to find a route between two points, and the search can’t fail.

- -
function goalOrientedRobot({place, parcels}, route) {
-  if (route.length == 0) {
-    let parcel = parcels[0];
-    if (parcel.place != place) {
-      route = findRoute(roadGraph, place, parcel.place);
-    } else {
-      route = findRoute(roadGraph, place, parcel.address);
-    }
-  }
-  return {direction: route[0], memory: route.slice(1)};
-}
- -

This robot uses its memory value as a list of directions to move in, just like the route-following robot. Whenever that list is empty, it has to figure out what to do next. It takes the first undelivered parcel in the set and, if that parcel hasn’t been picked up yet, plots a route toward it. If the parcel has been picked up, it still needs to be delivered, so the robot creates a route toward the delivery address instead.

- -

Let’s see how it does.

- -
runRobotAnimation(VillageState.random(),
-                  goalOrientedRobot, []);
- -

This robot usually finishes the task of delivering 5 parcels in about 16 turns. That’s slightly better than routeRobot but still definitely not optimal.

- -

Exercises

- -

Measuring a robot

- -

It’s hard to objectively compare robots by just letting them solve a few scenarios. Maybe one robot just happened to get easier tasks or the kind of tasks that it is good at, whereas the other didn’t.

- -

Write a function compareRobots that takes two robots (and their starting memory). It should generate 100 tasks and let each of the robots solve each of these tasks. When done, it should output the average number of steps each robot took per task.

- -

For the sake of fairness, make sure you give each task to both robots, rather than generating different tasks per robot.

- -
function compareRobots(robot1, memory1, robot2, memory2) {
-  // Your code here
-}
-
-compareRobots(routeRobot, [], goalOrientedRobot, []);
- -
- -

You’ll have to write a variant of the runRobot function that, instead of logging the events to the console, returns the number of steps the robot took to complete the task.

- -

Your measurement function can then, in a loop, generate new states and count the steps each of the robots takes. When it has generated enough measurements, it can use console.log to output the average for each robot, which is the total number of steps taken divided by the number of measurements.

- -
- -

Robot efficiency

- -

Can you write a robot that finishes the delivery task faster than goalOrientedRobot? If you observe that robot’s behavior, what obviously stupid things does it do? How could those be improved?

- -

If you solved the previous exercise, you might want to use your compareRobots function to verify whether you improved the robot.

- -
// Your code here
-
-runRobotAnimation(VillageState.random(), yourRobot, memory);
- -
- -

The main limitation of goalOrientedRobot is that it considers only one parcel at a time. It will often walk back and forth across the village because the parcel it happens to be looking at happens to be at the other side of the map, even if there are others much closer.

- -

One possible solution would be to compute routes for all packages and then take the shortest one. Even better results can be obtained, if there are multiple shortest routes, by preferring the ones that go to pick up a package instead of delivering a package.

- -
- -

Persistent group

- -

Most data structures provided in a standard JavaScript environment aren’t very well suited for persistent use. Arrays have slice and concat methods, which allow us to easily create new arrays without damaging the old one. But Set, for example, has no methods for creating a new set with an item added or removed.

- -

Write a new class PGroup, similar to the Group class from Chapter 6, which stores a set of values. Like Group, it has add, delete, and has methods.

- -

Its add method, however, should return a new PGroup instance with the given member added and leave the old one unchanged. Similarly, delete creates a new instance without a given member.

- -

The class should work for values of any type, not just strings. It does not have to be efficient when used with large amounts of values.

- -

The constructor shouldn’t be part of the class’s interface (though you’ll definitely want to use it internally). Instead, there is an empty instance, PGroup.empty, that can be used as a starting value.

- -

Why do you need only one PGroup.empty value, rather than having a function that creates a new, empty map every time?

- -
class PGroup {
-  // Your code here
-}
-
-let a = PGroup.empty.add("a");
-let ab = a.add("b");
-let b = ab.delete("a");
-
-console.log(b.has("b"));
-// → true
-console.log(a.has("b"));
-// → false
-console.log(b.has("a"));
-// → false
- -
- -

The most convenient way to represent the set of member values is still as an array since arrays are easy to copy.

- -

When a value is added to the group, you can create a new group with a copy of the original array that has the value added (for example, using concat). When a value is deleted, you filter it from the array.

- -

The class’s constructor can take such an array as argument and store it as the instance’s (only) property. This array is never updated.

- -

To add a property (empty) to a constructor that is not a method, you have to add it to the constructor after the class definition, as a regular property.

- -

You need only one empty instance because all empty groups are the same and instances of the class don’t change. You can create many different groups from that single empty group without affecting it.

- -
-
diff --git a/docs/08_error.html b/docs/08_error.html deleted file mode 100644 index 109e7120b..000000000 --- a/docs/08_error.html +++ /dev/null @@ -1,485 +0,0 @@ - - - - - Bugs and Errors :: Eloquent JavaScript - - - - - - -
- - -

Chapter 8Bugs and Errors

- -
- -

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

- -
Brian Kernighan and P.J. Plauger, The Elements of Programming Style
- -
Picture of a collection of bugs
- -

Flaws in computer programs are usually called bugs. It makes programmers feel good to imagine them as little things that just happen to crawl into our work. In reality, of course, we put them there ourselves.

- -

If a program is crystallized thought, you can roughly categorize bugs into those caused by the thoughts being confused and those caused by mistakes introduced while converting a thought to code. The former type is generally harder to diagnose and fix than the latter.

- -

Language

- -

Many mistakes could be pointed out to us automatically by the computer, if it knew enough about what we’re trying to do. But here JavaScript’s looseness is a hindrance. Its concept of bindings and properties is vague enough that it will rarely catch typos before actually running the program. And even then, it allows you to do some clearly nonsensical things without complaint, such as computing true * "monkey".

- -

There are some things that JavaScript does complain about. Writing a program that does not follow the language’s grammar will immediately make the computer complain. Other things, such as calling something that’s not a function or looking up a property on an undefined value, will cause an error to be reported when the program tries to perform the action.

- -

But often, your nonsense computation will merely produce NaN (not a number) or an undefined value, while the program happily continues, convinced that it’s doing something meaningful. The mistake will manifest itself only later, after the bogus value has traveled through several functions. It might not trigger an error at all but silently cause the program’s output to be wrong. Finding the source of such problems can be difficult.

- -

The process of finding mistakes—bugs—in programs is called debugging.

- -

Strict mode

- -

JavaScript can be made a little stricter by enabling strict mode. This is done by putting the string "use strict" at the top of a file or a function body. Here’s an example:

- -
function canYouSpotTheProblem() {
-  "use strict";
-  for (counter = 0; counter < 10; counter++) {
-    console.log("Happy happy");
-  }
-}
-
-canYouSpotTheProblem();
-// → ReferenceError: counter is not defined
- -

Normally, when you forget to put let in front of your binding, as with counter in the example, JavaScript quietly creates a global binding and uses that. In strict mode, an error is reported instead. This is very helpful. It should be noted, though, that this doesn’t work when the binding in question already exists as a global binding. In that case, the loop will still quietly overwrite the value of the binding.

- -

Another change in strict mode is that the this binding holds the value undefined in functions that are not called as methods. When making such a call outside of strict mode, this refers to the global scope object, which is an object whose properties are the global bindings. So if you accidentally call a method or constructor incorrectly in strict mode, JavaScript will produce an error as soon as it tries to read something from this, rather than happily writing to the global scope.

- -

For example, consider the following code, which calls a constructor function without the new keyword so that its this will not refer to a newly constructed object:

- -
function Person(name) { this.name = name; }
-let ferdinand = Person("Ferdinand"); // oops
-console.log(name);
-// → Ferdinand
- -

So the bogus call to Person succeeded but returned an undefined value and created the global binding name. In strict mode, the result is different.

- -
"use strict";
-function Person(name) { this.name = name; }
-let ferdinand = Person("Ferdinand"); // forgot new
-// → TypeError: Cannot set property 'name' of undefined
- -

We are immediately told that something is wrong. This is helpful.

- -

Fortunately, constructors created with the class notation will always complain if they are called without new, making this less of a problem even in non-strict mode.

- -

Strict mode does a few more things. It disallows giving a function multiple parameters with the same name and removes certain problematic language features entirely (such as the with statement, which is so wrong it is not further discussed in this book).

- -

In short, putting "use strict" at the top of your program rarely hurts and might help you spot a problem.

- -

Types

- -

Some languages want to know the types of all your bindings and expressions before even running a program. They will tell you right away when a type is used in an inconsistent way. JavaScript considers types only when actually running the program, and even there often tries to implicitly convert values to the type it expects, so it’s not much help.

- -

Still, types provide a useful framework for talking about programs. A lot of mistakes come from being confused about the kind of value that goes into or comes out of a function. If you have that information written down, you’re less likely to get confused.

- -

You could add a comment like the following before the goalOrientedRobot function from the previous chapter to describe its type:

- -
// (VillageState, Array) → {direction: string, memory: Array}
-function goalOrientedRobot(state, memory) {
-  // ...
-}
- -

There are a number of different conventions for annotating JavaScript programs with types.

- -

One thing about types is that they need to introduce their own complexity to be able to describe enough code to be useful. What do you think would be the type of the randomPick function that returns a random element from an array? You’d need to introduce a type -variable, T, which can stand in for any type, so that you can give randomPick a type like ([T]) → T (function from an array of Ts to a T).

- -

When the types of a program are known, it is possible for the computer to check them for you, pointing out mistakes before the program is run. There are several JavaScript dialects that add types to the language and check them. The most popular one is called TypeScript. If you are interested in adding more rigor to your programs, I recommend you give it a try.

- -

In this book, we’ll continue using raw, dangerous, untyped JavaScript code.

- -

Testing

- -

If the language is not going to do much to help us find mistakes, we’ll have to find them the hard way: by running the program and seeing whether it does the right thing.

- -

Doing this by hand, again and again, is a really bad idea. Not only is it annoying, it also tends to be ineffective since it takes too much time to exhaustively test everything every time you make a change.

- -

Computers are good at repetitive tasks, and testing is the ideal repetitive task. Automated testing is the process of writing a program that tests another program. Writing tests is a bit more work than testing manually, but once you’ve done it, you gain a kind of superpower: it takes you only a few seconds to verify that your program still behaves properly in all the situations you wrote tests for. When you break something, you’ll immediately notice, rather than randomly running into it at some later time.

- -

Tests usually take the form of little labeled programs that verify some aspect of your code. For example, a set of tests for the (standard, probably already tested by someone else) toUpperCase method might look like this:

- -
function test(label, body) {
-  if (!body()) console.log(`Failed: ${label}`);
-}
-
-test("convert Latin text to uppercase", () => {
-  return "hello".toUpperCase() == "HELLO";
-});
-test("convert Greek text to uppercase", () => {
-  return "Χαίρετε".toUpperCase() == "ΧΑΊΡΕΤΕ";
-});
-test("don't convert case-less characters", () => {
-  return "مرحبا".toUpperCase() == "مرحبا";
-});
- -

Writing tests like this tends to produce rather repetitive, awkward code. Fortunately, there exist pieces of software that help you build and run collections of tests (test suites) by providing a language (in the form of functions and methods) suited to expressing tests and by outputting informative information when a test fails. These are usually called test runners.

- -

Some code is easier to test than other code. Generally, the more external objects that the code interacts with, the harder it is to set up the context in which to test it. The style of programming shown in the previous chapter, which uses self-contained persistent values rather than changing objects, tends to be easy to test.

- -

Debugging

- -

Once you notice there is something wrong with your program because it misbehaves or produces errors, the next step is to figure out what the problem is.

- -

Sometimes it is obvious. The error message will point at a specific line of your program, and if you look at the error description and that line of code, you can often see the problem.

- -

But not always. Sometimes the line that triggered the problem is simply the first place where a flaky value produced elsewhere gets used in an invalid way. If you have been solving the exercises in earlier chapters, you will probably have already experienced such situations.

- -

The following example program tries to convert a whole number to a string in a given base (decimal, binary, and so on) by repeatedly picking out the last digit and then dividing the number to get rid of this digit. But the strange output that it currently produces suggests that it has a bug.

- -
function numberToString(n, base = 10) {
-  let result = "", sign = "";
-  if (n < 0) {
-    sign = "-";
-    n = -n;
-  }
-  do {
-    result = String(n % base) + result;
-    n /= base;
-  } while (n > 0);
-  return sign + result;
-}
-console.log(numberToString(13, 10));
-// → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…
- -

Even if you see the problem already, pretend for a moment that you don’t. We know that our program is malfunctioning, and we want to find out why.

- -

This is where you must resist the urge to start making random changes to the code to see whether that makes it better. Instead, think. Analyze what is happening and come up with a theory of why it might be happening. Then, make additional observations to test this theory—or, if you don’t yet have a theory, make additional observations to help you come up with one.

- -

Putting a few strategic console.log calls into the program is a good way to get additional information about what the program is doing. In this case, we want n to take the values 13, 1, and then 0. Let’s write out its value at the start of the loop.

- -
13
-1.3
-0.13
-0.013
-…
-1.5e-323
- -

Right. Dividing 13 by 10 does not produce a whole number. Instead of n /= base, what we actually want is n = Math.floor(n / base) so that the number is properly “shifted” to the right.

- -

An alternative to using console.log to peek into the program’s behavior is to use the debugger capabilities of your browser. Browsers come with the ability to set a breakpoint on a specific line of your code. When the execution of the program reaches a line with a breakpoint, it is paused, and you can inspect the values of bindings at that point. I won’t go into details, as debuggers differ from browser to browser, but look in your browser’s developer -tools or search the Web for more information.

- -

Another way to set a breakpoint is to include a debugger statement (consisting of simply that keyword) in your program. If the developer tools of your browser are active, the program will pause whenever it reaches such a statement.

- -

Error propagation

- -

Not all problems can be prevented by the programmer, unfortunately. If your program communicates with the outside world in any way, it is possible to get malformed input, to become overloaded with work, or to have the network fail.

- -

If you’re programming only for yourself, you can afford to just ignore such problems until they occur. But if you build something that is going to be used by anybody else, you usually want the program to do better than just crash. Sometimes the right thing to do is take the bad input in stride and continue running. In other cases, it is better to report to the user what went wrong and then give up. But in either situation, the program has to actively do something in response to the problem.

- -

Say you have a function promptNumber that asks the user for a number and returns it. What should it return if the user inputs “orange”?

- -

One option is to make it return a special value. Common choices for such values are null, undefined, or -1.

- -
function promptNumber(question) {
-  let result = Number(prompt(question));
-  if (Number.isNaN(result)) return null;
-  else return result;
-}
-
-console.log(promptNumber("How many trees do you see?"));
- -

Now any code that calls promptNumber must check whether an actual number was read and, failing that, must somehow recover—maybe by asking again or by filling in a default value. Or it could again return a special value to its caller to indicate that it failed to do what it was asked.

- -

In many situations, mostly when errors are common and the caller should be explicitly taking them into account, returning a special value is a good way to indicate an error. It does, however, have its downsides. First, what if the function can already return every possible kind of value? In such a function, you’ll have to do something like wrap the result in an object to be able to distinguish success from failure.

- -
function lastElement(array) {
-  if (array.length == 0) {
-    return {failed: true};
-  } else {
-    return {element: array[array.length - 1]};
-  }
-}
- -

The second issue with returning special values is that it can lead to awkward code. If a piece of code calls promptNumber 10 times, it has to check 10 times whether null was returned. And if its response to finding null is to simply return null itself, callers of the function will in turn have to check for it, and so on.

- -

Exceptions

- -

When a function cannot proceed normally, what we would like to do is just stop what we are doing and immediately jump to a place that knows how to handle the problem. This is what exception handling does.

- -

Exceptions are a mechanism that makes it possible for code that runs into a problem to raise (or throw) an exception. An exception can be any value. Raising one somewhat resembles a super-charged return from a function: it jumps out of not just the current function but also its callers, all the way down to the first call that started the current execution. This is called unwinding the -stack. You may remember the stack of function calls that was mentioned in Chapter 3. An exception zooms down this stack, throwing away all the call contexts it encounters.

- -

If exceptions always zoomed right down to the bottom of the stack, they would not be of much use. They’d just provide a novel way to blow up your program. Their power lies in the fact that you can set “obstacles” along the stack to catch the exception as it is zooming down. Once you’ve caught an exception, you can do something with it to address the problem and then continue to run the program.

- -

Here’s an example:

- -
function promptDirection(question) {
-  let result = prompt(question);
-  if (result.toLowerCase() == "left") return "L";
-  if (result.toLowerCase() == "right") return "R";
-  throw new Error("Invalid direction: " + result);
-}
-
-function look() {
-  if (promptDirection("Which way?") == "L") {
-    return "a house";
-  } else {
-    return "two angry bears";
-  }
-}
-
-try {
-  console.log("You see", look());
-} catch (error) {
-  console.log("Something went wrong: " + error);
-}
- -

The throw keyword is used to raise an exception. Catching one is done by wrapping a piece of code in a try block, followed by the keyword catch. When the code in the try block causes an exception to be raised, the catch block is evaluated, with the name in parentheses bound to the exception value. After the catch block finishes—or if the try block finishes without problems—the program proceeds beneath the entire try/catch statement.

- -

In this case, we used the Error constructor to create our exception value. This is a standard JavaScript constructor that creates an object with a message property. In most JavaScript environments, instances of this constructor also gather information about the call stack that existed when the exception was created, a so-called stack trace. This information is stored in the stack property and can be helpful when trying to debug a problem: it tells us the function where the problem occurred and which functions made the failing call.

- -

Note that the look function completely ignores the possibility that promptDirection might go wrong. This is the big advantage of exceptions: error-handling code is necessary only at the point where the error occurs and at the point where it is handled. The functions in between can forget all about it.

- -

Well, almost...

- -

Cleaning up after exceptions

- -

The effect of an exception is another kind of control flow. Every action that might cause an exception, which is pretty much every function call and property access, might cause control to suddenly leave your code.

- -

This means when code has several side effects, even if its “regular” control flow looks like they’ll always all happen, an exception might prevent some of them from taking place.

- -

Here is some really bad banking code.

- -
const accounts = {
-  a: 100,
-  b: 0,
-  c: 20
-};
-
-function getAccount() {
-  let accountName = prompt("Enter an account name");
-  if (!accounts.hasOwnProperty(accountName)) {
-    throw new Error(`No such account: ${accountName}`);
-  }
-  return accountName;
-}
-
-function transfer(from, amount) {
-  if (accounts[from] < amount) return;
-  accounts[from] -= amount;
-  accounts[getAccount()] += amount;
-}
- -

The transfer function transfers a sum of money from a given account to another, asking for the name of the other account in the process. If given an invalid account name, getAccount throws an exception.

- -

But transfer first removes the money from the account and then calls getAccount before it adds it to another account. If it is broken off by an exception at that point, it’ll just make the money disappear.

- -

That code could have been written a little more intelligently, for example by calling getAccount before it starts moving money around. But often problems like this occur in more subtle ways. Even functions that don’t look like they will throw an exception might do so in exceptional circumstances or when they contain a programmer mistake.

- -

One way to address this is to use fewer side effects. Again, a programming style that computes new values instead of changing existing data helps. If a piece of code stops running in the middle of creating a new value, no one ever sees the half-finished value, and there is no problem.

- -

But that isn’t always practical. So there is another feature that try statements have. They may be followed by a finally block either instead of or in addition to a catch block. A finally block says “no matter what happens, run this code after trying to run the code in the try block.”

- -
function transfer(from, amount) {
-  if (accounts[from] < amount) return;
-  let progress = 0;
-  try {
-    accounts[from] -= amount;
-    progress = 1;
-    accounts[getAccount()] += amount;
-    progress = 2;
-  } finally {
-    if (progress == 1) {
-      accounts[from] += amount;
-    }
-  }
-}
- -

This version of the function tracks its progress, and if, when leaving, it notices that it was aborted at a point where it had created an inconsistent program state, it repairs the damage it did.

- -

Note that even though the finally code is run when an exception is thrown in the try block, it does not interfere with the exception. After the finally block runs, the stack continues unwinding.

- -

Writing programs that operate reliably even when exceptions pop up in unexpected places is hard. Many people simply don’t bother, and because exceptions are typically reserved for exceptional circumstances, the problem may occur so rarely that it is never even noticed. Whether that is a good thing or a really bad thing depends on how much damage the software will do when it fails.

- -

Selective catching

- -

When an exception makes it all the way to the bottom of the stack without being caught, it gets handled by the environment. What this means differs between environments. In browsers, a description of the error typically gets written to the JavaScript console (reachable through the browser’s Tools or Developer menu). Node.js, the browserless JavaScript environment we will discuss in Chapter 20, is more careful about data corruption. It aborts the whole process when an unhandled exception occurs.

- -

For programmer mistakes, just letting the error go through is often the best you can do. An unhandled exception is a reasonable way to signal a broken program, and the JavaScript console will, on modern browsers, provide you with some information about which function calls were on the stack when the problem occurred.

- -

For problems that are expected to happen during routine use, crashing with an unhandled exception is a terrible strategy.

- -

Invalid uses of the language, such as referencing a nonexistent binding, looking up a property on null, or calling something that’s not a function, will also result in exceptions being raised. Such exceptions can also be caught.

- -

When a catch body is entered, all we know is that something in our try body caused an exception. But we don’t know what did or which exception it caused.

- -

JavaScript (in a rather glaring omission) doesn’t provide direct support for selectively catching exceptions: either you catch them all or you don’t catch any. This makes it tempting to assume that the exception you get is the one you were thinking about when you wrote the catch block.

- -

But it might not be. Some other assumption might be violated, or you might have introduced a bug that is causing an exception. Here is an example that attempts to keep on calling promptDirection until it gets a valid answer:

- -
for (;;) {
-  try {
-    let dir = promtDirection("Where?"); // ← typo!
-    console.log("You chose ", dir);
-    break;
-  } catch (e) {
-    console.log("Not a valid direction. Try again.");
-  }
-}
- -

The for (;;) construct is a way to intentionally create a loop that doesn’t terminate on its own. We break out of the loop only when a valid direction is given. But we misspelled promptDirection, which will result in an “undefined variable” error. Because the catch block completely ignores its exception value (e), assuming it knows what the problem is, it wrongly treats the binding error as indicating bad input. Not only does this cause an infinite loop, it “buries” the useful error message about the misspelled binding.

- -

As a general rule, don’t blanket-catch exceptions unless it is for the purpose of “routing” them somewhere—for example, over the network to tell another system that our program crashed. And even then, think carefully about how you might be hiding information.

- -

So we want to catch a specific kind of exception. We can do this by checking in the catch block whether the exception we got is the one we are interested in and rethrowing it otherwise. But how do we recognize an exception?

- -

We could compare its message property against the error message we happen to expect. But that’s a shaky way to write code—we’d be using information that’s intended for human consumption (the message) to make a programmatic decision. As soon as someone changes (or translates) the message, the code will stop working.

- -

Rather, let’s define a new type of error and use instanceof to identify it.

- -
class InputError extends Error {}
-
-function promptDirection(question) {
-  let result = prompt(question);
-  if (result.toLowerCase() == "left") return "L";
-  if (result.toLowerCase() == "right") return "R";
-  throw new InputError("Invalid direction: " + result);
-}
- -

The new error class extends Error. It doesn’t define its own constructor, which means that it inherits the Error constructor, which expects a string message as argument. In fact, it doesn’t define anything at all—the class is empty. InputError objects behave like Error objects, except that they have a different class by which we can recognize them.

- -

Now the loop can catch these more carefully.

- -
for (;;) {
-  try {
-    let dir = promptDirection("Where?");
-    console.log("You chose ", dir);
-    break;
-  } catch (e) {
-    if (e instanceof InputError) {
-      console.log("Not a valid direction. Try again.");
-    } else {
-      throw e;
-    }
-  }
-}
- -

This will catch only instances of InputError and let unrelated exceptions through. If you reintroduce the typo, the undefined binding error will be properly reported.

- -

Assertions

- -

Assertions are checks inside a program that verify that something is the way it is supposed to be. They are used not to handle situations that can come up in normal operation but to find programmer mistakes.

- -

If, for example, firstElement is described as a function that should never be called on empty arrays, we might write it like this:

- -
function firstElement(array) {
-  if (array.length == 0) {
-    throw new Error("firstElement called with []");
-  }
-  return array[0];
-}
- -

Now, instead of silently returning undefined (which you get when reading an array property that does not exist), this will loudly blow up your program as soon as you misuse it. This makes it less likely for such mistakes to go unnoticed and easier to find their cause when they occur.

- -

I do not recommend trying to write assertions for every possible kind of bad input. That’d be a lot of work and would lead to very noisy code. You’ll want to reserve them for mistakes that are easy to make (or that you find yourself making).

- -

Summary

- -

Mistakes and bad input are facts of life. An important part of programming is finding, diagnosing, and fixing bugs. Problems can become easier to notice if you have an automated test suite or add assertions to your programs.

- -

Problems caused by factors outside the program’s control should usually be handled gracefully. Sometimes, when the problem can be handled locally, special return values are a good way to track them. Otherwise, exceptions may be preferable.

- -

Throwing an exception causes the call stack to be unwound until the next enclosing try/catch block or until the bottom of the stack. The exception value will be given to the catch block that catches it, which should verify that it is actually the expected kind of exception and then do something with it. To help address the unpredictable control flow caused by exceptions, finally blocks can be used to ensure that a piece of code always runs when a block finishes.

- -

Exercises

- -

Retry

- -

Say you have a function primitiveMultiply that in 20 percent of cases multiplies two numbers and in the other 80 percent of cases raises an exception of type MultiplicatorUnitFailure. Write a function that wraps this clunky function and just keeps trying until a call succeeds, after which it returns the result.

- -

Make sure you handle only the exceptions you are trying to handle.

- -
class MultiplicatorUnitFailure extends Error {}
-
-function primitiveMultiply(a, b) {
-  if (Math.random() < 0.2) {
-    return a * b;
-  } else {
-    throw new MultiplicatorUnitFailure("Klunk");
-  }
-}
-
-function reliableMultiply(a, b) {
-  // Your code here.
-}
-
-console.log(reliableMultiply(8, 8));
-// → 64
- -
- -

The call to primitiveMultiply should definitely happen in a try block. The corresponding catch block should rethrow the exception when it is not an instance of MultiplicatorUnitFailure and ensure the call is retried when it is.

- -

To do the retrying, you can either use a loop that stops only when a call succeeds—as in the look example earlier in this chapter—or use recursion and hope you don’t get a string of failures so long that it overflows the stack (which is a pretty safe bet).

- -
- -

The locked box

- -

Consider the following (rather contrived) object:

- -
const box = {
-  locked: true,
-  unlock() { this.locked = false; },
-  lock() { this.locked = true;  },
-  _content: [],
-  get content() {
-    if (this.locked) throw new Error("Locked!");
-    return this._content;
-  }
-};
- -

It is a box with a lock. There is an array in the box, but you can get at it only when the box is unlocked. Directly accessing the private _content property is forbidden.

- -

Write a function called withBoxUnlocked that takes a function value as argument, unlocks the box, runs the function, and then ensures that the box is locked again before returning, regardless of whether the argument function returned normally or threw an exception.

- -
const box = {
-  locked: true,
-  unlock() { this.locked = false; },
-  lock() { this.locked = true;  },
-  _content: [],
-  get content() {
-    if (this.locked) throw new Error("Locked!");
-    return this._content;
-  }
-};
-
-function withBoxUnlocked(body) {
-  // Your code here.
-}
-
-withBoxUnlocked(function() {
-  box.content.push("gold piece");
-});
-
-try {
-  withBoxUnlocked(function() {
-    throw new Error("Pirates on the horizon! Abort!");
-  });
-} catch (e) {
-  console.log("Error raised: " + e);
-}
-console.log(box.locked);
-// → true
- -

For extra points, make sure that if you call withBoxUnlocked when the box is already unlocked, the box stays unlocked.

- -
- -

This exercise calls for a finally block. Your function should first unlock the box and then call the argument function from inside a try body. The finally block after it should lock the box again.

- -

To make sure we don’t lock the box when it wasn’t already locked, check its lock at the start of the function and unlock and lock it only when it started out locked.

- -
-
diff --git a/docs/09_regexp.html b/docs/09_regexp.html deleted file mode 100644 index 7c2b5b7d8..000000000 --- a/docs/09_regexp.html +++ /dev/null @@ -1,853 +0,0 @@ - - - - - Regular Expressions :: Eloquent JavaScript - - - - - - -
- - -

Chapter 9Regular Expressions

- -
- -

Some people, when confronted with a problem, think ‘I know, I’ll use regular expressions.’ Now they have two problems.

- -
Jamie Zawinski
- -
- -
- -

Yuan-Ma said, ‘When you cut against the grain of the wood, much strength is needed. When you program against the grain of the problem, much code is needed.’

- -
Master Yuan-Ma, The Book of Programming
- -
A railroad diagram
- -

Programming tools and techniques survive and spread in a chaotic, evolutionary way. It’s not always the pretty or brilliant ones that win but rather the ones that function well enough within the right niche or that happen to be integrated with another successful piece of technology.

- -

In this chapter, I will discuss one such tool, regular -expressions. Regular expressions are a way to describe patterns in string data. They form a small, separate language that is part of JavaScript and many other languages and systems.

- -

Regular expressions are both terribly awkward and extremely useful. Their syntax is cryptic, and the programming interface JavaScript provides for them is clumsy. But they are a powerful tool for inspecting and processing strings. Properly understanding regular expressions will make you a more effective programmer.

- -

Creating a regular expression

- -

A regular expression is a type of object. It can be either constructed with the RegExp constructor or written as a literal value by enclosing a pattern in forward slash (/) characters.

- -
let re1 = new RegExp("abc");
-let re2 = /abc/;
- -

Both of those regular expression objects represent the same pattern: an a character followed by a b followed by a c.

- -

When using the RegExp constructor, the pattern is written as a normal string, so the usual rules apply for backslashes.

- -

The second notation, where the pattern appears between slash characters, treats backslashes somewhat differently. First, since a forward slash ends the pattern, we need to put a backslash before any forward slash that we want to be part of the pattern. In addition, backslashes that aren’t part of special character codes (like \n) will be preserved, rather than ignored as they are in strings, and change the meaning of the pattern. Some characters, such as question marks and plus signs, have special meanings in regular expressions and must be preceded by a backslash if they are meant to represent the character itself.

- -
let eighteenPlus = /eighteen\+/;
- -

Testing for matches

- -

Regular expression objects have a number of methods. The simplest one is test. If you pass it a string, it will return a Boolean telling you whether the string contains a match of the pattern in the expression.

- -
console.log(/abc/.test("abcde"));
-// → true
-console.log(/abc/.test("abxde"));
-// → false
- -

A regular expression consisting of only nonspecial characters simply represents that sequence of characters. If abc occurs anywhere in the string we are testing against (not just at the start), test will return true.

- -

Sets of characters

- -

Finding out whether a string contains abc could just as well be done with a call to indexOf. Regular expressions allow us to express more complicated patterns.

- -

Say we want to match any number. In a regular expression, putting a set of characters between square brackets makes that part of the expression match any of the characters between the brackets.

- -

Both of the following expressions match all strings that contain a digit:

- -
console.log(/[0123456789]/.test("in 1992"));
-// → true
-console.log(/[0-9]/.test("in 1992"));
-// → true
- -

Within square brackets, a hyphen (-) between two characters can be used to indicate a range of characters, where the ordering is determined by the character’s Unicode number. Characters 0 to 9 sit right next to each other in this ordering (codes 48 to 57), so [0-9] covers all of them and matches any digit.

- -

A number of common character groups have their own built-in shortcuts. Digits are one of them: \d means the same thing as [0-9].

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
\dAny digit character
\wAn alphanumeric character (“word character”)
\sAny whitespace character (space, tab, newline, and similar)
\DA character that is not a digit
\WA nonalphanumeric character
\SA nonwhitespace character
.Any character except for newline
- -

So you could match a date and time format like 01-30-2003 15:20 with the following expression:

- -
let dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/;
-console.log(dateTime.test("01-30-2003 15:20"));
-// → true
-console.log(dateTime.test("30-jan-2003 15:20"));
-// → false
- -

That looks completely awful, doesn’t it? Half of it is backslashes, producing a background noise that makes it hard to spot the actual pattern expressed. We’ll see a slightly improved version of this expression later.

- -

These backslash codes can also be used inside square brackets. For example, [\d.] means any digit or a period character. But the period itself, between square brackets, loses its special meaning. The same goes for other special characters, such as +.

- -

To invert a set of characters—that is, to express that you want to match any character except the ones in the set—you can write a caret (^) character after the opening bracket.

- -
let notBinary = /[^01]/;
-console.log(notBinary.test("1100100010100110"));
-// → false
-console.log(notBinary.test("1100100010200110"));
-// → true
- -

Repeating parts of a pattern

- -

We now know how to match a single digit. What if we want to match a whole number—a sequence of one or more digits?

- -

When you put a plus sign (+) after something in a regular expression, it indicates that the element may be repeated more than once. Thus, /\d+/ matches one or more digit characters.

- -
console.log(/'\d+'/.test("'123'"));
-// → true
-console.log(/'\d+'/.test("''"));
-// → false
-console.log(/'\d*'/.test("'123'"));
-// → true
-console.log(/'\d*'/.test("''"));
-// → true
- -

The star (*) has a similar meaning but also allows the pattern to match zero times. Something with a star after it never prevents a pattern from matching—it’ll just match zero instances if it can’t find any suitable text to match.

- -

A question mark makes a part of a pattern optional, meaning it may occur zero times or one time. In the following example, the u character is allowed to occur, but the pattern also matches when it is missing.

- -
let neighbor = /neighbou?r/;
-console.log(neighbor.test("neighbour"));
-// → true
-console.log(neighbor.test("neighbor"));
-// → true
- -

To indicate that a pattern should occur a precise number of times, use braces. Putting {4} after an element, for example, requires it to occur exactly four times. It is also possible to specify a range this way: {2,4} means the element must occur at least twice and at most four times.

- -

Here is another version of the date and time pattern that allows both single- and double-digit days, months, and hours. It is also slightly easier to decipher.

- -
let dateTime = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/;
-console.log(dateTime.test("1-30-2003 8:45"));
-// → true
- -

You can also specify open-ended ranges when using braces by omitting the number after the comma. So, {5,} means five or more times.

- -

Grouping subexpressions

- -

To use an operator like * or + on more than one element at a time, you have to use parentheses. A part of a regular expression that is enclosed in parentheses counts as a single element as far as the operators following it are concerned.

- -
let cartoonCrying = /boo+(hoo+)+/i;
-console.log(cartoonCrying.test("Boohoooohoohooo"));
-// → true
- -

The first and second + characters apply only to the second o in boo and hoo, respectively. The third + applies to the whole group (hoo+), matching one or more sequences like that.

- -

The i at the end of the expression in the example makes this regular expression case insensitive, allowing it to match the uppercase B in the input string, even though the pattern is itself all lowercase.

- -

Matches and groups

- -

The test method is the absolute simplest way to match a regular expression. It tells you only whether it matched and nothing else. Regular expressions also have an exec (execute) method that will return null if no match was found and return an object with information about the match otherwise.

- -
let match = /\d+/.exec("one two 100");
-console.log(match);
-// → ["100"]
-console.log(match.index);
-// → 8
- -

An object returned from exec has an index property that tells us where in the string the successful match begins. Other than that, the object looks like (and in fact is) an array of strings, whose first element is the string that was matched. In the previous example, this is the sequence of digits that we were looking for.

- -

String values have a match method that behaves similarly.

- -
console.log("one two 100".match(/\d+/));
-// → ["100"]
- -

When the regular expression contains subexpressions grouped with parentheses, the text that matched those groups will also show up in the array. The whole match is always the first element. The next element is the part matched by the first group (the one whose opening parenthesis comes first in the expression), then the second group, and so on.

- -
let quotedText = /'([^']*)'/;
-console.log(quotedText.exec("she said 'hello'"));
-// → ["'hello'", "hello"]
- -

When a group does not end up being matched at all (for example, when followed by a question mark), its position in the output array will hold undefined. Similarly, when a group is matched multiple times, only the last match ends up in the array.

- -
console.log(/bad(ly)?/.exec("bad"));
-// → ["bad", undefined]
-console.log(/(\d)+/.exec("123"));
-// → ["123", "3"]
- -

Groups can be useful for extracting parts of a string. If we don’t just want to verify whether a string contains a date but also extract it and construct an object that represents it, we can wrap parentheses around the digit patterns and directly pick the date out of the result of exec.

- -

But first we’ll take a brief detour, in which we discuss the built-in way to represent date and time values in JavaScript.

- -

The Date class

- -

JavaScript has a standard class for representing dates—or, rather, points in time. It is called Date. If you simply create a date object using new, you get the current date and time.

- -
console.log(new Date());
-// → Mon Nov 13 2017 16:19:11 GMT+0100 (CET)
- -

You can also create an object for a specific time.

- -
console.log(new Date(2009, 11, 9));
-// → Wed Dec 09 2009 00:00:00 GMT+0100 (CET)
-console.log(new Date(2009, 11, 9, 12, 59, 59, 999));
-// → Wed Dec 09 2009 12:59:59 GMT+0100 (CET)
- -

JavaScript uses a convention where month numbers start at zero (so December is 11), yet day numbers start at one. This is confusing and silly. Be careful.

- -

The last four arguments (hours, minutes, seconds, and milliseconds) are optional and taken to be zero when not given.

- -

Timestamps are stored as the number of milliseconds since the start of 1970, in the UTC time zone. This follows a convention set by “Unix time”, which was invented around that time. You can use negative numbers for times before 1970. The getTime method on a date object returns this number. It is big, as you can imagine.

- -
console.log(new Date(2013, 11, 19).getTime());
-// → 1387407600000
-console.log(new Date(1387407600000));
-// → Thu Dec 19 2013 00:00:00 GMT+0100 (CET)
- -

If you give the Date constructor a single argument, that argument is treated as such a millisecond count. You can get the current millisecond count by creating a new Date object and calling getTime on it or by calling the Date.now function.

- -

Date objects provide methods such as getFullYear, getMonth, getDate, getHours, getMinutes, and getSeconds to extract their components. Besides getFullYear there’s also getYear, which gives you the year minus 1900 (98 or 119) and is mostly useless.

- -

Putting parentheses around the parts of the expression that we are interested in, we can now create a date object from a string.

- -
function getDate(string) {
-  let [_, month, day, year] =
-    /(\d{1,2})-(\d{1,2})-(\d{4})/.exec(string);
-  return new Date(year, month - 1, day);
-}
-console.log(getDate("1-30-2003"));
-// → Thu Jan 30 2003 00:00:00 GMT+0100 (CET)
- -

The _ (underscore) binding is ignored and used only to skip the full match element in the array returned by exec.

- -

Word and string boundaries

- -

Unfortunately, getDate will also happily extract the nonsensical date 00-1-3000 from the string "100-1-30000". A match may happen anywhere in the string, so in this case, it’ll just start at the second character and end at the second-to-last character.

- -

If we want to enforce that the match must span the whole string, we can add the markers ^ and $. The caret matches the start of the input string, whereas the dollar sign matches the end. So, /^\d+$/ matches a string consisting entirely of one or more digits, /^!/ matches any string that starts with an exclamation mark, and /x^/ does not match any string (there cannot be an x before the start of the string).

- -

If, on the other hand, we just want to make sure the date starts and ends on a word boundary, we can use the marker \b. A word boundary can be the start or end of the string or any point in the string that has a word character (as in \w) on one side and a nonword character on the other.

- -
console.log(/cat/.test("concatenate"));
-// → true
-console.log(/\bcat\b/.test("concatenate"));
-// → false
- -

Note that a boundary marker doesn’t match an actual character. It just enforces that the regular expression matches only when a certain condition holds at the place where it appears in the pattern.

- -

Choice patterns

- -

Say we want to know whether a piece of text contains not only a number but a number followed by one of the words pig, cow, or chicken, or any of their plural forms.

- -

We could write three regular expressions and test them in turn, but there is a nicer way. The pipe character (|) denotes a choice between the pattern to its left and the pattern to its right. So I can say this:

- -
let animalCount = /\b\d+ (pig|cow|chicken)s?\b/;
-console.log(animalCount.test("15 pigs"));
-// → true
-console.log(animalCount.test("15 pigchickens"));
-// → false
- -

Parentheses can be used to limit the part of the pattern that the pipe operator applies to, and you can put multiple such operators next to each other to express a choice between more than two alternatives.

- -

The mechanics of matching

- -

Conceptually, when you use exec or test, the regular expression engine looks for a match in your string by trying to match the expression first from the start of the string, then from the second character, and so on, until it finds a match or reaches the end of the string. It’ll either return the first match that can be found or fail to find any match at all.

- -

To do the actual matching, the engine treats a regular expression something like a flow diagram. This is the diagram for the livestock expression in the previous example:

Visualization of /\b\d+ (pig|cow|chicken)s?\b/
- -

Our expression matches if we can find a path from the left side of the diagram to the right side. We keep a current position in the string, and every time we move through a box, we verify that the part of the string after our current position matches that box.

- -

So if we try to match "the 3 pigs" from position 4, our progress through the flow chart would look like this:

- -
    - -
  • - -

    At position 4, there is a word boundary, so we can move past the first box.

  • - -
  • - -

    Still at position 4, we find a digit, so we can also move past the second box.

  • - -
  • - -

    At position 5, one path loops back to before the second (digit) box, while the other moves forward through the box that holds a single space character. There is a space here, not a digit, so we must take the second path.

  • - -
  • - -

    We are now at position 6 (the start of pigs) and at the three-way branch in the diagram. We don’t see cow or chicken here, but we do see pig, so we take that branch.

  • - -
  • - -

    At position 9, after the three-way branch, one path skips the s box and goes straight to the final word boundary, while the other path matches an s. There is an s character here, not a word boundary, so we go through the s box.

  • - -
  • - -

    We’re at position 10 (the end of the string) and can match only a word boundary. The end of a string counts as a word boundary, so we go through the last box and have successfully matched this string.

- -

Backtracking

- -

The regular expression /\b([01]+b|[\da-f]+h|\d+)\b/ matches either a binary number followed by a b, a hexadecimal number (that is, base 16, with the letters a to f standing for the digits 10 to 15) followed by an h, or a regular decimal number with no suffix character. This is the corresponding diagram:

Visualization of /\b([01]+b|\d+|[\da-f]+h)\b/
- -

When matching this expression, it will often happen that the top (binary) branch is entered even though the input does not actually contain a binary number. When matching the string "103", for example, it becomes clear only at the 3 that we are in the wrong branch. The string does match the expression, just not the branch we are currently in.

- -

So the matcher backtracks. When entering a branch, it remembers its current position (in this case, at the start of the string, just past the first boundary box in the diagram) so that it can go back and try another branch if the current one does not work out. For the string "103", after encountering the 3 character, it will start trying the branch for hexadecimal numbers, which fails again because there is no h after the number. So it tries the decimal number branch. This one fits, and a match is reported after all.

- -

The matcher stops as soon as it finds a full match. This means that if multiple branches could potentially match a string, only the first one (ordered by where the branches appear in the regular expression) is used.

- -

Backtracking also happens for repetition operators like + and *. If you match /^.*x/ against "abcxe", the .* part will first try to consume the whole string. The engine will then realize that it needs an x to match the pattern. Since there is no x past the end of the string, the star operator tries to match one character less. But the matcher doesn’t find an x after abcx either, so it backtracks again, matching the star operator to just abc. Now it finds an x where it needs it and reports a successful match from positions 0 to 4.

- -

It is possible to write regular expressions that will do a lot of backtracking. This problem occurs when a pattern can match a piece of input in many different ways. For example, if we get confused while writing a binary-number regular expression, we might accidentally write something like /([01]+)+b/.

Visualization of /([01]+)+b/
- -

If that tries to match some long series of zeros and ones with no trailing b character, the matcher first goes through the inner loop until it runs out of digits. Then it notices there is no b, so it backtracks one position, goes through the outer loop once, and gives up again, trying to backtrack out of the inner loop once more. It will continue to try every possible route through these two loops. This means the amount of work doubles with each additional character. For even just a few dozen characters, the resulting match will take practically forever.

- -

The replace method

- -

String values have a replace method that can be used to replace part of the string with another string.

- -
console.log("papa".replace("p", "m"));
-// → mapa
- -

The first argument can also be a regular expression, in which case the first match of the regular expression is replaced. When a g option (for global) is added to the regular expression, all matches in the string will be replaced, not just the first.

- -
console.log("Borobudur".replace(/[ou]/, "a"));
-// → Barobudur
-console.log("Borobudur".replace(/[ou]/g, "a"));
-// → Barabadar
- -

It would have been sensible if the choice between replacing one match or all matches was made through an additional argument to replace or by providing a different method, replaceAll. But for some unfortunate reason, the choice relies on a property of the regular expression instead.

- -

The real power of using regular expressions with replace comes from the fact that we can refer to matched groups in the replacement string. For example, say we have a big string containing the names of people, one name per line, in the format Lastname, Firstname. If we want to swap these names and remove the comma to get a Firstname Lastname format, we can use the following code:

- -
console.log(
-  "Liskov, Barbara\nMcCarthy, John\nWadler, Philip"
-    .replace(/(\w+), (\w+)/g, "$2 $1"));
-// → Barbara Liskov
-//   John McCarthy
-//   Philip Wadler
- -

The $1 and $2 in the replacement string refer to the parenthesized groups in the pattern. $1 is replaced by the text that matched against the first group, $2 by the second, and so on, up to $9. The whole match can be referred to with $&.

- -

It is possible to pass a function—rather than a string—as the second argument to replace. For each replacement, the function will be called with the matched groups (as well as the whole match) as arguments, and its return value will be inserted into the new string.

- -

Here’s a small example:

- -
let s = "the cia and fbi";
-console.log(s.replace(/\b(fbi|cia)\b/g,
-            str => str.toUpperCase()));
-// → the CIA and FBI
- -

Here’s a more interesting one:

- -
let stock = "1 lemon, 2 cabbages, and 101 eggs";
-function minusOne(match, amount, unit) {
-  amount = Number(amount) - 1;
-  if (amount == 1) { // only one left, remove the 's'
-    unit = unit.slice(0, unit.length - 1);
-  } else if (amount == 0) {
-    amount = "no";
-  }
-  return amount + " " + unit;
-}
-console.log(stock.replace(/(\d+) (\w+)/g, minusOne));
-// → no lemon, 1 cabbage, and 100 eggs
- -

This takes a string, finds all occurrences of a number followed by an alphanumeric word, and returns a string wherein every such occurrence is decremented by one.

- -

The (\d+) group ends up as the amount argument to the function, and the (\w+) group gets bound to unit. The function converts amount to a number—which always works since it matched \d+—and makes some adjustments in case there is only one or zero left.

- -

Greed

- -

It is possible to use replace to write a function that removes all comments from a piece of JavaScript code. Here is a first attempt:

- -
function stripComments(code) {
-  return code.replace(/\/\/.*|\/\*[^]*\*\//g, "");
-}
-console.log(stripComments("1 + /* 2 */3"));
-// → 1 + 3
-console.log(stripComments("x = 10;// ten!"));
-// → x = 10;
-console.log(stripComments("1 /* a */+/* b */ 1"));
-// → 1  1
- -

The part before the or operator matches two slash characters followed by any number of non-newline characters. The part for multiline comments is more involved. We use [^] (any character that is not in the empty set of characters) as a way to match any character. We cannot just use a period here because block comments can continue on a new line, and the period character does not match newline characters.

- -

But the output for the last line appears to have gone wrong. Why?

- -

The [^]* part of the expression, as I described in the section on backtracking, will first match as much as it can. If that causes the next part of the pattern to fail, the matcher moves back one character and tries again from there. In the example, the matcher first tries to match the whole rest of the string and then moves back from there. It will find an occurrence of */ after going back four characters and match that. This is not what we wanted—the intention was to match a single comment, not to go all the way to the end of the code and find the end of the last block comment.

- -

Because of this behavior, we say the repetition operators (+, *, ?, and {}) are greedy, meaning they match as much as they can and backtrack from there. If you put a question mark after them (+?, *?, ??, {}?), they become nongreedy and start by matching as little as possible, matching more only when the remaining pattern does not fit the smaller match.

- -

And that is exactly what we want in this case. By having the star match the smallest stretch of characters that brings us to a */, we consume one block comment and nothing more.

- -
function stripComments(code) {
-  return code.replace(/\/\/.*|\/\*[^]*?\*\//g, "");
-}
-console.log(stripComments("1 /* a */+/* b */ 1"));
-// → 1 + 1
- -

A lot of bugs in regular expression programs can be traced to unintentionally using a greedy operator where a nongreedy one would work better. When using a repetition operator, consider the nongreedy variant first.

- -

Dynamically creating RegExp objects

- -

There are cases where you might not know the exact pattern you need to match against when you are writing your code. Say you want to look for the user’s name in a piece of text and enclose it in underscore characters to make it stand out. Since you will know the name only once the program is actually running, you can’t use the slash-based notation.

- -

But you can build up a string and use the RegExp constructor on that. Here’s an example:

- -
let name = "harry";
-let text = "Harry is a suspicious character.";
-let regexp = new RegExp("\\b(" + name + ")\\b", "gi");
-console.log(text.replace(regexp, "_$1_"));
-// → _Harry_ is a suspicious character.
- -

When creating the \b boundary markers, we have to use two backslashes because we are writing them in a normal string, not a slash-enclosed regular expression. The second argument to the RegExp constructor contains the options for the regular expression—in this case, "gi" for global and case insensitive.

- -

But what if the name is "dea+hl[]rd" because our user is a nerdy teenager? That would result in a nonsensical regular expression that won’t actually match the user’s name.

- -

To work around this, we can add backslashes before any character that has a special meaning.

- -
let name = "dea+hl[]rd";
-let text = "This dea+hl[]rd guy is super annoying.";
-let escaped = name.replace(/[\\[.+*?(){|^$]/g, "\\$&");
-let regexp = new RegExp("\\b" + escaped + "\\b", "gi");
-console.log(text.replace(regexp, "_$&_"));
-// → This _dea+hl[]rd_ guy is super annoying.
- -

The search method

- -

The indexOf method on strings cannot be called with a regular expression. But there is another method, search, that does expect a regular expression. Like indexOf, it returns the first index on which the expression was found, or -1 when it wasn’t found.

- -
console.log("  word".search(/\S/));
-// → 2
-console.log("    ".search(/\S/));
-// → -1
- -

Unfortunately, there is no way to indicate that the match should start at a given offset (like we can with the second argument to indexOf), which would often be useful.

- -

The lastIndex property

- -

The exec method similarly does not provide a convenient way to start searching from a given position in the string. But it does provide an inconvenient way.

- -

Regular expression objects have properties. One such property is source, which contains the string that expression was created from. Another property is lastIndex, which controls, in some limited circumstances, where the next match will start.

- -

Those circumstances are that the regular expression must have the global (g) or sticky (y) option enabled, and the match must happen through the exec method. Again, a less confusing solution would have been to just allow an extra argument to be passed to exec, but confusion is an essential feature of JavaScript’s regular expression interface.

- -
let pattern = /y/g;
-pattern.lastIndex = 3;
-let match = pattern.exec("xyzzy");
-console.log(match.index);
-// → 4
-console.log(pattern.lastIndex);
-// → 5
- -

If the match was successful, the call to exec automatically updates the lastIndex property to point after the match. If no match was found, lastIndex is set back to zero, which is also the value it has in a newly constructed regular expression object.

- -

The difference between the global and the sticky options is that, when sticky is enabled, the match will succeed only if it starts directly at lastIndex, whereas with global, it will search ahead for a position where a match can start.

- -
let global = /abc/g;
-console.log(global.exec("xyz abc"));
-// → ["abc"]
-let sticky = /abc/y;
-console.log(sticky.exec("xyz abc"));
-// → null
- -

When using a shared regular expression value for multiple exec calls, these automatic updates to the lastIndex property can cause problems. Your regular expression might be accidentally starting at an index that was left over from a previous call.

- -
let digit = /\d/g;
-console.log(digit.exec("here it is: 1"));
-// → ["1"]
-console.log(digit.exec("and now: 1"));
-// → null
- -

Another interesting effect of the global option is that it changes the way the match method on strings works. When called with a global expression, instead of returning an array similar to that returned by exec, match will find all matches of the pattern in the string and return an array containing the matched strings.

- -
console.log("Banana".match(/an/g));
-// → ["an", "an"]
- -

So be cautious with global regular expressions. The cases where they are necessary—calls to replace and places where you want to explicitly use lastIndex—are typically the only places where you want to use them.

- -

Looping over matches

- -

A common thing to do is to scan through all occurrences of a pattern in a string, in a way that gives us access to the match object in the loop body. We can do this by using lastIndex and exec.

- -
let input = "A string with 3 numbers in it... 42 and 88.";
-let number = /\b\d+\b/g;
-let match;
-while (match = number.exec(input)) {
-  console.log("Found", match[0], "at", match.index);
-}
-// → Found 3 at 14
-//   Found 42 at 33
-//   Found 88 at 40
- -

This makes use of the fact that the value of an assignment expression (=) is the assigned value. So by using match = number.exec(input) as the condition in the while statement, we perform the match at the start of each iteration, save its result in a binding, and stop looping when no more matches are found.

- -

Parsing an INI file

- -

To conclude the chapter, we’ll look at a problem that calls for regular expressions. Imagine we are writing a program to automatically collect information about our enemies from the Internet. (We will not actually write that program here, just the part that reads the configuration file. Sorry.) The configuration file looks like this:

- -
searchengine=https://duckduckgo.com/?q=$1
-spitefulness=9.7
-
-; comments are preceded by a semicolon...
-; each section concerns an individual enemy
-[larry]
-fullname=Larry Doe
-type=kindergarten bully
-website=http://www.geocities.com/CapeCanaveral/11451
-
-[davaeorn]
-fullname=Davaeorn
-type=evil wizard
-outputdir=/home/marijn/enemies/davaeorn
- -

The exact rules for this format (which is a widely used format, usually called an INI file) are as follows:

- -
    - -
  • - -

    Blank lines and lines starting with semicolons are ignored.

  • - -
  • - -

    Lines wrapped in [ and ] start a new section.

  • - -
  • - -

    Lines containing an alphanumeric identifier followed by an = character add a setting to the current section.

  • - -
  • - -

    Anything else is invalid.

- -

Our task is to convert a string like this into an object whose properties hold strings for settings written before the first section header and subobjects for sections, with those subobjects holding the section’s settings.

- -

Since the format has to be processed line by line, splitting up the file into separate lines is a good start. We saw the split method in Chapter 4. Some operating systems, however, use not just a newline character to separate lines but a carriage return character followed by a newline ("\r\n"). Given that the split method also allows a regular expression as its argument, we can use a regular expression like /\r?\n/ to split in a way that allows both "\n" and "\r\n" between lines.

- -
function parseINI(string) {
-  // Start with an object to hold the top-level fields
-  let result = {};
-  let section = result;
-  string.split(/\r?\n/).forEach(line => {
-    let match;
-    if (match = line.match(/^(\w+)=(.*)$/)) {
-      section[match[1]] = match[2];
-    } else if (match = line.match(/^\[(.*)\]$/)) {
-      section = result[match[1]] = {};
-    } else if (!/^\s*(;.*)?$/.test(line)) {
-      throw new Error("Line '" + line + "' is not valid.");
-    }
-  });
-  return result;
-}
-
-console.log(parseINI(`
-name=Vasilis
-[address]
-city=Tessaloniki`));
-// → {name: "Vasilis", address: {city: "Tessaloniki"}}
- -

The code goes over the file’s lines and builds up an object. Properties at the top are stored directly into that object, whereas properties found in sections are stored in a separate section object. The section binding points at the object for the current section.

- -

There are two kinds of significant lines—section headers or property lines. When a line is a regular property, it is stored in the current section. When it is a section header, a new section object is created, and section is set to point at it.

- -

Note the recurring use of ^ and $ to make sure the expression matches the whole line, not just part of it. Leaving these out results in code that mostly works but behaves strangely for some input, which can be a difficult bug to track down.

- -

The pattern if (match = string.match(...)) is similar to the trick of using an assignment as the condition for while. You often aren’t sure that your call to match will succeed, so you can access the resulting object only inside an if statement that tests for this. To not break the pleasant chain of else if forms, we assign the result of the match to a binding and immediately use that assignment as the test for the if statement.

- -

If a line is not a section header or a property, the function checks whether it is a comment or an empty line using the expression /^\s*(;.*)?$/. Do you see how it works? The part between the parentheses will match comments, and the ? makes sure it also matches lines containing only whitespace. When a line doesn’t match any of the expected forms, the function throws an exception.

- -

International characters

- -

Because of JavaScript’s initial simplistic implementation and the fact that this simplistic approach was later set in stone as standard behavior, JavaScript’s regular expressions are rather dumb about characters that do not appear in the English language. For example, as far as JavaScript’s regular expressions are concerned, a “word -character” is only one of the 26 characters in the Latin alphabet (uppercase or lowercase), decimal digits, and, for some reason, the underscore character. Things like é or β, which most definitely are word characters, will not match \w (and will match uppercase \W, the nonword category).

- -

By a strange historical accident, \s (whitespace) does not have this problem and matches all characters that the Unicode standard considers whitespace, including things like the nonbreaking space and the Mongolian vowel separator.

- -

Another problem is that, by default, regular expressions work on code units, as discussed in Chapter 5, not actual characters. This means characters that are composed of two code units behave strangely.

- -
console.log(/🍎{3}/.test("🍎🍎🍎"));
-// → false
-console.log(/<.>/.test("<🌹>"));
-// → false
-console.log(/<.>/u.test("<🌹>"));
-// → true
- -

The problem is that the 🍎 in the first line is treated as two code units, and the {3} part is applied only to the second one. Similarly, the dot matches a single code unit, not the two that make up the rose emoji.

- -

You must add a u option (for Unicode) to your regular expression to make it treat such characters properly. The wrong behavior remains the default, unfortunately, because changing that might cause problems for existing code that depends on it.

- -

Though this was only just standardized and is, at the time of writing, not widely supported yet, it is possible to use \p in a regular expression (that must have the Unicode option enabled) to match all characters to which the Unicode standard assigns a given property.

- -
console.log(/\p{Script=Greek}/u.test("α"));
-// → true
-console.log(/\p{Script=Arabic}/u.test("α"));
-// → false
-console.log(/\p{Alphabetic}/u.test("α"));
-// → true
-console.log(/\p{Alphabetic}/u.test("!"));
-// → false
- -

Unicode defines a number of useful properties, though finding the one that you need may not always be trivial. You can use the \p{Property=Value} notation to match any character that has the given value for that property. If the property name is left off, as in \p{Name}, the name is assumed to be either a binary property such as Alphabetic or a category such as Number.

- -

Summary

- -

Regular expressions are objects that represent patterns in strings. They use their own language to express these patterns.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/abc/A sequence of characters
/[abc]/Any character from a set of characters
/[^abc]/Any character not in a set of characters
/[0-9]/Any character in a range of characters
/x+/One or more occurrences of the pattern x
/x+?/One or more occurrences, nongreedy
/x*/Zero or more occurrences
/x?/Zero or one occurrence
/x{2,4}/Two to four occurrences
/(abc)/A group
/a|b|c/Any one of several patterns
/\d/Any digit character
/\w/An alphanumeric character (“word character”)
/\s/Any whitespace character
/./Any character except newlines
/\b/A word boundary
/^/Start of input
/$/End of input
- -

A regular expression has a method test to test whether a given string matches it. It also has a method exec that, when a match is found, returns an array containing all matched groups. Such an array has an index property that indicates where the match started.

- -

Strings have a match method to match them against a regular expression and a search method to search for one, returning only the starting position of the match. Their replace method can replace matches of a pattern with a replacement string or function.

- -

Regular expressions can have options, which are written after the closing slash. The i option makes the match case insensitive. The g option makes the expression global, which, among other things, causes the replace method to replace all instances instead of just the first. The y option makes it sticky, which means that it will not search ahead and skip part of the string when looking for a match. The u option turns on Unicode mode, which fixes a number of problems around the handling of characters that take up two code units.

- -

Regular expressions are a sharp tool with an awkward handle. They simplify some tasks tremendously but can quickly become unmanageable when applied to complex problems. Part of knowing how to use them is resisting the urge to try to shoehorn things that they cannot cleanly express into them.

- -

Exercises

- -

It is almost unavoidable that, in the course of working on these exercises, you will get confused and frustrated by some regular expression’s inexplicable behavior. Sometimes it helps to enter your expression into an online tool like https://debuggex.com to see whether its visualization corresponds to what you intended and to experiment with the way it responds to various input strings.

- -

Regexp golf

- -

Code golf is a term used for the game of trying to express a particular program in as few characters as possible. Similarly, regexp golf is the practice of writing as tiny a regular expression as possible to match a given pattern, and only that pattern.

- -

For each of the following items, write a regular expression to test whether any of the given substrings occur in a string. The regular expression should match only strings containing one of the substrings described. Do not worry about word boundaries unless explicitly mentioned. When your expression works, see whether you can make it any smaller.

- -
    - -
  1. - -

    car and cat

  2. - -
  3. - -

    pop and prop

  4. - -
  5. - -

    ferret, ferry, and ferrari

  6. - -
  7. - -

    Any word ending in ious

  8. - -
  9. - -

    A whitespace character followed by a period, comma, colon, or semicolon

  10. - -
  11. - -

    A word longer than six letters

  12. - -
  13. - -

    A word without the letter e (or E)

  14. - -
- -

Refer to the table in the chapter summary for help. Test each solution with a few test strings.

- -
// Fill in the regular expressions
-
-verify(/.../,
-       ["my car", "bad cats"],
-       ["camper", "high art"]);
-
-verify(/.../,
-       ["pop culture", "mad props"],
-       ["plop", "prrrop"]);
-
-verify(/.../,
-       ["ferret", "ferry", "ferrari"],
-       ["ferrum", "transfer A"]);
-
-verify(/.../,
-       ["how delicious", "spacious room"],
-       ["ruinous", "consciousness"]);
-
-verify(/.../,
-       ["bad punctuation ."],
-       ["escape the period"]);
-
-verify(/.../,
-       ["hottentottententen"],
-       ["no", "hotten totten tenten"]);
-
-verify(/.../,
-       ["red platypus", "wobbling nest"],
-       ["earth bed", "learning ape", "BEET"]);
-
-
-function verify(regexp, yes, no) {
-  // Ignore unfinished exercises
-  if (regexp.source == "...") return;
-  for (let str of yes) if (!regexp.test(str)) {
-    console.log(`Failure to match '${str}'`);
-  }
-  for (let str of no) if (regexp.test(str)) {
-    console.log(`Unexpected match for '${str}'`);
-  }
-}
- -

Quoting style

- -

Imagine you have written a story and used single quotation marks throughout to mark pieces of dialogue. Now you want to replace all the dialogue quotes with double quotes, while keeping the single quotes used in contractions like aren’t.

- -

Think of a pattern that distinguishes these two kinds of quote usage and craft a call to the replace method that does the proper replacement.

- -
let text = "'I'm the cook,' he said, 'it's my job.'";
-// Change this call.
-console.log(text.replace(/A/g, "B"));
-// → "I'm the cook," he said, "it's my job."
- -
- -

The most obvious solution is to replace only quotes with a nonword character on at least one side—something like /\W'|'\W/. But you also have to take the start and end of the line into account.

- -

In addition, you must ensure that the replacement also includes the characters that were matched by the \W pattern so that those are not dropped. This can be done by wrapping them in parentheses and including their groups in the replacement string ($1, $2). Groups that are not matched will be replaced by nothing.

- -
- -

Numbers again

- -

Write an expression that matches only JavaScript-style numbers. It must support an optional minus or plus sign in front of the number, the decimal dot, and exponent notation—5e-3 or 1E10—again with an optional sign in front of the exponent. Also note that it is not necessary for there to be digits in front of or after the dot, but the number cannot be a dot alone. That is, .5 and 5. are valid JavaScript numbers, but a lone dot isn’t.

- -
// Fill in this regular expression.
-let number = /^...$/;
-
-// Tests:
-for (let str of ["1", "-1", "+15", "1.55", ".5", "5.",
-                 "1.3e2", "1E-4", "1e+12"]) {
-  if (!number.test(str)) {
-    console.log(`Failed to match '${str}'`);
-  }
-}
-for (let str of ["1a", "+-1", "1.2.3", "1+1", "1e4.5",
-                 ".5.", "1f5", "."]) {
-  if (number.test(str)) {
-    console.log(`Incorrectly accepted '${str}'`);
-  }
-}
- -
- -

First, do not forget the backslash in front of the period.

- -

Matching the optional sign in front of the number, as well as in front of the exponent, can be done with [+\-]? or (\+|-|) (plus, minus, or nothing).

- -

The more complicated part of the exercise is the problem of matching both "5." and ".5" without also matching ".". For this, a good solution is to use the | operator to separate the two cases—either one or more digits optionally followed by a dot and zero or more digits or a dot followed by one or more digits.

- -

Finally, to make the e case insensitive, either add an i option to the regular expression or use [eE].

- -
-
diff --git a/docs/10_modules.html b/docs/10_modules.html deleted file mode 100644 index f9fbf9ed6..000000000 --- a/docs/10_modules.html +++ /dev/null @@ -1,375 +0,0 @@ - - - - - Modules :: Eloquent JavaScript - - - - - - -
- - -

Chapter 10Modules

- -
- -

Write code that is easy to delete, not easy to extend.

- -
Tef, Programming is Terrible
- -
Picture of a building built from modular pieces
- -

The ideal program has a crystal-clear structure. The way it works is easy to explain, and each part plays a well-defined role.

- -

A typical real program grows organically. New pieces of functionality are added as new needs come up. Structuring—and preserving structure—is additional work. It’s work that will pay off only in the future, the next time someone works on the program. So it is tempting to neglect it and allow the parts of the program to become deeply entangled.

- -

This causes two practical issues. First, understanding such a system is hard. If everything can touch everything else, it is difficult to look at any given piece in isolation. You are forced to build up a holistic understanding of the entire thing. Second, if you want to use any of the functionality from such a program in another situation, rewriting it may be easier than trying to disentangle it from its context.

- -

The phrase “big ball of mud” is often used for such large, structureless programs. Everything sticks together, and when you try to pick out a piece, the whole thing comes apart, and your hands get dirty.

- -

Modules

- -

Modules are an attempt to avoid these problems. A module is a piece of program that specifies which other pieces it relies on and which functionality it provides for other modules to use (its interface).

- -

Module interfaces have a lot in common with object interfaces, as we saw them in Chapter 6. They make part of the module available to the outside world and keep the rest private. By restricting the ways in which modules interact with each other, the system becomes more like LEGO, where pieces interact through well-defined connectors, and less like mud, where everything mixes with everything.

- -

The relations between modules are called dependencies. When a module needs a piece from another module, it is said to depend on that module. When this fact is clearly specified in the module itself, it can be used to figure out which other modules need to be present to be able to use a given module and to automatically load dependencies.

- -

To separate modules in that way, each needs its own private scope.

- -

Just putting your JavaScript code into different files does not satisfy these requirements. The files still share the same global namespace. They can, intentionally or accidentally, interfere with each other’s bindings. And the dependency structure remains unclear. We can do better, as we’ll see later in the chapter.

- -

Designing a fitting module structure for a program can be difficult. In the phase where you are still exploring the problem, trying different things to see what works, you might want to not worry about it too much since it can be a big distraction. Once you have something that feels solid, that’s a good time to take a step back and organize it.

- -

Packages

- -

One of the advantages of building a program out of separate pieces, and being actually able to run those pieces on their own, is that you might be able to apply the same piece in different programs.

- -

But how do you set this up? Say I want to use the parseINI function from Chapter 9 in another program. If it is clear what the function depends on (in this case, nothing), I can just copy all the necessary code into my new project and use it. But then, if I find a mistake in that code, I’ll probably fix it in whichever program I’m working with at the time and forget to also fix it in the other program.

- -

Once you start duplicating code, you’ll quickly find yourself wasting time and energy moving copies around and keeping them up-to-date.

- -

That’s where packages come in. A package is a chunk of code that can be distributed (copied and installed). It may contain one or more modules and has information about which other packages it depends on. A package also usually comes with documentation explaining what it does so that people who didn’t write it might still be able to use it.

- -

When a problem is found in a package or a new feature is added, the package is updated. Now the programs that depend on it (which may also be packages) can upgrade to the new version.

- -

Working in this way requires infrastructure. We need a place to store and find packages and a convenient way to install and upgrade them. In the JavaScript world, this infrastructure is provided by NPM (https://npmjs.org).

- -

NPM is two things: an online service where one can download (and upload) packages and a program (bundled with Node.js) that helps you install and manage them.

- -

At the time of writing, there are more than half a million different packages available on NPM. A large portion of those are rubbish, I should mention, but almost every useful, publicly available package can be found on there. For example, an INI file parser, similar to the one we built in Chapter 9, is available under the package name ini.

- -

Chapter 20 will show how to install such packages locally using the npm command line program.

- -

Having quality packages available for download is extremely valuable. It means that we can often avoid reinventing a program that 100 people have written before and get a solid, well-tested implementation at the press of a few keys.

- -

Software is cheap to copy, so once someone has written it, distributing it to other people is an efficient process. But writing it in the first place is work, and responding to people who have found problems in the code, or who want to propose new features, is even more work.

- -

By default, you own the copyright to the code you write, and other people may use it only with your permission. But because some people are just nice and because publishing good software can help make you a little bit famous among programmers, many packages are published under a license that explicitly allows other people to use it.

- -

Most code on NPM is licensed this way. Some licenses require you to also publish code that you build on top of the package under the same license. Others are less demanding, just requiring that you keep the license with the code as you distribute it. The JavaScript community mostly uses the latter type of license. When using other people’s packages, make sure you are aware of their license.

- -

Improvised modules

- -

Until 2015, the JavaScript language had no built-in module system. Yet people had been building large systems in JavaScript for more than a decade, and they needed modules.

- -

So they designed their own module systems on top of the language. You can use JavaScript functions to create local scopes and objects to represent module interfaces.

- -

This is a module for going between day names and numbers (as returned by Date’s getDay method). Its interface consists of weekDay.name and weekDay.number, and it hides its local binding names inside the scope of a function expression that is immediately invoked.

- -
const weekDay = function() {
-  const names = ["Sunday", "Monday", "Tuesday", "Wednesday",
-                 "Thursday", "Friday", "Saturday"];
-  return {
-    name(number) { return names[number]; },
-    number(name) { return names.indexOf(name); }
-  };
-}();
-
-console.log(weekDay.name(weekDay.number("Sunday")));
-// → Sunday
- -

This style of modules provides isolation, to a certain degree, but it does not declare dependencies. Instead, it just puts its interface into the global scope and expects its dependencies, if any, to do the same. For a long time this was the main approach used in web programming, but it is mostly obsolete now.

- -

If we want to make dependency relations part of the code, we’ll have to take control of loading dependencies. Doing that requires being able to execute strings as code. JavaScript can do this.

- -

Evaluating data as code

- -

There are several ways to take data (a string of code) and run it as part of the current program.

- -

The most obvious way is the special operator eval, which will execute a string in the current scope. This is usually a bad idea because it breaks some of the properties that scopes normally have, such as it being easily predictable which binding a given name refers to.

- -
const x = 1;
-function evalAndReturnX(code) {
-  eval(code);
-  return x;
-}
-
-console.log(evalAndReturnX("var x = 2"));
-// → 2
-console.log(x);
-// → 1
- -

A less scary way of interpreting data as code is to use the Function constructor. It takes two arguments: a string containing a comma-separated list of argument names and a string containing the function body. It wraps the code in a function value so that it gets its own scope and won’t do odd things with other scopes.

- -
let plusOne = Function("n", "return n + 1;");
-console.log(plusOne(4));
-// → 5
- -

This is precisely what we need for a module system. We can wrap the module’s code in a function and use that function’s scope as module scope.

- -

CommonJS

- -

The most widely used approach to bolted-on JavaScript modules is called CommonJS modules. Node.js uses it and is the system used by most packages on NPM.

- -

The main concept in CommonJS modules is a function called require. When you call this with the module name of a dependency, it makes sure the module is loaded and returns its interface.

- -

Because the loader wraps the module code in a function, modules automatically get their own local scope. All they have to do is call require to access their dependencies and put their interface in the object bound to exports.

- -

This example module provides a date-formatting function. It uses two packages from NPM—ordinal to convert numbers to strings like "1st" and "2nd", and date-names to get the English names for weekdays and months. It exports a single function, formatDate, which takes a Date object and a template string.

- -

The template string may contain codes that direct the format, such as YYYY for the full year and Do for the ordinal day of the month. You could give it a string like "MMMM Do YYYY" to get output like “November 22nd 2017”.

- -
const ordinal = require("ordinal");
-const {days, months} = require("date-names");
-
-exports.formatDate = function(date, format) {
-  return format.replace(/YYYY|M(MMM)?|Do?|dddd/g, tag => {
-    if (tag == "YYYY") return date.getFullYear();
-    if (tag == "M") return date.getMonth();
-    if (tag == "MMMM") return months[date.getMonth()];
-    if (tag == "D") return date.getDate();
-    if (tag == "Do") return ordinal(date.getDate());
-    if (tag == "dddd") return days[date.getDay()];
-  });
-};
- -

The interface of ordinal is a single function, whereas date-names exports an object containing multiple things—days and months are arrays of names. Destructuring is very convenient when creating bindings for imported interfaces.

- -

The module adds its interface function to exports so that modules that depend on it get access to it. We could use the module like this:

- -
const {formatDate} = require("./format-date");
-
-console.log(formatDate(new Date(2017, 9, 13),
-                       "dddd the Do"));
-// → Friday the 13th
- -

We can define require, in its most minimal form, like this:

- -
require.cache = Object.create(null);
-
-function require(name) {
-  if (!(name in require.cache)) {
-    let code = readFile(name);
-    let module = {exports: {}};
-    require.cache[name] = module;
-    let wrapper = Function("require, exports, module", code);
-    wrapper(require, module.exports, module);
-  }
-  return require.cache[name].exports;
-}
- -

In this code, readFile is a made-up function that reads a file and returns its contents as a string. Standard JavaScript provides no such functionality—but different JavaScript environments, such as the browser and Node.js, provide their own ways of accessing files. The example just pretends that readFile exists.

- -

To avoid loading the same module multiple times, require keeps a store (cache) of already loaded modules. When called, it first checks if the requested module has been loaded and, if not, loads it. This involves reading the module’s code, wrapping it in a function, and calling it.

- -

The interface of the ordinal package we saw before is not an object but a function. A quirk of the CommonJS modules is that, though the module system will create an empty interface object for you (bound to exports), you can replace that with any value by overwriting module.exports. This is done by many modules to export a single value instead of an interface object.

- -

By defining require, exports, and module as parameters for the generated wrapper function (and passing the appropriate values when calling it), the loader makes sure that these bindings are available in the module’s scope.

- -

The way the string given to require is translated to an actual filename or web address differs in different systems. When it starts with "./" or "../", it is generally interpreted as relative to the current module’s filename. So "./format-date" would be the file named format-date.js in the same directory.

- -

When the name isn’t relative, Node.js will look for an installed package by that name. In the example code in this chapter, we’ll interpret such names as referring to NPM packages. We’ll go into more detail on how to install and use NPM modules in Chapter 20.

- -

Now, instead of writing our own INI file parser, we can use one from NPM.

- -
const {parse} = require("ini");
-
-console.log(parse("x = 10\ny = 20"));
-// → {x: "10", y: "20"}
- -

ECMAScript modules

- -

CommonJS modules work quite well and, in combination with NPM, have allowed the JavaScript community to start sharing code on a large scale.

- -

But they remain a bit of a duct-tape hack. The notation is slightly awkward—the things you add to exports are not available in the local scope, for example. And because require is a normal function call taking any kind of argument, not just a string literal, it can be hard to determine the dependencies of a module without running its code.

- -

This is why the JavaScript standard from 2015 introduces its own, different module system. It is usually called ES modules, where ES stands for ECMAScript. The main concepts of dependencies and interfaces remain the same, but the details differ. For one thing, the notation is now integrated into the language. Instead of calling a function to access a dependency, you use a special import keyword.

- -
import ordinal from "ordinal";
-import {days, months} from "date-names";
-
-export function formatDate(date, format) { /* ... */ }
- -

Similarly, the export keyword is used to export things. It may appear in front of a function, class, or binding definition (let, const, or var).

- -

An ES module’s interface is not a single value but a set of named bindings. The preceding module binds formatDate to a function. When you import from another module, you import the binding, not the value, which means an exporting module may change the value of the binding at any time, and the modules that import it will see its new value.

- -

When there is a binding named default, it is treated as the module’s main exported value. If you import a module like ordinal in the example, without braces around the binding name, you get its default binding. Such modules can still export other bindings under different names alongside their default export.

- -

To create a default export, you write export default before an expression, a function declaration, or a class declaration.

- -
export default ["Winter", "Spring", "Summer", "Autumn"];
- -

It is possible to rename imported bindings using the word as.

- -
import {days as dayNames} from "date-names";
-
-console.log(dayNames.length);
-// → 7
- -

Another important difference is that ES module imports happen before a module’s script starts running. That means import declarations may not appear inside functions or blocks, and the names of dependencies must be quoted strings, not arbitrary expressions.

- -

At the time of writing, the JavaScript community is in the process of adopting this module style. But it has been a slow process. It took a few years, after the format was specified, for browsers and Node.js to start supporting it. And though they mostly support it now, this support still has issues, and the discussion on how such modules should be distributed through NPM is still ongoing.

- -

Many projects are written using ES modules and then automatically converted to some other format when published. We are in a transitional period in which two different module systems are used side by side, and it is useful to be able to read and write code in either of them.

- -

Building and bundling

- -

In fact, many JavaScript projects aren’t even, technically, written in JavaScript. There are extensions, such as the type checking dialect mentioned in Chapter 8, that are widely used. People also often start using planned extensions to the language long before they have been added to the platforms that actually run JavaScript.

- -

To make this possible, they compile their code, translating it from their chosen JavaScript dialect to plain old JavaScript—or even to a past version of JavaScript—so that old browsers can run it.

- -

Including a modular program that consists of 200 different files in a web page produces its own problems. If fetching a single file over the network takes 50 milliseconds, loading the whole program takes 10 seconds, or maybe half that if you can load several files simultaneously. That’s a lot of wasted time. Because fetching a single big file tends to be faster than fetching a lot of tiny ones, web programmers have started using tools that roll their programs (which they painstakingly split into modules) back into a single big file before they publish it to the Web. Such tools are called bundlers.

- -

And we can go further. Apart from the number of files, the size of the files also determines how fast they can be transferred over the network. Thus, the JavaScript community has invented minifiers. These are tools that take a JavaScript program and make it smaller by automatically removing comments and whitespace, renaming bindings, and replacing pieces of code with equivalent code that take up less space.

- -

So it is not uncommon for the code that you find in an NPM package or that runs on a web page to have gone through multiple stages of transformation—converted from modern JavaScript to historic JavaScript, from ES module format to CommonJS, bundled, and minified. We won’t go into the details of these tools in this book since they tend to be boring and change rapidly. Just be aware that the JavaScript code you run is often not the code as it was written.

- -

Module design

- -

Structuring programs is one of the subtler aspects of programming. Any nontrivial piece of functionality can be modeled in various ways.

- -

Good program design is subjective—there are trade-offs involved and matters of taste. The best way to learn the value of well-structured design is to read or work on a lot of programs and notice what works and what doesn’t. Don’t assume that a painful mess is “just the way it is”. You can improve the structure of almost everything by putting more thought into it.

- -

One aspect of module design is ease of use. If you are designing something that is intended to be used by multiple people—or even by yourself, in three months when you no longer remember the specifics of what you did—it is helpful if your interface is simple and predictable.

- -

That may mean following existing conventions. A good example is the ini package. This module imitates the standard JSON object by providing parse and stringify (to write an INI file) functions, and, like JSON, converts between strings and plain objects. So the interface is small and familiar, and after you’ve worked with it once, you’re likely to remember how to use it.

- -

Even if there’s no standard function or widely used package to imitate, you can keep your modules predictable by using simple data -structures and doing a single, focused thing. Many of the INI-file parsing modules on NPM provide a function that directly reads such a file from the hard disk and parses it, for example. This makes it impossible to use such modules in the browser, where we don’t have direct file system access, and adds complexity that would have been better addressed by composing the module with some file-reading function.

- -

This points to another helpful aspect of module design—the ease with which something can be composed with other code. Focused modules that compute values are applicable in a wider range of programs than bigger modules that perform complicated actions with side effects. An INI file reader that insists on reading the file from disk is useless in a scenario where the file’s content comes from some other source.

- -

Relatedly, stateful objects are sometimes useful or even necessary, but if something can be done with a function, use a function. Several of the INI file readers on NPM provide an interface style that requires you to first create an object, then load the file into your object, and finally use specialized methods to get at the results. This type of thing is common in the object-oriented tradition, and it’s terrible. Instead of making a single function call and moving on, you have to perform the ritual of moving your object through various states. And because the data is now wrapped in a specialized object type, all code that interacts with it has to know about that type, creating unnecessary interdependencies.

- -

Often defining new data structures can’t be avoided—only a few basic ones are provided by the language standard, and many types of data have to be more complex than an array or a map. But when an array suffices, use an array.

- -

An example of a slightly more complex data structure is the graph from Chapter 7. There is no single obvious way to represent a graph in JavaScript. In that chapter, we used an object whose properties hold arrays of strings—the other nodes reachable from that node.

- -

There are several different pathfinding packages on NPM, but none of them uses this graph format. They usually allow the graph’s edges to have a weight, which is the cost or distance associated with it. That isn’t possible in our representation.

- -

For example, there’s the dijkstrajs package. A well-known approach to pathfinding, quite similar to our findRoute function, is called Dijkstra’s algorithm, after Edsger Dijkstra, who first wrote it down. The js suffix is often added to package names to indicate the fact that they are written in JavaScript. This dijkstrajs package uses a graph format similar to ours, but instead of arrays, it uses objects whose property values are numbers—the weights of the edges.

- -

So if we wanted to use that package, we’d have to make sure that our graph was stored in the format it expects. All edges get the same weight since our simplified model treats each road as having the same cost (one turn).

- -
const {find_path} = require("dijkstrajs");
-
-let graph = {};
-for (let node of Object.keys(roadGraph)) {
-  let edges = graph[node] = {};
-  for (let dest of roadGraph[node]) {
-    edges[dest] = 1;
-  }
-}
-
-console.log(find_path(graph, "Post Office", "Cabin"));
-// → ["Post Office", "Alice's House", "Cabin"]
- -

This can be a barrier to composition—when various packages are using different data structures to describe similar things, combining them is difficult. Therefore, if you want to design for composability, find out what data structures other people are using and, when possible, follow their example.

- -

Summary

- -

Modules provide structure to bigger programs by separating the code into pieces with clear interfaces and dependencies. The interface is the part of the module that’s visible from other modules, and the dependencies are the other modules that it makes use of.

- -

Because JavaScript historically did not provide a module system, the CommonJS system was built on top of it. Then at some point it did get a built-in system, which now coexists uneasily with the CommonJS system.

- -

A package is a chunk of code that can be distributed on its own. NPM is a repository of JavaScript packages. You can download all kinds of useful (and useless) packages from it.

- -

Exercises

- -

A modular robot

- -

These are the bindings that the project from Chapter 7 creates:

- -
roads
-buildGraph
-roadGraph
-VillageState
-runRobot
-randomPick
-randomRobot
-mailRoute
-routeRobot
-findRoute
-goalOrientedRobot
- -

If you were to write that project as a modular program, what modules would you create? Which module would depend on which other module, and what would their interfaces look like?

- -

Which pieces are likely to be available prewritten on NPM? Would you prefer to use an NPM package or write them yourself?

- -
- -

Here’s what I would have done (but again, there is no single right way to design a given module):

- -

The code used to build the road graph lives in the graph module. Because I’d rather use dijkstrajs from NPM than our own pathfinding code, we’ll make this build the kind of graph data that dijkstrajs expects. This module exports a single function, buildGraph. I’d have buildGraph accept an array of two-element arrays, rather than strings containing hyphens, to make the module less dependent on the input format.

- -

The roads module contains the raw road data (the roads array) and the roadGraph binding. This module depends on ./graph and exports the road graph.

- -

The VillageState class lives in the state module. It depends on the ./roads module because it needs to be able to verify that a given road exists. It also needs randomPick. Since that is a three-line function, we could just put it into the state module as an internal helper function. But randomRobot needs it too. So we’d have to either duplicate it or put it into its own module. Since this function happens to exist on NPM in the random-item package, a good solution is to just make both modules depend on that. We can add the runRobot function to this module as well, since it’s small and closely related to state management. The module exports both the VillageState class and the runRobot function.

- -

Finally, the robots, along with the values they depend on such as mailRoute, could go into an example-robots module, which depends on ./roads and exports the robot functions. To make it possible for goalOrientedRobot to do route-finding, this module also depends on dijkstrajs.

- -

By offloading some work to NPM modules, the code became a little smaller. Each individual module does something rather simple and can be read on its own. Dividing code into modules also often suggests further improvements to the program’s design. In this case, it seems a little odd that the VillageState and the robots depend on a specific road graph. It might be a better idea to make the graph an argument to the state’s constructor and make the robots read it from the state object—this reduces dependencies (which is always good) and makes it possible to run simulations on different maps (which is even better).

- -

Is it a good idea to use NPM modules for things that we could have written ourselves? In principle, yes—for nontrivial things like the pathfinding function you are likely to make mistakes and waste time writing them yourself. For tiny functions like random-item, writing them yourself is easy enough. But adding them wherever you need them does tend to clutter your modules.

- -

However, you should also not underestimate the work involved in finding an appropriate NPM package. And even if you find one, it might not work well or may be missing some feature you need. On top of that, depending on NPM packages means you have to make sure they are installed, you have to distribute them with your program, and you might have to periodically upgrade them.

- -

So again, this is a trade-off, and you can decide either way depending on how much the packages help you.

- -
- -

Roads module

- -

Write a CommonJS module, based on the example from Chapter 7, that contains the array of roads and exports the graph data structure representing them as roadGraph. It should depend on a module ./graph, which exports a function buildGraph that is used to build the graph. This function expects an array of two-element arrays (the start and end points of the roads).

- -
// Add dependencies and exports
-
-const roads = [
-  "Alice's House-Bob's House",   "Alice's House-Cabin",
-  "Alice's House-Post Office",   "Bob's House-Town Hall",
-  "Daria's House-Ernie's House", "Daria's House-Town Hall",
-  "Ernie's House-Grete's House", "Grete's House-Farm",
-  "Grete's House-Shop",          "Marketplace-Farm",
-  "Marketplace-Post Office",     "Marketplace-Shop",
-  "Marketplace-Town Hall",       "Shop-Town Hall"
-];
- -
- -

Since this is a CommonJS module, you have to use require to import the graph module. That was described as exporting a buildGraph function, which you can pick out of its interface object with a destructuring const declaration.

- -

To export roadGraph, you add a property to the exports object. Because buildGraph takes a data structure that doesn’t precisely match roads, the splitting of the road strings must happen in your module.

- -
- -

Circular dependencies

- -

A circular dependency is a situation where module A depends on B, and B also, directly or indirectly, depends on A. Many module systems simply forbid this because whichever order you choose for loading such modules, you cannot make sure that each module’s dependencies have been loaded before it runs.

- -

CommonJS modules allow a limited form of cyclic dependencies. As long as the modules do not replace their default exports object and don’t access each other’s interface until after they finish loading, cyclic dependencies are okay.

- -

The require function given earlier in this chapter supports this type of dependency cycle. Can you see how it handles cycles? What would go wrong when a module in a cycle does replace its default exports object?

- -
- -

The trick is that require adds modules to its cache before it starts loading the module. That way, if any require call made while it is running tries to load it, it is already known, and the current interface will be returned, rather than starting to load the module once more (which would eventually overflow the stack).

- -

If a module overwrites its module.exports value, any other module that has received its interface value before it finished loading will have gotten hold of the default interface object (which is likely empty), rather than the intended interface value.

- -
-
diff --git a/docs/11_async.html b/docs/11_async.html deleted file mode 100644 index 0fa419ee3..000000000 --- a/docs/11_async.html +++ /dev/null @@ -1,681 +0,0 @@ - - - - - Asynchronous Programming :: Eloquent JavaScript - - - - - - -
- - -

Chapter 11Asynchronous Programming

- -
- -

Who can wait quietly while the mud settles?
Who can remain still until the moment of action?

- -
Laozi, Tao Te Ching
- -
Picture of two crows on a branch
- -

The central part of a computer, the part that carries out the individual steps that make up our programs, is called the processor. The programs we have seen so far are things that will keep the processor busy until they have finished their work. The speed at which something like a loop that manipulates numbers can be executed depends pretty much entirely on the speed of the processor.

- -

But many programs interact with things outside of the processor. For example, they may communicate over a computer network or request data from the hard disk—which is a lot slower than getting it from memory.

- -

When such a thing is happening, it would be a shame to let the processor sit idle—there might be some other work it could do in the meantime. In part, this is handled by your operating system, which will switch the processor between multiple running programs. But that doesn’t help when we want a single program to be able to make progress while it is waiting for a network request.

- -

Asynchronicity

- -

In a synchronous programming model, things happen one at a time. When you call a function that performs a long-running action, it returns only when the action has finished and it can return the result. This stops your program for the time the action takes.

- -

An asynchronous model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk).

- -

We can compare synchronous and asynchronous programming using a small example: a program that fetches two resources from the network and then combines results.

- -

In a synchronous environment, where the request function returns only after it has done its work, the easiest way to perform this task is to make the requests one after the other. This has the drawback that the second request will be started only when the first has finished. The total time taken will be at least the sum of the two response times.

- -

The solution to this problem, in a synchronous system, is to start additional threads of control. A thread is another running program whose execution may be interleaved with other programs by the operating system—since most modern computers contain multiple processors, multiple threads may even run at the same time, on different processors. A second thread could start the second request, and then both threads wait for their results to come back, after which they resynchronize to combine their results.

- -

In the following diagram, the thick lines represent time the program spends running normally, and the thin lines represent time spent waiting for the network. In the synchronous model, the time taken by the network is part of the timeline for a given thread of control. In the asynchronous model, starting a network action conceptually causes a split in the timeline. The program that initiated the action continues running, and the action happens alongside it, notifying the program when it is finished.

Control flow for synchronous and asynchronous programming
- -

Another way to describe the difference is that waiting for actions to finish is implicit in the synchronous model, while it is explicit, under our control, in the asynchronous one.

- -

Asynchronicity cuts both ways. It makes expressing programs that do not fit the straight-line model of control easier, but it can also make expressing programs that do follow a straight line more awkward. We’ll see some ways to address this awkwardness later in the chapter.

- -

Both of the important JavaScript programming platforms—browsers and Node.js—make operations that might take a while asynchronous, rather than relying on threads. Since programming with threads is notoriously hard (understanding what a program does is much more difficult when it’s doing multiple things at once), this is generally considered a good thing.

- -

Crow tech

- -

Most people are aware of the fact that crows are very smart birds. They can use tools, plan ahead, remember things, and even communicate these things among themselves.

- -

What most people don’t know is that they are capable of many things that they keep well hidden from us. I’ve been told by a reputable (if somewhat eccentric) expert on corvids that crow technology is not far behind human technology, and they are catching up.

- -

For example, many crow cultures have the ability to construct computing devices. These are not electronic, as human computing devices are, but operate through the actions of tiny insects, a species closely related to the termite, which has developed a symbiotic relationship with the crows. The birds provide them with food, and in return the insects build and operate their complex colonies that, with the help of the living creatures inside them, perform computations.

- -

Such colonies are usually located in big, long-lived nests. The birds and insects work together to build a network of bulbous clay structures, hidden between the twigs of the nest, in which the insects live and work.

- -

To communicate with other devices, these machines use light signals. The crows embed pieces of reflective material in special communication stalks, and the insects aim these to reflect light at another nest, encoding data as a sequence of quick flashes. This means that only nests that have an unbroken visual connection can communicate.

- -

Our friend the corvid expert has mapped the network of crow nests in the village of Hières-sur-Amby, on the banks of the river Rhône. This map shows the nests and their connections:

A network of crow nests in a small village
- -

In an astounding example of convergent evolution, crow computers run JavaScript. In this chapter we’ll write some basic networking functions for them.

- -

Callbacks

- -

One approach to asynchronous programming is to make functions that perform a slow action take an extra argument, a callback -function. The action is started, and when it finishes, the callback function is called with the result.

- -

As an example, the setTimeout function, available both in Node.js and in browsers, waits a given number of milliseconds (a second is a thousand milliseconds) and then calls a function.

- -
setTimeout(() => console.log("Tick"), 500);
- -

Waiting is not generally a very important type of work, but it can be useful when doing something like updating an animation or checking whether something is taking longer than a given amount of time.

- -

Performing multiple asynchronous actions in a row using callbacks means that you have to keep passing new functions to handle the continuation of the computation after the actions.

- -

Most crow nest computers have a long-term data storage bulb, where pieces of information are etched into twigs so that they can be retrieved later. Etching, or finding a piece of data, takes a moment, so the interface to long-term storage is asynchronous and uses callback functions.

- -

Storage bulbs store pieces of JSON-encodable data under names. A crow might store information about the places where it’s hidden food under the name "food caches", which could hold an array of names that point at other pieces of data, describing the actual cache. To look up a food cache in the storage bulbs of the Big Oak nest, a crow could run code like this:

- -
import {bigOak} from "./crow-tech";
-
-bigOak.readStorage("food caches", caches => {
-  let firstCache = caches[0];
-  bigOak.readStorage(firstCache, info => {
-    console.log(info);
-  });
-});
- -

(All binding names and strings have been translated from crow language to English.)

- -

This style of programming is workable, but the indentation level increases with each asynchronous action because you end up in another function. Doing more complicated things, such as running multiple actions at the same time, can get a little awkward.

- -

Crow nest computers are built to communicate using request-response pairs. That means one nest sends a message to another nest, which then immediately sends a message back, confirming receipt and possibly including a reply to a question asked in the message.

- -

Each message is tagged with a type, which determines how it is handled. Our code can define handlers for specific request types, and when such a request comes in, the handler is called to produce a response.

- -

The interface exported by the "./crow-tech" module provides callback-based functions for communication. Nests have a send method that sends off a request. It expects the name of the target nest, the type of the request, and the content of the request as its first three arguments, and it expects a function to call when a response comes in as its fourth and last argument.

- -
bigOak.send("Cow Pasture", "note", "Let's caw loudly at 7PM",
-            () => console.log("Note delivered."));
- -

But to make nests capable of receiving that request, we first have to define a request type named "note". The code that handles the requests has to run not just on this nest-computer but on all nests that can receive messages of this type. We’ll just assume that a crow flies over and installs our handler code on all the nests.

- -
import {defineRequestType} from "./crow-tech";
-
-defineRequestType("note", (nest, content, source, done) => {
-  console.log(`${nest.name} received note: ${content}`);
-  done();
-});
- -

The defineRequestType function defines a new type of request. The example adds support for "note" requests, which just sends a note to a given nest. Our implementation calls console.log so that we can verify that the request arrived. Nests have a name property that holds their name.

- -

The fourth argument given to the handler, done, is a callback function that it must call when it is done with the request. If we had used the handler’s return value as the response value, that would mean that a request handler can’t itself perform asynchronous actions. A function doing asynchronous work typically returns before the work is done, having arranged for a callback to be called when it completes. So we need some asynchronous mechanism—in this case, another callback function—to signal when a response is available.

- -

In a way, asynchronicity is contagious. Any function that calls a function that works asynchronously must itself be asynchronous, using a callback or similar mechanism to deliver its result. Calling a callback is somewhat more involved and error-prone than simply returning a value, so needing to structure large parts of your program that way is not great.

- -

Promises

- -

Working with abstract concepts is often easier when those concepts can be represented by values. In the case of asynchronous actions, you could, instead of arranging for a function to be called at some point in the future, return an object that represents this future event.

- -

This is what the standard class Promise is for. A promise is an asynchronous action that may complete at some point and produce a value. It is able to notify anyone who is interested when its value is available.

- -

The easiest way to create a promise is by calling Promise.resolve. This function ensures that the value you give it is wrapped in a promise. If it’s already a promise, it is simply returned—otherwise, you get a new promise that immediately finishes with your value as its result.

- -
let fifteen = Promise.resolve(15);
-fifteen.then(value => console.log(`Got ${value}`));
-// → Got 15
- -

To get the result of a promise, you can use its then method. This registers a callback function to be called when the promise resolves and produces a value. You can add multiple callbacks to a single promise, and they will be called, even if you add them after the promise has already resolved (finished).

- -

But that’s not all the then method does. It returns another promise, which resolves to the value that the handler function returns or, if that returns a promise, waits for that promise and then resolves to its result.

- -

It is useful to think of promises as a device to move values into an asynchronous reality. A normal value is simply there. A promised value is a value that might already be there or might appear at some point in the future. Computations defined in terms of promises act on such wrapped values and are executed asynchronously as the values become available.

- -

To create a promise, you can use Promise as a constructor. It has a somewhat odd interface—the constructor expects a function as argument, which it immediately calls, passing it a function that it can use to resolve the promise. It works this way, instead of for example with a resolve method, so that only the code that created the promise can resolve it.

- -

This is how you’d create a promise-based interface for the readStorage function:

- -
function storage(nest, name) {
-  return new Promise(resolve => {
-    nest.readStorage(name, result => resolve(result));
-  });
-}
-
-storage(bigOak, "enemies")
-  .then(value => console.log("Got", value));
- -

This asynchronous function returns a meaningful value. This is the main advantage of promises—they simplify the use of asynchronous functions. Instead of having to pass around callbacks, promise-based functions look similar to regular ones: they take input as arguments and return their output. The only difference is that the output may not be available yet.

- -

Failure

- -

Regular JavaScript computations can fail by throwing an exception. Asynchronous computations often need something like that. A network request may fail, or some code that is part of the asynchronous computation may throw an exception.

- -

One of the most pressing problems with the callback style of asynchronous programming is that it makes it extremely difficult to make sure failures are properly reported to the callbacks.

- -

A widely used convention is that the first argument to the callback is used to indicate that the action failed, and the second contains the value produced by the action when it was successful. Such callback functions must always check whether they received an exception and make sure that any problems they cause, including exceptions thrown by functions they call, are caught and given to the right function.

- -

Promises make this easier. They can be either resolved (the action finished successfully) or rejected (it failed). Resolve handlers (as registered with then) are called only when the action is successful, and rejections are automatically propagated to the new promise that is returned by then. And when a handler throws an exception, this automatically causes the promise produced by its then call to be rejected. So if any element in a chain of asynchronous actions fails, the outcome of the whole chain is marked as rejected, and no success handlers are called beyond the point where it failed.

- -

Much like resolving a promise provides a value, rejecting one also provides one, usually called the reason of the rejection. When an exception in a handler function causes the rejection, the exception value is used as the reason. Similarly, when a handler returns a promise that is rejected, that rejection flows into the next promise. There’s a Promise.reject function that creates a new, immediately rejected promise.

- -

To explicitly handle such rejections, promises have a catch method that registers a handler to be called when the promise is rejected, similar to how then handlers handle normal resolution. It’s also very much like then in that it returns a new promise, which resolves to the original promise’s value if it resolves normally and to the result of the catch handler otherwise. If a catch handler throws an error, the new promise is also rejected.

- -

As a shorthand, then also accepts a rejection handler as a second argument, so you can install both types of handlers in a single method call.

- -

A function passed to the Promise constructor receives a second argument, alongside the resolve function, which it can use to reject the new promise.

- -

The chains of promise values created by calls to then and catch can be seen as a pipeline through which asynchronous values or failures move. Since such chains are created by registering handlers, each link has a success handler or a rejection handler (or both) associated with it. Handlers that don’t match the type of outcome (success or failure) are ignored. But those that do match are called, and their outcome determines what kind of value comes next—success when it returns a non-promise value, rejection when it throws an exception, and the outcome of a promise when it returns one of those.

- -
new Promise((_, reject) => reject(new Error("Fail")))
-  .then(value => console.log("Handler 1"))
-  .catch(reason => {
-    console.log("Caught failure " + reason);
-    return "nothing";
-  })
-  .then(value => console.log("Handler 2", value));
-// → Caught failure Error: Fail
-// → Handler 2 nothing
- -

Much like an uncaught exception is handled by the environment, JavaScript environments can detect when a promise rejection isn’t handled and will report this as an error.

- -

Networks are hard

- -

Occasionally, there isn’t enough light for the crows’ mirror systems to transmit a signal or something is blocking the path of the signal. It is possible for a signal to be sent but never received.

- -

As it is, that will just cause the callback given to send to never be called, which will probably cause the program to stop without even noticing there is a problem. It would be nice if, after a given period of not getting a response, a request would time out and report failure.

- -

Often, transmission failures are random accidents, like a car’s headlight interfering with the light signals, and simply retrying the request may cause it to succeed. So while we’re at it, let’s make our request function automatically retry the sending of the request a few times before it gives up.

- -

And, since we’ve established that promises are a good thing, we’ll also make our request function return a promise. In terms of what they can express, callbacks and promises are equivalent. Callback-based functions can be wrapped to expose a promise-based interface, and vice versa.

- -

Even when a request and its response are successfully delivered, the response may indicate failure—for example, if the request tries to use a request type that hasn’t been defined or the handler throws an error. To support this, send and defineRequestType follow the convention mentioned before, where the first argument passed to callbacks is the failure reason, if any, and the second is the actual result.

- -

These can be translated to promise resolution and rejection by our wrapper.

- -
class Timeout extends Error {}
-
-function request(nest, target, type, content) {
-  return new Promise((resolve, reject) => {
-    let done = false;
-    function attempt(n) {
-      nest.send(target, type, content, (failed, value) => {
-        done = true;
-        if (failed) reject(failed);
-        else resolve(value);
-      });
-      setTimeout(() => {
-        if (done) return;
-        else if (n < 3) attempt(n + 1);
-        else reject(new Timeout("Timed out"));
-      }, 250);
-    }
-    attempt(1);
-  });
-}
- -

Because promises can be resolved (or rejected) only once, this will work. The first time resolve or reject is called determines the outcome of the promise, and any further calls, such as the timeout arriving after the request finishes or a request coming back after another request finished, are ignored.

- -

To build an asynchronous loop, for the retries, we need to use a recursive function—a regular loop doesn’t allow us to stop and wait for an asynchronous action. The attempt function makes a single attempt to send a request. It also sets a timeout that, if no response has come back after 250 milliseconds, either starts the next attempt or, if this was the fourth attempt, rejects the promise with an instance of Timeout as the reason.

- -

Retrying every quarter-second and giving up when no response has come in after a second is definitely somewhat arbitrary. It is even possible, if the request did come through but the handler is just taking a bit longer, for requests to be delivered multiple times. We’ll write our handlers with that problem in mind—duplicate messages should be harmless.

- -

In general, we will not be building a world-class, robust network today. But that’s okay—crows don’t have very high expectations yet when it comes to computing.

- -

To isolate ourselves from callbacks altogether, we’ll go ahead and also define a wrapper for defineRequestType that allows the handler function to return a promise or plain value and wires that up to the callback for us.

- -
function requestType(name, handler) {
-  defineRequestType(name, (nest, content, source,
-                           callback) => {
-    try {
-      Promise.resolve(handler(nest, content, source))
-        .then(response => callback(null, response),
-              failure => callback(failure));
-    } catch (exception) {
-      callback(exception);
-    }
-  });
-}
- -

Promise.resolve is used to convert the value returned by handler to a promise if it isn’t already.

- -

Note that the call to handler had to be wrapped in a try block to make sure any exception it raises directly is given to the callback. This nicely illustrates the difficulty of properly handling errors with raw callbacks—it is easy to forget to properly route exceptions like that, and if you don’t do it, failures won’t get reported to the right callback. Promises make this mostly automatic and thus less error-prone.

- -

Collections of promises

- -

Each nest computer keeps an array of other nests within transmission distance in its neighbors property. To check which of those are currently reachable, you could write a function that tries to send a "ping" request (a request that simply asks for a response) to each of them and see which ones come back.

- -

When working with collections of promises running at the same time, the Promise.all function can be useful. It returns a promise that waits for all of the promises in the array to resolve and then resolves to an array of the values that these promises produced (in the same order as the original array). If any promise is rejected, the result of Promise.all is itself rejected.

- -
requestType("ping", () => "pong");
-
-function availableNeighbors(nest) {
-  let requests = nest.neighbors.map(neighbor => {
-    return request(nest, neighbor, "ping")
-      .then(() => true, () => false);
-  });
-  return Promise.all(requests).then(result => {
-    return nest.neighbors.filter((_, i) => result[i]);
-  });
-}
- -

When a neighbor isn’t available, we don’t want the entire combined promise to fail since then we still wouldn’t know anything. So the function that is mapped over the set of neighbors to turn them into request promises attaches handlers that make successful requests produce true and rejected ones produce false.

- -

In the handler for the combined promise, filter is used to remove those elements from the neighbors array whose corresponding value is false. This makes use of the fact that filter passes the array index of the current element as a second argument to its filtering function (map, some, and similar higher-order array methods do the same).

- -

Network flooding

- -

The fact that nests can talk only to their neighbors greatly inhibits the usefulness of this network.

- -

For broadcasting information to the whole network, one solution is to set up a type of request that is automatically forwarded to neighbors. These neighbors then in turn forward it to their neighbors, until the whole network has received the message.

- -
import {everywhere} from "./crow-tech";
-
-everywhere(nest => {
-  nest.state.gossip = [];
-});
-
-function sendGossip(nest, message, exceptFor = null) {
-  nest.state.gossip.push(message);
-  for (let neighbor of nest.neighbors) {
-    if (neighbor == exceptFor) continue;
-    request(nest, neighbor, "gossip", message);
-  }
-}
-
-requestType("gossip", (nest, message, source) => {
-  if (nest.state.gossip.includes(message)) return;
-  console.log(`${nest.name} received gossip '${
-               message}' from ${source}`);
-  sendGossip(nest, message, source);
-});
- -

To avoid sending the same message around the network forever, each nest keeps an array of gossip strings that it has already seen. To define this array, we use the everywhere function—which runs code on every nest—to add a property to the nest’s state object, which is where we’ll keep nest-local state.

- -

When a nest receives a duplicate gossip message, which is very likely to happen with everybody blindly resending them, it ignores it. But when it receives a new message, it excitedly tells all its neighbors except for the one who sent it the message.

- -

This will cause a new piece of gossip to spread through the network like an ink stain in water. Even when some connections aren’t currently working, if there is an alternative route to a given nest, the gossip will reach it through there.

- -

This style of network communication is called flooding—it floods the network with a piece of information until all nodes have it.

- -

We can call sendGossip to see a message flow through the village.

- -
sendGossip(bigOak, "Kids with airgun in the park");
- -

Message routing

- -

If a given node wants to talk to a single other node, flooding is not a very efficient approach. Especially when the network is big, that would lead to a lot of useless data transfers.

- -

An alternative approach is to set up a way for messages to hop from node to node until they reach their destination. The difficulty with that is it requires knowledge about the layout of the network. To send a request in the direction of a faraway nest, it is necessary to know which neighboring nest gets it closer to its destination. Sending it in the wrong direction will not do much good.

- -

Since each nest knows only about its direct neighbors, it doesn’t have the information it needs to compute a route. We must somehow spread the information about these connections to all nests, preferably in a way that allows it to change over time, when nests are abandoned or new nests are built.

- -

We can use flooding again, but instead of checking whether a given message has already been received, we now check whether the new set of neighbors for a given nest matches the current set we have for it.

- -
requestType("connections", (nest, {name, neighbors},
-                            source) => {
-  let connections = nest.state.connections;
-  if (JSON.stringify(connections.get(name)) ==
-      JSON.stringify(neighbors)) return;
-  connections.set(name, neighbors);
-  broadcastConnections(nest, name, source);
-});
-
-function broadcastConnections(nest, name, exceptFor = null) {
-  for (let neighbor of nest.neighbors) {
-    if (neighbor == exceptFor) continue;
-    request(nest, neighbor, "connections", {
-      name,
-      neighbors: nest.state.connections.get(name)
-    });
-  }
-}
-
-everywhere(nest => {
-  nest.state.connections = new Map;
-  nest.state.connections.set(nest.name, nest.neighbors);
-  broadcastConnections(nest, nest.name);
-});
- -

The comparison uses JSON.stringify because ==, on objects or arrays, will return true only when the two are the exact same value, which is not what we need here. Comparing the JSON strings is a crude but effective way to compare their content.

- -

The nodes immediately start broadcasting their connections, which should, unless some nests are completely unreachable, quickly give every nest a map of the current network graph.

- -

A thing you can do with graphs is find routes in them, as we saw in Chapter 7. If we have a route toward a message’s destination, we know which direction to send it in.

- -

This findRoute function, which greatly resembles the findRoute from Chapter 7, searches for a way to reach a given node in the network. But instead of returning the whole route, it just returns the next step. That next nest will itself, using its current information about the network, decide where it sends the message.

- -
function findRoute(from, to, connections) {
-  let work = [{at: from, via: null}];
-  for (let i = 0; i < work.length; i++) {
-    let {at, via} = work[i];
-    for (let next of connections.get(at) || []) {
-      if (next == to) return via;
-      if (!work.some(w => w.at == next)) {
-        work.push({at: next, via: via || next});
-      }
-    }
-  }
-  return null;
-}
- -

Now we can build a function that can send long-distance messages. If the message is addressed to a direct neighbor, it is delivered as usual. If not, it is packaged in an object and sent to a neighbor that is closer to the target, using the "route" request type, which will cause that neighbor to repeat the same behavior.

- -
function routeRequest(nest, target, type, content) {
-  if (nest.neighbors.includes(target)) {
-    return request(nest, target, type, content);
-  } else {
-    let via = findRoute(nest.name, target,
-                        nest.state.connections);
-    if (!via) throw new Error(`No route to ${target}`);
-    return request(nest, via, "route",
-                   {target, type, content});
-  }
-}
-
-requestType("route", (nest, {target, type, content}) => {
-  return routeRequest(nest, target, type, content);
-});
- -

We can now send a message to the nest in the church tower, which is four network hops removed.

- -
routeRequest(bigOak, "Church Tower", "note",
-             "Incoming jackdaws!");
- -

We’ve constructed several layers of functionality on top of a primitive communication system to make it convenient to use. This is a nice (though simplified) model of how real computer networks work.

- -

A distinguishing property of computer networks is that they aren’t reliable—abstractions built on top of them can help, but you can’t abstract away network failure. So network programming is typically very much about anticipating and dealing with failures.

- -

Async functions

- -

To store important information, crows are known to duplicate it across nests. That way, when a hawk destroys a nest, the information isn’t lost.

- -

To retrieve a given piece of information that it doesn’t have in its own storage bulb, a nest computer might consult random other nests in the network until it finds one that has it.

- -
requestType("storage", (nest, name) => storage(nest, name));
-
-function findInStorage(nest, name) {
-  return storage(nest, name).then(found => {
-    if (found != null) return found;
-    else return findInRemoteStorage(nest, name);
-  });
-}
-
-function network(nest) {
-  return Array.from(nest.state.connections.keys());
-}
-
-function findInRemoteStorage(nest, name) {
-  let sources = network(nest).filter(n => n != nest.name);
-  function next() {
-    if (sources.length == 0) {
-      return Promise.reject(new Error("Not found"));
-    } else {
-      let source = sources[Math.floor(Math.random() *
-                                      sources.length)];
-      sources = sources.filter(n => n != source);
-      return routeRequest(nest, source, "storage", name)
-        .then(value => value != null ? value : next(),
-              next);
-    }
-  }
-  return next();
-}
- -

Because connections is a Map, Object.keys doesn’t work on it. It has a keys method, but that returns an iterator rather than an array. An iterator (or iterable value) can be converted to an array with the Array.from function.

- -

Even with promises this is some rather awkward code. Multiple asynchronous actions are chained together in non-obvious ways. We again need a recursive function (next) to model looping through the nests.

- -

And the thing the code actually does is completely linear—it always waits for the previous action to complete before starting the next one. In a synchronous programming model, it’d be simpler to express.

- -

The good news is that JavaScript allows you to write pseudo-synchronous code to describe asynchronous computation. An async function is a function that implicitly returns a promise and that can, in its body, await other promises in a way that looks synchronous.

- -

We can rewrite findInStorage like this:

- -
async function findInStorage(nest, name) {
-  let local = await storage(nest, name);
-  if (local != null) return local;
-
-  let sources = network(nest).filter(n => n != nest.name);
-  while (sources.length > 0) {
-    let source = sources[Math.floor(Math.random() *
-                                    sources.length)];
-    sources = sources.filter(n => n != source);
-    try {
-      let found = await routeRequest(nest, source, "storage",
-                                     name);
-      if (found != null) return found;
-    } catch (_) {}
-  }
-  throw new Error("Not found");
-}
- -

An async function is marked by the word async before the function keyword. Methods can also be made async by writing async before their name. When such a function or method is called, it returns a promise. As soon as the body returns something, that promise is resolved. If it throws an exception, the promise is rejected.

- -
findInStorage(bigOak, "events on 2017-12-21")
-  .then(console.log);
- -

Inside an async function, the word await can be put in front of an expression to wait for a promise to resolve and only then continue the execution of the function.

- -

Such a function no longer, like a regular JavaScript function, runs from start to completion in one go. Instead, it can be frozen at any point that has an await, and can be resumed at a later time.

- -

For non-trivial asynchronous code, this notation is usually more convenient than directly using promises. Even if you need to do something that doesn’t fit the synchronous model, such as perform multiple actions at the same time, it is easy to combine await with the direct use of promises.

- -

Generators

- -

This ability of functions to be paused and then resumed again is not exclusive to async functions. JavaScript also has a feature called generator functions. These are similar, but without the promises.

- -

When you define a function with function* (placing an asterisk after the word function), it becomes a generator. When you call a generator, it returns an iterator, which we already saw in Chapter 6.

- -
function* powers(n) {
-  for (let current = n;; current *= n) {
-    yield current;
-  }
-}
-
-for (let power of powers(3)) {
-  if (power > 50) break;
-  console.log(power);
-}
-// → 3
-// → 9
-// → 27
- -

Initially, when you call powers, the function is frozen at its start. Every time you call next on the iterator, the function runs until it hits a yield expression, which pauses it and causes the yielded value to become the next value produced by the iterator. When the function returns (the one in the example never does), the iterator is done.

- -

Writing iterators is often much easier when you use generator functions. The iterator for the Group class (from the exercise in Chapter 6) can be written with this generator:

- -
Group.prototype[Symbol.iterator] = function*() {
-  for (let i = 0; i < this.members.length; i++) {
-    yield this.members[i];
-  }
-};
- -

There’s no longer a need to create an object to hold the iteration state—generators automatically save their local state every time they yield.

- -

Such yield expressions may occur only directly in the generator function itself and not in an inner function you define inside of it. The state a generator saves, when yielding, is only its local environment and the position where it yielded.

- -

An async function is a special type of generator. It produces a promise when called, which is resolved when it returns (finishes) and rejected when it throws an exception. Whenever it yields (awaits) a promise, the result of that promise (value or thrown exception) is the result of the await expression.

- -

The event loop

- -

Asynchronous programs are executed piece by piece. Each piece may start some actions and schedule code to be executed when the action finishes or fails. In between these pieces, the program sits idle, waiting for the next action.

- -

So callbacks are not directly called by the code that scheduled them. If I call setTimeout from within a function, that function will have returned by the time the callback function is called. And when the callback returns, control does not go back to the function that scheduled it.

- -

Asynchronous behavior happens on its own empty function call -stack. This is one of the reasons that, without promises, managing exceptions across asynchronous code is hard. Since each callback starts with a mostly empty stack, your catch handlers won’t be on the stack when they throw an exception.

- -
try {
-  setTimeout(() => {
-    throw new Error("Woosh");
-  }, 20);
-} catch (_) {
-  // This will not run
-  console.log("Caught!");
-}
- -

No matter how closely together events—such as timeouts or incoming requests—happen, a JavaScript environment will run only one program at a time. You can think of this as it running a big loop around your program, called the event loop. When there’s nothing to be done, that loop is stopped. But as events come in, they are added to a queue, and their code is executed one after the other. Because no two things run at the same time, slow-running code might delay the handling of other events.

- -

This example sets a timeout but then dallies until after the timeout’s intended point of time, causing the timeout to be late.

- -
let start = Date.now();
-setTimeout(() => {
-  console.log("Timeout ran at", Date.now() - start);
-}, 20);
-while (Date.now() < start + 50) {}
-console.log("Wasted time until", Date.now() - start);
-// → Wasted time until 50
-// → Timeout ran at 55
- -

Promises always resolve or reject as a new event. Even if a promise is already resolved, waiting for it will cause your callback to run after the current script finishes, rather than right away.

- -
Promise.resolve("Done").then(console.log);
-console.log("Me first!");
-// → Me first!
-// → Done
- -

In later chapters we’ll see various other types of events that run on the event loop.

- -

Asynchronous bugs

- -

When your program runs synchronously, in a single go, there are no state changes happening except those that the program itself makes. For asynchronous programs this is different—they may have gaps in their execution during which other code can run.

- -

Let’s look at an example. One of the hobbies of our crows is to count the number of chicks that hatch throughout the village every year. Nests store this count in their storage bulbs. The following code tries to enumerate the counts from all the nests for a given year:

- -
function anyStorage(nest, source, name) {
-  if (source == nest.name) return storage(nest, name);
-  else return routeRequest(nest, source, "storage", name);
-}
-
-async function chicks(nest, year) {
-  let list = "";
-  await Promise.all(network(nest).map(async name => {
-    list += `${name}: ${
-      await anyStorage(nest, name, `chicks in ${year}`)
-    }\n`;
-  }));
-  return list;
-}
- -

The async name => part shows that arrow functions can also be made async by putting the word async in front of them.

- -

The code doesn’t immediately look suspicious...it maps the async arrow function over the set of nests, creating an array of promises, and then uses Promise.all to wait for all of these before returning the list they build up.

- -

But it is seriously broken. It’ll always return only a single line of output, listing the nest that was slowest to respond.

- -
chicks(bigOak, 2017).then(console.log);
- -

Can you work out why?

- -

The problem lies in the += operator, which takes the current value of list at the time where the statement starts executing and then, when the await finishes, sets the list binding to be that value plus the added string.

- -

But between the time where the statement starts executing and the time where it finishes there’s an asynchronous gap. The map expression runs before anything has been added to the list, so each of the += operators starts from an empty string and ends up, when its storage retrieval finishes, setting list to a single-line list—the result of adding its line to the empty string.

- -

This could have easily been avoided by returning the lines from the mapped promises and calling join on the result of Promise.all, instead of building up the list by changing a binding. As usual, computing new values is less error-prone than changing existing values.

- -
async function chicks(nest, year) {
-  let lines = network(nest).map(async name => {
-    return name + ": " +
-      await anyStorage(nest, name, `chicks in ${year}`);
-  });
-  return (await Promise.all(lines)).join("\n");
-}
- -

Mistakes like this are easy to make, especially when using await, and you should be aware of where the gaps in your code occur. An advantage of JavaScript’s explicit asynchronicity (whether through callbacks, promises, or await) is that spotting these gaps is relatively easy.

- -

Summary

- -

Asynchronous programming makes it possible to express waiting for long-running actions without freezing the program during these actions. JavaScript environments typically implement this style of programming using callbacks, functions that are called when the actions complete. An event loop schedules such callbacks to be called when appropriate, one after the other, so that their execution does not overlap.

- -

Programming asynchronously is made easier by promises, objects that represent actions that might complete in the future, and async functions, which allow you to write an asynchronous program as if it were synchronous.

- -

Exercises

- -

Tracking the scalpel

- -

The village crows own an old scalpel that they occasionally use on special missions—say, to cut through screen doors or packaging. To be able to quickly track it down, every time the scalpel is moved to another nest, an entry is added to the storage of both the nest that had it and the nest that took it, under the name "scalpel", with its new location as the value.

- -

This means that finding the scalpel is a matter of following the breadcrumb trail of storage entries, until you find a nest where that points at the nest itself.

- -

Write an async function locateScalpel that does this, starting at the nest on which it runs. You can use the anyStorage function defined earlier to access storage in arbitrary nests. The scalpel has been going around long enough that you may assume that every nest has a "scalpel" entry in its data storage.

- -

Next, write the same function again without using async and await.

- -

Do request failures properly show up as rejections of the returned promise in both versions? How?

- -
async function locateScalpel(nest) {
-  // Your code here.
-}
-
-function locateScalpel2(nest) {
-  // Your code here.
-}
-
-locateScalpel(bigOak).then(console.log);
-// → Butcher Shop
- -
- -

This can be done with a single loop that searches through the nests, moving forward to the next when it finds a value that doesn’t match the current nest’s name and returning the name when it finds a matching value. In the async function, a regular for or while loop can be used.

- -

To do the same in a plain function, you will have to build your loop using a recursive function. The easiest way to do this is to have that function return a promise by calling then on the promise that retrieves the storage value. Depending on whether that value matches the name of the current nest, the handler returns that value or a further promise created by calling the loop function again.

- -

Don’t forget to start the loop by calling the recursive function once from the main function.

- -

In the async function, rejected promises are converted to exceptions by await. When an async function throws an exception, its promise is rejected. So that works.

- -

If you implemented the non-async function as outlined earlier, the way then works also automatically causes a failure to end up in the returned promise. If a request fails, the handler passed to then isn’t called, and the promise it returns is rejected with the same reason.

- -
- -

Building Promise.all

- -

Given an array of promises, Promise.all returns a promise that waits for all of the promises in the array to finish. It then succeeds, yielding an array of result values. If a promise in the array fails, the promise returned by all fails too, with the failure reason from the failing promise.

- -

Implement something like this yourself as a regular function called Promise_all.

- -

Remember that after a promise has succeeded or failed, it can’t succeed or fail again, and further calls to the functions that resolve it are ignored. This can simplify the way you handle failure of your promise.

- -
function Promise_all(promises) {
-  return new Promise((resolve, reject) => {
-    // Your code here.
-  });
-}
-
-// Test code.
-Promise_all([]).then(array => {
-  console.log("This should be []:", array);
-});
-function soon(val) {
-  return new Promise(resolve => {
-    setTimeout(() => resolve(val), Math.random() * 500);
-  });
-}
-Promise_all([soon(1), soon(2), soon(3)]).then(array => {
-  console.log("This should be [1, 2, 3]:", array);
-});
-Promise_all([soon(1), Promise.reject("X"), soon(3)])
-  .then(array => {
-    console.log("We should not get here");
-  })
-  .catch(error => {
-    if (error != "X") {
-      console.log("Unexpected failure:", error);
-    }
-  });
- -
- -

The function passed to the Promise constructor will have to call then on each of the promises in the given array. When one of them succeeds, two things need to happen. The resulting value needs to be stored in the correct position of a result array, and we must check whether this was the last pending promise and finish our own promise if it was.

- -

The latter can be done with a counter that is initialized to the length of the input array and from which we subtract 1 every time a promise succeeds. When it reaches 0, we are done. Make sure you take into account the situation where the input array is empty (and thus no promise will ever resolve).

- -

Handling failure requires some thought but turns out to be extremely simple. Just pass the reject function of the wrapping promise to each of the promises in the array as a catch handler or as a second argument to then so that a failure in one of them triggers the rejection of the whole wrapper promise.

- -
-
diff --git a/docs/12_language.html b/docs/12_language.html deleted file mode 100644 index aa32b6168..000000000 --- a/docs/12_language.html +++ /dev/null @@ -1,502 +0,0 @@ - - - - - Project: A Programming Language :: Eloquent JavaScript - - - - - - -
- - -

Chapter 12Project: A Programming Language

- -
- -

The evaluator, which determines the meaning of expressions in a programming language, is just another program.

- -
Hal Abelson and Gerald Sussman, Structure and Interpretation of Computer Programs
- -
Picture of an egg with smaller eggs inside
- -

Building your own programming language is surprisingly easy (as long as you do not aim too high) and very enlightening.

- -

The main thing I want to show in this chapter is that there is no magic involved in building your own language. I’ve often felt that some human inventions were so immensely clever and complicated that I’d never be able to understand them. But with a little reading and experimenting, they often turn out to be quite mundane.

- -

We will build a programming language called Egg. It will be a tiny, simple language—but one that is powerful enough to express any computation you can think of. It will allow simple abstraction based on functions.

- -

Parsing

- -

The most immediately visible part of a programming language is its syntax, or notation. A parser is a program that reads a piece of text and produces a data structure that reflects the structure of the program contained in that text. If the text does not form a valid program, the parser should point out the error.

- -

Our language will have a simple and uniform syntax. Everything in Egg is an expression. An expression can be the name of a binding, a number, a string, or an application. Applications are used for function calls but also for constructs such as if or while.

- -

To keep the parser simple, strings in Egg do not support anything like backslash escapes. A string is simply a sequence of characters that are not double quotes, wrapped in double quotes. A number is a sequence of digits. Binding names can consist of any character that is not whitespace and that does not have a special meaning in the syntax.

- -

Applications are written the way they are in JavaScript, by putting parentheses after an expression and having any number of arguments between those parentheses, separated by commas.

- -
do(define(x, 10),
-   if(>(x, 5),
-      print("large"),
-      print("small")))
- -

The uniformity of the Egg language means that things that are operators in JavaScript (such as >) are normal bindings in this language, applied just like other functions. And since the syntax has no concept of a block, we need a do construct to represent doing multiple things in sequence.

- -

The data structure that the parser will use to describe a program consists of expression objects, each of which has a type property indicating the kind of expression it is and other properties to describe its content.

- -

Expressions of type "value" represent literal strings or numbers. Their value property contains the string or number value that they represent. Expressions of type "word" are used for identifiers (names). Such objects have a name property that holds the identifier’s name as a string. Finally, "apply" expressions represent applications. They have an operator property that refers to the expression that is being applied, as well as an args property that holds an array of argument expressions.

- -

The >(x, 5) part of the previous program would be represented like this:

- -
{
-  type: "apply",
-  operator: {type: "word", name: ">"},
-  args: [
-    {type: "word", name: "x"},
-    {type: "value", value: 5}
-  ]
-}
- -

Such a data structure is called a syntax tree. If you imagine the objects as dots and the links between them as lines between those dots, it has a treelike shape. The fact that expressions contain other expressions, which in turn might contain more expressions, is similar to the way tree branches split and split again.

The structure of a syntax tree
- -

Contrast this to the parser we wrote for the configuration file format in Chapter 9, which had a simple structure: it split the input into lines and handled those lines one at a time. There were only a few simple forms that a line was allowed to have.

- -

Here we must find a different approach. Expressions are not separated into lines, and they have a recursive structure. Application expressions contain other expressions.

- -

Fortunately, this problem can be solved very well by writing a parser function that is recursive in a way that reflects the recursive nature of the language.

- -

We define a function parseExpression, which takes a string as input and returns an object containing the data structure for the expression at the start of the string, along with the part of the string left after parsing this expression. When parsing subexpressions (the argument to an application, for example), this function can be called again, yielding the argument expression as well as the text that remains. This text may in turn contain more arguments or may be the closing parenthesis that ends the list of arguments.

- -

This is the first part of the parser:

- -
function parseExpression(program) {
-  program = skipSpace(program);
-  let match, expr;
-  if (match = /^"([^"]*)"/.exec(program)) {
-    expr = {type: "value", value: match[1]};
-  } else if (match = /^\d+\b/.exec(program)) {
-    expr = {type: "value", value: Number(match[0])};
-  } else if (match = /^[^\s(),#"]+/.exec(program)) {
-    expr = {type: "word", name: match[0]};
-  } else {
-    throw new SyntaxError("Unexpected syntax: " + program);
-  }
-
-  return parseApply(expr, program.slice(match[0].length));
-}
-
-function skipSpace(string) {
-  let first = string.search(/\S/);
-  if (first == -1) return "";
-  return string.slice(first);
-}
- -

Because Egg, like JavaScript, allows any amount of whitespace between its elements, we have to repeatedly cut the whitespace off the start of the program string. That is what the skipSpace function helps with.

- -

After skipping any leading space, parseExpression uses three regular expressions to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which one matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use SyntaxError instead of Error as the exception constructor, which is another standard error type, because it is a little more specific—it is also the error type thrown when an attempt is made to run an invalid JavaScript program.

- -

We then cut off the part that was matched from the program string and pass that, along with the object for the expression, to parseApply, which checks whether the expression is an application. If so, it parses a parenthesized list of arguments.

- -
function parseApply(expr, program) {
-  program = skipSpace(program);
-  if (program[0] != "(") {
-    return {expr: expr, rest: program};
-  }
-
-  program = skipSpace(program.slice(1));
-  expr = {type: "apply", operator: expr, args: []};
-  while (program[0] != ")") {
-    let arg = parseExpression(program);
-    expr.args.push(arg.expr);
-    program = skipSpace(arg.rest);
-    if (program[0] == ",") {
-      program = skipSpace(program.slice(1));
-    } else if (program[0] != ")") {
-      throw new SyntaxError("Expected ',' or ')'");
-    }
-  }
-  return parseApply(expr, program.slice(1));
-}
- -

If the next character in the program is not an opening parenthesis, this is not an application, and parseApply returns the expression it was given.

- -

Otherwise, it skips the opening parenthesis and creates the syntax -tree object for this application expression. It then recursively calls parseExpression to parse each argument until a closing parenthesis is found. The recursion is indirect, through parseApply and parseExpression calling each other.

- -

Because an application expression can itself be applied (such as in multiplier(2)(1)), parseApply must, after it has parsed an application, call itself again to check whether another pair of parentheses follows.

- -

This is all we need to parse Egg. We wrap it in a convenient parse function that verifies that it has reached the end of the input string after parsing the expression (an Egg program is a single expression), and that gives us the program’s data structure.

- -
function parse(program) {
-  let {expr, rest} = parseExpression(program);
-  if (skipSpace(rest).length > 0) {
-    throw new SyntaxError("Unexpected text after program");
-  }
-  return expr;
-}
-
-console.log(parse("+(a, 10)"));
-// → {type: "apply",
-//    operator: {type: "word", name: "+"},
-//    args: [{type: "word", name: "a"},
-//           {type: "value", value: 10}]}
- -

It works! It doesn’t give us very helpful information when it fails and doesn’t store the line and column on which each expression starts, which might be helpful when reporting errors later, but it’s good enough for our purposes.

- -

The evaluator

- -

What can we do with the syntax tree for a program? Run it, of course! And that is what the evaluator does. You give it a syntax tree and a scope object that associates names with values, and it will evaluate the expression that the tree represents and return the value that this produces.

- -
const specialForms = Object.create(null);
-
-function evaluate(expr, scope) {
-  if (expr.type == "value") {
-    return expr.value;
-  } else if (expr.type == "word") {
-    if (expr.name in scope) {
-      return scope[expr.name];
-    } else {
-      throw new ReferenceError(
-        `Undefined binding: ${expr.name}`);
-    }
-  } else if (expr.type == "apply") {
-    let {operator, args} = expr;
-    if (operator.type == "word" &&
-        operator.name in specialForms) {
-      return specialForms[operator.name](expr.args, scope);
-    } else {
-      let op = evaluate(operator, scope);
-      if (typeof op == "function") {
-        return op(...args.map(arg => evaluate(arg, scope)));
-      } else {
-        throw new TypeError("Applying a non-function.");
-      }
-    }
-  }
-}
- -

The evaluator has code for each of the expression types. A literal value expression produces its value. (For example, the expression 100 just evaluates to the number 100.) For a binding, we must check whether it is actually defined in the scope and, if it is, fetch the binding’s value.

- -

Applications are more involved. If they are a special form, like if, we do not evaluate anything and pass the argument expressions, along with the scope, to the function that handles this form. If it is a normal call, we evaluate the operator, verify that it is a function, and call it with the evaluated arguments.

- -

We use plain JavaScript function values to represent Egg’s function values. We will come back to this later, when the special form called fun is defined.

- -

The recursive structure of evaluate resembles the similar structure of the parser, and both mirror the structure of the language itself. It would also be possible to integrate the parser with the evaluator and evaluate during parsing, but splitting them up this way makes the program clearer.

- -

This is really all that is needed to interpret Egg. It is that simple. But without defining a few special forms and adding some useful values to the environment, you can’t do much with this language yet.

- -

Special forms

- -

The specialForms object is used to define special syntax in Egg. It associates words with functions that evaluate such forms. It is currently empty. Let’s add if.

- -
specialForms.if = (args, scope) => {
-  if (args.length != 3) {
-    throw new SyntaxError("Wrong number of args to if");
-  } else if (evaluate(args[0], scope) !== false) {
-    return evaluate(args[1], scope);
-  } else {
-    return evaluate(args[2], scope);
-  }
-};
- -

Egg’s if construct expects exactly three arguments. It will evaluate the first, and if the result isn’t the value false, it will evaluate the second. Otherwise, the third gets evaluated. This if form is more similar to JavaScript’s ternary ?: operator than to JavaScript’s if. It is an expression, not a statement, and it produces a value, namely, the result of the second or third argument.

- -

Egg also differs from JavaScript in how it handles the condition value to if. It will not treat things like zero or the empty string as false, only the precise value false.

- -

The reason we need to represent if as a special form, rather than a regular function, is that all arguments to functions are evaluated before the function is called, whereas if should evaluate only either its second or its third argument, depending on the value of the first.

- -

The while form is similar.

- -
specialForms.while = (args, scope) => {
-  if (args.length != 2) {
-    throw new SyntaxError("Wrong number of args to while");
-  }
-  while (evaluate(args[0], scope) !== false) {
-    evaluate(args[1], scope);
-  }
-
-  // Since undefined does not exist in Egg, we return false,
-  // for lack of a meaningful result.
-  return false;
-};
- -

Another basic building block is do, which executes all its arguments from top to bottom. Its value is the value produced by the last argument.

- -
specialForms.do = (args, scope) => {
-  let value = false;
-  for (let arg of args) {
-    value = evaluate(arg, scope);
-  }
-  return value;
-};
- -

To be able to create bindings and give them new values, we also create a form called define. It expects a word as its first argument and an expression producing the value to assign to that word as its second argument. Since define, like everything, is an expression, it must return a value. We’ll make it return the value that was assigned (just like JavaScript’s = operator).

- -
specialForms.define = (args, scope) => {
-  if (args.length != 2 || args[0].type != "word") {
-    throw new SyntaxError("Incorrect use of define");
-  }
-  let value = evaluate(args[1], scope);
-  scope[args[0].name] = value;
-  return value;
-};
- -

The environment

- -

The scope accepted by evaluate is an object with properties whose names correspond to binding names and whose values correspond to the values those bindings are bound to. Let’s define an object to represent the global scope.

- -

To be able to use the if construct we just defined, we must have access to Boolean values. Since there are only two Boolean values, we do not need special syntax for them. We simply bind two names to the values true and false and use them.

- -
const topScope = Object.create(null);
-
-topScope.true = true;
-topScope.false = false;
- -

We can now evaluate a simple expression that negates a Boolean value.

- -
let prog = parse(`if(true, false, true)`);
-console.log(evaluate(prog, topScope));
-// → false
- -

To supply basic arithmetic and comparison operators, we will also add some function values to the scope. In the interest of keeping the code short, we’ll use Function to synthesize a bunch of operator functions in a loop, instead of defining them individually.

- -
for (let op of ["+", "-", "*", "/", "==", "<", ">"]) {
-  topScope[op] = Function("a, b", `return a ${op} b;`);
-}
- -

A way to output values is also useful, so we’ll wrap console.log in a function and call it print.

- -
topScope.print = value => {
-  console.log(value);
-  return value;
-};
- -

That gives us enough elementary tools to write simple programs. The following function provides a convenient way to parse a program and run it in a fresh scope:

- -
function run(program) {
-  return evaluate(parse(program), Object.create(topScope));
-}
- -

We’ll use object prototype chains to represent nested scopes so that the program can add bindings to its local scope without changing the top-level scope.

- -
run(`
-do(define(total, 0),
-   define(count, 1),
-   while(<(count, 11),
-         do(define(total, +(total, count)),
-            define(count, +(count, 1)))),
-   print(total))
-`);
-// → 55
- -

This is the program we’ve seen several times before, which computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in less than 150 lines of code.

- -

Functions

- -

A programming language without functions is a poor programming language indeed.

- -

Fortunately, it isn’t hard to add a fun construct, which treats its last argument as the function’s body and uses all arguments before that as the names of the function’s parameters.

- -
specialForms.fun = (args, scope) => {
-  if (!args.length) {
-    throw new SyntaxError("Functions need a body");
-  }
-  let body = args[args.length - 1];
-  let params = args.slice(0, args.length - 1).map(expr => {
-    if (expr.type != "word") {
-      throw new SyntaxError("Parameter names must be words");
-    }
-    return expr.name;
-  });
-
-  return function() {
-    if (arguments.length != params.length) {
-      throw new TypeError("Wrong number of arguments");
-    }
-    let localScope = Object.create(scope);
-    for (let i = 0; i < arguments.length; i++) {
-      localScope[params[i]] = arguments[i];
-    }
-    return evaluate(body, localScope);
-  };
-};
- -

Functions in Egg get their own local scope. The function produced by the fun form creates this local scope and adds the argument bindings to it. It then evaluates the function body in this scope and returns the result.

- -
run(`
-do(define(plusOne, fun(a, +(a, 1))),
-   print(plusOne(10)))
-`);
-// → 11
-
-run(`
-do(define(pow, fun(base, exp,
-     if(==(exp, 0),
-        1,
-        *(base, pow(base, -(exp, 1)))))),
-   print(pow(2, 10)))
-`);
-// → 1024
- -

Compilation

- -

What we have built is an interpreter. During evaluation, it acts directly on the representation of the program produced by the parser.

- -

Compilation is the process of adding another step between the parsing and the running of a program, which transforms the program into something that can be evaluated more efficiently by doing as much work as possible in advance. For example, in well-designed languages it is obvious, for each use of a binding, which binding is being referred to, without actually running the program. This can be used to avoid looking up the binding by name every time it is accessed, instead directly fetching it from some predetermined memory location.

- -

Traditionally, compilation involves converting the program to machine code, the raw format that a computer’s processor can execute. But any process that converts a program to a different representation can be thought of as compilation.

- -

It would be possible to write an alternative evaluation strategy for Egg, one that first converts the program to a JavaScript program, uses Function to invoke the JavaScript compiler on it, and then runs the result. When done right, this would make Egg run very fast while still being quite simple to implement.

- -

If you are interested in this topic and willing to spend some time on it, I encourage you to try to implement such a compiler as an exercise.

- -

Cheating

- -

When we defined if and while, you probably noticed that they were more or less trivial wrappers around JavaScript’s own if and while. Similarly, the values in Egg are just regular old JavaScript values.

- -

If you compare the implementation of Egg, built on top of JavaScript, with the amount of work and complexity required to build a programming language directly on the raw functionality provided by a machine, the difference is huge. Regardless, this example ideally gave you an impression of the way programming languages work.

- -

And when it comes to getting something done, cheating is more effective than doing everything yourself. Though the toy language in this chapter doesn’t do anything that couldn’t be done better in JavaScript, there are situations where writing small languages helps get real work done.

- -

Such a language does not have to resemble a typical programming language. If JavaScript didn’t come equipped with regular expressions, for example, you could write your own parser and evaluator for regular expressions.

- -

Or imagine you are building a giant robotic dinosaur and need to program its behavior. JavaScript might not be the most effective way to do this. You might instead opt for a language that looks like this:

- -
behavior walk
-  perform when
-    destination ahead
-  actions
-    move left-foot
-    move right-foot
-
-behavior attack
-  perform when
-    Godzilla in-view
-  actions
-    fire laser-eyes
-    launch arm-rockets
- -

This is what is usually called a domain-specific language, a language tailored to express a narrow domain of knowledge. Such a language can be more expressive than a general-purpose language because it is designed to describe exactly the things that need to be described in its domain, and nothing else.

- -

Exercises

- -

Arrays

- -

Add support for arrays to Egg by adding the following three functions to the top scope: array(...values) to construct an array containing the argument values, length(array) to get an array’s length, and element(array, n) to fetch the nth element from an array.

- -
// Modify these definitions...
-
-topScope.array = "...";
-
-topScope.length = "...";
-
-topScope.element = "...";
-
-run(`
-do(define(sum, fun(array,
-     do(define(i, 0),
-        define(sum, 0),
-        while(<(i, length(array)),
-          do(define(sum, +(sum, element(array, i))),
-             define(i, +(i, 1)))),
-        sum))),
-   print(sum(array(1, 2, 3))))
-`);
-// → 6
- -
- -

The easiest way to do this is to represent Egg arrays with JavaScript arrays.

- -

The values added to the top scope must be functions. By using a rest argument (with triple-dot notation), the definition of array can be very simple.

- -
- -

Closure

- -

The way we have defined fun allows functions in Egg to reference the surrounding scope, allowing the function’s body to use local values that were visible at the time the function was defined, just like JavaScript functions do.

- -

The following program illustrates this: function f returns a function that adds its argument to f’s argument, meaning that it needs access to the local scope inside f to be able to use binding a.

- -
run(`
-do(define(f, fun(a, fun(b, +(a, b)))),
-   print(f(4)(5)))
-`);
-// → 9
- -

Go back to the definition of the fun form and explain which mechanism causes this to work.

- -
- -

Again, we are riding along on a JavaScript mechanism to get the equivalent feature in Egg. Special forms are passed the local scope in which they are evaluated so that they can evaluate their subforms in that scope. The function returned by fun has access to the scope argument given to its enclosing function and uses that to create the function’s local scope when it is called.

- -

This means that the prototype of the local scope will be the scope in which the function was created, which makes it possible to access bindings in that scope from the function. This is all there is to implementing closure (though to compile it in a way that is actually efficient, you’d need to do some more work).

- -
- -

Comments

- -

It would be nice if we could write comments in Egg. For example, whenever we find a hash sign (#), we could treat the rest of the line as a comment and ignore it, similar to // in JavaScript.

- -

We do not have to make any big changes to the parser to support this. We can simply change skipSpace to skip comments as if they are whitespace so that all the points where skipSpace is called will now also skip comments. Make this change.

- -
// This is the old skipSpace. Modify it...
-function skipSpace(string) {
-  let first = string.search(/\S/);
-  if (first == -1) return "";
-  return string.slice(first);
-}
-
-console.log(parse("# hello\nx"));
-// → {type: "word", name: "x"}
-
-console.log(parse("a # one\n   # two\n()"));
-// → {type: "apply",
-//    operator: {type: "word", name: "a"},
-//    args: []}
- -
- -

Make sure your solution handles multiple comments in a row, with potentially whitespace between or after them.

- -

A regular expression is probably the easiest way to solve this. Write something that matches “whitespace or a comment, zero or more times”. Use the exec or match method and look at the length of the first element in the returned array (the whole match) to find out how many characters to slice off.

- -
- -

Fixing scope

- -

Currently, the only way to assign a binding a value is define. This construct acts as a way both to define new bindings and to give existing ones a new value.

- -

This ambiguity causes a problem. When you try to give a nonlocal binding a new value, you will end up defining a local one with the same name instead. Some languages work like this by design, but I’ve always found it an awkward way to handle scope.

- -

Add a special form set, similar to define, which gives a binding a new value, updating the binding in an outer scope if it doesn’t already exist in the inner scope. If the binding is not defined at all, throw a ReferenceError (another standard error type).

- -

The technique of representing scopes as simple objects, which has made things convenient so far, will get in your way a little at this point. You might want to use the Object.getPrototypeOf function, which returns the prototype of an object. Also remember that scopes do not derive from Object.prototype, so if you want to call hasOwnProperty on them, you have to use this clumsy expression:

- -
Object.prototype.hasOwnProperty.call(scope, name);
- -
specialForms.set = (args, scope) => {
-  // Your code here.
-};
-
-run(`
-do(define(x, 4),
-   define(setx, fun(val, set(x, val))),
-   setx(50),
-   print(x))
-`);
-// → 50
-run(`set(quux, true)`);
-// → Some kind of ReferenceError
- -
- -

You will have to loop through one scope at a time, using Object.getPrototypeOf to go to the next outer scope. For each scope, use hasOwnProperty to find out whether the binding, indicated by the name property of the first argument to set, exists in that scope. If it does, set it to the result of evaluating the second argument to set and then return that value.

- -

If the outermost scope is reached (Object.getPrototypeOf returns null) and we haven’t found the binding yet, it doesn’t exist, and an error should be thrown.

- -
-
diff --git a/docs/13_browser.html b/docs/13_browser.html deleted file mode 100644 index 3649e531a..000000000 --- a/docs/13_browser.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - JavaScript and the Browser :: Eloquent JavaScript - - - - - - -
- - -

Chapter 13JavaScript and the Browser

- -
- -

The dream behind the Web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished.

- -
Tim Berners-Lee, The World Wide Web: A very short personal history
- -
Picture of a telephone switchboard
- -

The next chapters of this book will talk about web browsers. Without web browsers, there would be no JavaScript. Or even if there were, no one would ever have paid any attention to it.

- -

Web technology has been decentralized from the start, not just technically but also in the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which then, sometimes, ended up being adopted by others—and finally set down as in standards.

- -

This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose collaboration (or occasionally open hostility). On the other hand, the haphazard way in which the Web was developed means that the resulting system is not exactly a shining example of internal consistency. Some parts of it are downright confusing and poorly conceived.

- -

Networks and the Internet

- -

Computer networks have been around since the 1950s. If you put cables between two or more computers and allow them to send data back and forth through these cables, you can do all kinds of wonderful things.

- -

And if connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the Internet. It has lived up to its promise.

- -

A computer can use this network to shoot bits at another computer. For any effective communication to arise out of this bit-shooting, the computers on both ends must know what the bits are supposed to represent. The meaning of any given sequence of bits depends entirely on the kind of thing that it is trying to express and on the encoding mechanism used.

- -

A network protocol describes a style of communication over a network. There are protocols for sending email, for fetching email, for sharing files, and even for controlling computers that happen to be infected by malicious software.

- -

For example, the Hypertext Transfer Protocol (HTTP) is a protocol for retrieving named resources (chunks of information, such as web pages or pictures). It specifies that the side making the request should start with a line like this, naming the resource and the version of the protocol that it is trying to use:

- -
GET /index.html HTTP/1.1
- -

There are a lot more rules about the way the requester can include more information in the request and the way the other side, which returns the resource, packages up its content. We’ll look at HTTP in a little more detail in Chapter 18.

- -

Most protocols are built on top of other protocols. HTTP treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. As we saw in Chapter 11, ensuring those things is already a rather difficult problem.

- -

The Transmission Control Protocol (TCP) is a protocol that addresses this problem. All Internet-connected devices “speak” it, and most communication on the Internet is built on top of it.

- -

A TCP connection works as follows: one computer must be waiting, or listening, for other computers to start talking to it. To be able to listen for different kinds of communication at the same time on a single machine, each listener has a number (called a port) associated with it. Most protocols specify which port should be used by default. For example, when we want to send an email using the SMTP protocol, the machine through which we send it is expected to be listening on port 25.

- -

Another computer can then establish a connection by connecting to the target machine using the correct port number. If the target machine can be reached and is listening on that port, the connection is successfully created. The listening computer is called the server, and the connecting computer is called the client.

- -

Such a connection acts as a two-way pipe through which bits can flow—the machines on both ends can put data into it. Once the bits are successfully transmitted, they can be read out again by the machine on the other side. This is a convenient model. You could say that TCP provides an abstraction of the network.

- -

The Web

- -

The World Wide Web (not to be confused with the Internet as a whole) is a set of protocols and formats that allow us to visit web pages in a browser. The “Web” part in the name refers to the fact that such pages can easily link to each other, thus connecting into a huge mesh that users can move through.

- -

To become part of the Web, all you need to do is connect a machine to the Internet and have it listen on port 80 with the HTTP protocol so that other computers can ask it for documents.

- -

Each document on the Web is named by a Uniform Resource Locator (URL), which looks something like this:

- -
  http://eloquentjavascript.net/13_browser.html
- |      |                      |               |
- protocol       server               path
- -

The first part tells us that this URL uses the HTTP protocol (as opposed to, for example, encrypted HTTP, which would be https://). Then comes the part that identifies which server we are requesting the document from. Last is a path string that identifies the specific document (or resource) we are interested in.

- -

Machines connected to the Internet get an IP address, which is a number that can be used to send messages to that machine, and looks something like 149.210.142.219 or 2001:4860:4860::8888. But lists of more or less random numbers are hard to remember and awkward to type, so you can instead register a domain name for a specific address or set of addresses. I registered eloquentjavascript.net to point at the IP address of a machine I control and can thus use that domain name to serve web pages.

- -

If you type this URL into your browser’s address bar, the browser will try to retrieve and display the document at that URL. First, your browser has to find out what address eloquentjavascript.net refers to. Then, using the HTTP protocol, it will make a connection to the server at that address and ask for the resource /13_browser.html. If all goes well, the server sends back a document, which your browser then displays on your screen.

- -

HTML

- -

HTML, which stands for Hypertext Markup Language, is the document format used for web pages. An HTML document contains text, as well as tags that give structure to the text, describing things such as links, paragraphs, and headings.

- -

A short HTML document might look like this:

- -
<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>My home page</title>
-  </head>
-  <body>
-    <h1>My home page</h1>
-    <p>Hello, I am Marijn and this is my home page.</p>
-    <p>I also wrote a book! Read it
-      <a href="http://eloquentjavascript.net">here</a>.</p>
-  </body>
-</html>
- -

The tags, wrapped in angle brackets (< and >, the symbols for less than and greater than), provide information about the structure of the document. The other text is just plain text.

- -

The document starts with <!doctype html>, which tells the browser to interpret the page as modern HTML, as opposed to various dialects that were in use in the past.

- -

HTML documents have a head and a body. The head contains information about the document, and the body contains the document itself. In this case, the head declares that the title of this document is “My home page” and that it uses the UTF-8 encoding, which is a way to encode Unicode text as binary data. The document’s body contains a heading (<h1>, meaning “heading 1”—<h2> to <h6> produce subheadings) and two paragraphs (<p>).

- -

Tags come in several forms. An element, such as the body, a paragraph, or a link, is started by an opening tag like <p> and ended by a closing tag like </p>. Some opening tags, such as the one for the link (<a>), contain extra information in the form of name="value" pairs. These are called attributes. In this case, the destination of the link is indicated with href="http://eloquentjavascript.net", where href stands for “hypertext reference”.

- -

Some kinds of tags do not enclose anything and thus do not need to be closed. The metadata tag <meta charset="utf-8"> is an example of this.

- -

To be able to include angle brackets in the text of a document, even though they have a special meaning in HTML, yet another form of special notation has to be introduced. A plain opening angle bracket is written as &lt; (“less than”), and a closing bracket is written as &gt; (“greater than”). In HTML, an ampersand (&) character followed by a name or character code and a semicolon (;) is called an entity and will be replaced by the character it encodes.

- -

This is analogous to the way backslashes are used in JavaScript strings. Since this mechanism gives ampersand characters a special meaning, too, they need to be escaped as &amp;. Inside attribute values, which are wrapped in double quotes, &quot; can be used to insert an actual quote character.

- -

HTML is parsed in a remarkably error-tolerant way. When tags that should be there are missing, the browser reconstructs them. The way in which this is done has been standardized, and you can rely on all modern browsers to do it in the same way.

- -

The following document will be treated just like the one shown previously:

- -
<!doctype html>
-
-<meta charset=utf-8>
-<title>My home page</title>
-
-<h1>My home page</h1>
-<p>Hello, I am Marijn and this is my home page.
-<p>I also wrote a book! Read it
-  <a href=http://eloquentjavascript.net>here</a>.
- -

The <html>, <head>, and <body> tags are gone completely. The browser knows that <meta> and <title> belong in the head and that <h1> means the body has started. Furthermore, I am no longer explicitly closing the paragraphs since opening a new paragraph or ending the document will close them implicitly. The quotes around the attribute values are also gone.

- -

This book will usually omit the <html>, <head>, and <body> tags from examples to keep them short and free of clutter. But I will close tags and include quotes around attributes.

- -

I will also usually omit the doctype and charset declaration. This is not to be taken as an encouragement to drop these from HTML documents. Browsers will often do ridiculous things when you forget them. You should consider the doctype and the charset metadata to be implicitly present in examples, even when they are not actually shown in the text.

- -

HTML and JavaScript

- -

In the context of this book, the most important HTML tag is <script>. This tag allows us to include a piece of JavaScript in a document.

- -
<h1>Testing alert</h1>
-<script>alert("hello!");</script>
- -

Such a script will run as soon as its <script> tag is encountered while the browser reads the HTML. This page will pop up a dialog when opened—the alert function resembles prompt, in that it pops up a little window, but only shows a message without asking for input.

- -

Including large programs directly in HTML documents is often impractical. The <script> tag can be given an src attribute to fetch a script file (a text file containing a JavaScript program) from a URL.

- -
<h1>Testing alert</h1>
-<script src="code/hello.js"></script>
- -

The code/hello.js file included here contains the same program—alert("hello!"). When an HTML page references other URLs as part of itself—for example, an image file or a script—web browsers will retrieve them immediately and include them in the page.

- -

A script tag must always be closed with </script>, even if it refers to a script file and doesn’t contain any code. If you forget this, the rest of the page will be interpreted as part of the script.

- -

You can load ES modules (see Chapter 10) in the browser by giving your script tag a type="module" attribute. Such modules can depend on other modules by using URLs relative to themselves as module names in import declarations.

- -

Some attributes can also contain a JavaScript program. The <button> tag shown next (which shows up as a button) has an onclick attribute. The attribute’s value will be run whenever the button is clicked.

- -
<button onclick="alert('Boom!');">DO NOT PRESS</button>
- -

Note that I had to use single quotes for the string in the onclick attribute because double quotes are already used to quote the whole attribute. I could also have used &quot;.

- -

In the sandbox

- -

Running programs downloaded from the Internet is potentially dangerous. You do not know much about the people behind most sites you visit, and they do not necessarily mean well. Running programs by people who do not mean well is how you get your computer infected by viruses, your data stolen, and your accounts hacked.

- -

Yet the attraction of the Web is that you can browse it without necessarily trusting all the pages you visit. This is why browsers severely limit the things a JavaScript program may do: it can’t look at the files on your computer or modify anything not related to the web page it was embedded in.

- -

Isolating a programming environment in this way is called sandboxing, the idea being that the program is harmlessly playing in a sandbox. But you should imagine this particular kind of sandbox as having a cage of thick steel bars over it so that the programs playing in it can’t actually get out.

- -

The hard part of sandboxing is allowing the programs enough room to be useful yet at the same time restricting them from doing anything dangerous. Lots of useful functionality, such as communicating with other servers or reading the content of the copy-paste clipboard, can also be used to do problematic, privacy-invading things.

- -

Every now and then, someone comes up with a new way to circumvent the limitations of a browser and do something harmful, ranging from leaking minor private information to taking over the whole machine that the browser runs on. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized, rather than secretly exploited by some government agency or mafia.

- -

Compatibility and the browser wars

- -

In the early stages of the Web, a browser called Mosaic dominated the market. After a few years, the balance shifted to Netscape, which was then, in turn, largely supplanted by Microsoft’s Internet Explorer. At any point where a single browser was dominant, that browser’s vendor would feel entitled to unilaterally invent new features for the Web. Since most users used the most popular browser, websites would simply start using those features—never mind the other browsers.

- -

This was the dark age of compatibility, often called the browser wars. Web developers were left with not one unified Web but two or three incompatible platforms. To make things worse, the browsers in use around 2003 were all full of bugs, and of course the bugs were different for each browser. Life was hard for people writing web pages.

- -

Mozilla Firefox, a not-for-profit offshoot of Netscape, challenged Internet Explorer’s position in the late 2000s. Because Microsoft was not particularly interested in staying competitive at the time, Firefox took a lot of market share away from it. Around the same time, Google introduced its Chrome browser, and Apple’s Safari browser gained popularity, leading to a situation where there were four major players, rather than one.

- -

The new players had a more serious attitude toward standards and better engineering practices, giving us less incompatibility and fewer bugs. Microsoft, seeing its market share crumble, came around and adopted these attitudes in its Edge browser, which replaces Internet Explorer. If you are starting to learn web development today, consider yourself lucky. The latest versions of the major browsers behave quite uniformly and have relatively few bugs.

-
diff --git a/docs/14_dom.html b/docs/14_dom.html deleted file mode 100644 index f816c432a..000000000 --- a/docs/14_dom.html +++ /dev/null @@ -1,572 +0,0 @@ - - - - - The Document Object Model :: Eloquent JavaScript - - - - - - -
- - -

Chapter 14The Document Object Model

- -
- -

Too bad! Same old story! Once you’ve finished building your house you notice you’ve accidentally learned something that you really should have known—before you started.

- -
Friedrich Nietzsche, Beyond Good and Evil
- -
Picture of a tree with letters and scripts hanging from its branches
- -

When you open a web page in your browser, the browser retrieves the page’s HTML text and parses it, much like the way our parser from Chapter 12 parsed programs. The browser builds up a model of the document’s structure and uses this model to draw the page on the screen.

- -

This representation of the document is one of the toys that a JavaScript program has available in its sandbox. It is a data -structure that you can read or modify. It acts as a live data structure: when it’s modified, the page on the screen is updated to reflect the changes.

- -

Document structure

- -

You can imagine an HTML document as a nested set of boxes. Tags such as <body> and </body> enclose other tags, which in turn contain other tags or text. Here’s the example document from the previous chapter:

- -
<!doctype html>
-<html>
-  <head>
-    <title>My home page</title>
-  </head>
-  <body>
-    <h1>My home page</h1>
-    <p>Hello, I am Marijn and this is my home page.</p>
-    <p>I also wrote a book! Read it
-      <a href="http://eloquentjavascript.net">here</a>.</p>
-  </body>
-</html>
- -

This page has the following structure:

HTML document as nested boxes
- -

The data structure the browser uses to represent the document follows this shape. For each box, there is an object, which we can interact with to find out things such as what HTML tag it represents and which boxes and text it contains. This representation is called the Document Object Model, or DOM for short.

- -

The global binding document gives us access to these objects. Its documentElement property refers to the object representing the <html> tag. Since every HTML document has a head and a body, it also has head and body properties, pointing at those elements.

- -

Trees

- -

Think back to the syntax trees from Chapter 12 for a moment. Their structures are strikingly similar to the structure of a browser’s document. Each node may refer to other nodes, children, which in turn may have their own children. This shape is typical of nested structures where elements can contain subelements that are similar to themselves.

- -

We call a data structure a tree when it has a branching structure, has no cycles (a node may not contain itself, directly or indirectly), and has a single, well-defined root. In the case of the DOM, document.documentElement serves as the root.

- -

Trees come up a lot in computer science. In addition to representing recursive structures such as HTML documents or programs, they are often used to maintain sorted sets of data because elements can usually be found or inserted more efficiently in a tree than in a flat array.

- -

A typical tree has different kinds of nodes. The syntax tree for the Egg language had identifiers, values, and application nodes. Application nodes may have children, whereas identifiers and values are leaves, or nodes without children.

- -

The same goes for the DOM. Nodes for elements, which represent HTML tags, determine the structure of the document. These can have child nodes. An example of such a node is document.body. Some of these children can be leaf nodes, such as pieces of text or comment nodes.

- -

Each DOM node object has a nodeType property, which contains a code (number) that identifies the type of node. Elements have code 1, which is also defined as the constant property Node.ELEMENT_NODE. Text nodes, representing a section of text in the document, get code 3 (Node.TEXT_NODE). Comments have code 8 (Node.COMMENT_NODE).

- -

Another way to visualize our document tree is as follows:

HTML document as a tree
- -

The leaves are text nodes, and the arrows indicate parent-child relationships between nodes.

- -

The standard

- -

Using cryptic numeric codes to represent node types is not a very JavaScript-like thing to do. Later in this chapter, we’ll see that other parts of the DOM interface also feel cumbersome and alien. The reason for this is that the DOM wasn’t designed for just JavaScript. Rather, it tries to be a language-neutral interface that can be used in other systems as well—not just for HTML but also for XML, which is a generic data format with an HTML-like syntax.

- -

This is unfortunate. Standards are often useful. But in this case, the advantage (cross-language consistency) isn’t all that compelling. Having an interface that is properly integrated with the language you are using will save you more time than having a familiar interface across languages.

- -

As an example of this poor integration, consider the childNodes property that element nodes in the DOM have. This property holds an array-like object, with a length property and properties labeled by numbers to access the child nodes. But it is an instance of the NodeList type, not a real array, so it does not have methods such as slice and map.

- -

Then there are issues that are simply poor design. For example, there is no way to create a new node and immediately add children or attributes to it. Instead, you have to first create it and then add the children and attributes one by one, using side effects. Code that interacts heavily with the DOM tends to get long, repetitive, and ugly.

- -

But these flaws aren’t fatal. Since JavaScript allows us to create our own abstractions, it is possible to design improved ways to express the operations you are performing. Many libraries intended for browser programming come with such tools.

- -

Moving through the tree

- -

DOM nodes contain a wealth of links to other nearby nodes. The following diagram illustrates these:

Links between DOM nodes
- -

Although the diagram shows only one link of each type, every node has a parentNode property that points to the node it is part of, if any. Likewise, every element node (node type 1) has a childNodes property that points to an array-like object holding its children.

- -

In theory, you could move anywhere in the tree using just these parent and child links. But JavaScript also gives you access to a number of additional convenience links. The firstChild and lastChild properties point to the first and last child elements or have the value null for nodes without children. Similarly, previousSibling and nextSibling point to adjacent nodes, which are nodes with the same parent that appear immediately before or after the node itself. For a first child, previousSibling will be null, and for a last child, nextSibling will be null.

- -

There’s also the children property, which is like childNodes but contains only element (type 1) children, not other types of child nodes. This can be useful when you aren’t interested in text nodes.

- -

When dealing with a nested data structure like this one, recursive functions are often useful. The following function scans a document for text nodes containing a given string and returns true when it has found one:

- -
function talksAbout(node, string) {
-  if (node.nodeType == Node.ELEMENT_NODE) {
-    for (let i = 0; i < node.childNodes.length; i++) {
-      if (talksAbout(node.childNodes[i], string)) {
-        return true;
-      }
-    }
-    return false;
-  } else if (node.nodeType == Node.TEXT_NODE) {
-    return node.nodeValue.indexOf(string) > -1;
-  }
-}
-
-console.log(talksAbout(document.body, "book"));
-// → true
- -

Because childNodes is not a real array, we cannot loop over it with for/of and have to run over the index range using a regular for loop or use Array.from.

- -

The nodeValue property of a text node holds the string of text that it represents.

- -

Finding elements

- -

Navigating these links among parents, children, and siblings is often useful. But if we want to find a specific node in the document, reaching it by starting at document.body and following a fixed path of properties is a bad idea. Doing so bakes assumptions into our program about the precise structure of the document—a structure you might want to change later. Another complicating factor is that text nodes are created even for the whitespace between nodes. The example document’s <body> tag does not have just three children (<h1> and two <p> elements) but actually has seven: those three, plus the spaces before, after, and between them.

- -

So if we want to get the href attribute of the link in that document, we don’t want to say something like “Get the second child of the sixth child of the document body”. It’d be better if we could say “Get the first link in the document”. And we can.

- -
let link = document.body.getElementsByTagName("a")[0];
-console.log(link.href);
- -

All element nodes have a getElementsByTagName method, which collects all elements with the given tag name that are descendants (direct or indirect children) of that node and returns them as an array-like -object.

- -

To find a specific single node, you can give it an id attribute and use document.getElementById instead.

- -
<p>My ostrich Gertrude:</p>
-<p><img id="gertrude" src="img/ostrich.png"></p>
-
-<script>
-  let ostrich = document.getElementById("gertrude");
-  console.log(ostrich.src);
-</script>
- -

A third, similar method is getElementsByClassName, which, like getElementsByTagName, searches through the contents of an element node and retrieves all elements that have the given string in their class attribute.

- -

Changing the document

- -

Almost everything about the DOM data structure can be changed. The shape of the document tree can be modified by changing parent-child relationships. Nodes have a remove method to remove them from their current parent node. To add a child node to an element node, we can use appendChild, which puts it at the end of the list of children, or insertBefore, which inserts the node given as the first argument before the node given as the second argument.

- -
<p>One</p>
-<p>Two</p>
-<p>Three</p>
-
-<script>
-  let paragraphs = document.body.getElementsByTagName("p");
-  document.body.insertBefore(paragraphs[2], paragraphs[0]);
-</script>
- -

A node can exist in the document in only one place. Thus, inserting paragraph Three in front of paragraph One will first remove it from the end of the document and then insert it at the front, resulting in Three/One/Two. All operations that insert a node somewhere will, as a side effect, cause it to be removed from its current position (if it has one).

- -

The replaceChild method is used to replace a child node with another one. It takes as arguments two nodes: a new node and the node to be replaced. The replaced node must be a child of the element the method is called on. Note that both replaceChild and insertBefore expect the new node as their first argument.

- -

Creating nodes

- -

Say we want to write a script that replaces all images (<img> tags) in the document with the text held in their alt attributes, which specifies an alternative textual representation of the image.

- -

This involves not only removing the images but adding a new text node to replace them. Text nodes are created with the document.createTextNode method.

- -
<p>The <img src="img/cat.png" alt="Cat"> in the
-  <img src="img/hat.png" alt="Hat">.</p>
-
-<p><button onclick="replaceImages()">Replace</button></p>
-
-<script>
-  function replaceImages() {
-    let images = document.body.getElementsByTagName("img");
-    for (let i = images.length - 1; i >= 0; i--) {
-      let image = images[i];
-      if (image.alt) {
-        let text = document.createTextNode(image.alt);
-        image.parentNode.replaceChild(text, image);
-      }
-    }
-  }
-</script>
- -

Given a string, createTextNode gives us a text node that we can insert into the document to make it show up on the screen.

- -

The loop that goes over the images starts at the end of the list. This is necessary because the node list returned by a method like getElementsByTagName (or a property like childNodes) is live. That is, it is updated as the document changes. If we started from the front, removing the first image would cause the list to lose its first element so that the second time the loop repeats, where i is 1, it would stop because the length of the collection is now also 1.

- -

If you want a solid collection of nodes, as opposed to a live one, you can convert the collection to a real array by calling Array.from.

- -
let arrayish = {0: "one", 1: "two", length: 2};
-let array = Array.from(arrayish);
-console.log(array.map(s => s.toUpperCase()));
-// → ["ONE", "TWO"]
- -

To create element nodes, you can use the document.createElement method. This method takes a tag name and returns a new empty node of the given type.

- -

The following example defines a utility elt, which creates an element node and treats the rest of its arguments as children to that node. This function is then used to add an attribution to a quote.

- -
<blockquote id="quote">
-  No book can ever be finished. While working on it we learn
-  just enough to find it immature the moment we turn away
-  from it.
-</blockquote>
-
-<script>
-  function elt(type, ...children) {
-    let node = document.createElement(type);
-    for (let child of children) {
-      if (typeof child != "string") node.appendChild(child);
-      else node.appendChild(document.createTextNode(child));
-    }
-    return node;
-  }
-
-  document.getElementById("quote").appendChild(
-    elt("footer", "—",
-        elt("strong", "Karl Popper"),
-        ", preface to the second edition of ",
-        elt("em", "The Open Society and Its Enemies"),
-        ", 1950"));
-</script>
- -

Attributes

- -

Some element attributes, such as href for links, can be accessed through a property of the same name on the element’s DOM object. This is the case for most commonly used standard attributes.

- -

But HTML allows you to set any attribute you want on nodes. This can be useful because it allows you to store extra information in a document. If you make up your own attribute names, though, such attributes will not be present as properties on the element’s node. Instead, you have to use the getAttribute and setAttribute methods to work with them.

- -
<p data-classified="secret">The launch code is 00000000.</p>
-<p data-classified="unclassified">I have two feet.</p>
-
-<script>
-  let paras = document.body.getElementsByTagName("p");
-  for (let para of Array.from(paras)) {
-    if (para.getAttribute("data-classified") == "secret") {
-      para.remove();
-    }
-  }
-</script>
- -

It is recommended to prefix the names of such made-up attributes with data- to ensure they do not conflict with any other attributes.

- -

There is a commonly used attribute, class, which is a keyword in the JavaScript language. For historical reasons—some old JavaScript implementations could not handle property names that matched keywords—the property used to access this attribute is called className. You can also access it under its real name, "class", by using the getAttribute and setAttribute methods.

- -

Layout

- -

You may have noticed that different types of elements are laid out differently. Some, such as paragraphs (<p>) or headings (<h1>), take up the whole width of the document and are rendered on separate lines. These are called block elements. Others, such as links (<a>) or the <strong> element, are rendered on the same line with their surrounding text. Such elements are called inline elements.

- -

For any given document, browsers are able to compute a layout, which gives each element a size and position based on its type and content. This layout is then used to actually draw the document.

- -

The size and position of an element can be accessed from JavaScript. The offsetWidth and offsetHeight properties give you the space the element takes up in pixels. A pixel is the basic unit of measurement in the browser. It traditionally corresponds to the smallest dot that the screen can draw, but on modern displays, which can draw very small dots, that may no longer be the case, and a browser pixel may span multiple display dots.

- -

Similarly, clientWidth and clientHeight give you the size of the space inside the element, ignoring border width.

- -
<p style="border: 3px solid red">
-  I'm boxed in
-</p>
-
-<script>
-  let para = document.body.getElementsByTagName("p")[0];
-  console.log("clientHeight:", para.clientHeight);
-  console.log("offsetHeight:", para.offsetHeight);
-</script>
- -

The most effective way to find the precise position of an element on the screen is the getBoundingClientRect method. It returns an object with top, bottom, left, and right properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. If you want them relative to the whole document, you must add the current scroll position, which you can find in the pageXOffset and pageYOffset bindings.

- -

Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time you change it but wait as long as they can. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout to draw the changed document to the screen. When a program asks for the position or size of something by reading properties such as offsetHeight or calling getBoundingClientRect, providing correct information also requires computing a layout.

- -

A program that repeatedly alternates between reading DOM layout information and changing the DOM forces a lot of layout computations to happen and will consequently run very slowly. The following code is an example of this. It contains two different programs that build up a line of X characters 2,000 pixels wide and measures the time each one takes.

- -
<p><span id="one"></span></p>
-<p><span id="two"></span></p>
-
-<script>
-  function time(name, action) {
-    let start = Date.now(); // Current time in milliseconds
-    action();
-    console.log(name, "took", Date.now() - start, "ms");
-  }
-
-  time("naive", () => {
-    let target = document.getElementById("one");
-    while (target.offsetWidth < 2000) {
-      target.appendChild(document.createTextNode("X"));
-    }
-  });
-  // → naive took 32 ms
-
-  time("clever", function() {
-    let target = document.getElementById("two");
-    target.appendChild(document.createTextNode("XXXXX"));
-    let total = Math.ceil(2000 / (target.offsetWidth / 5));
-    target.firstChild.nodeValue = "X".repeat(total);
-  });
-  // → clever took 1 ms
-</script>
- -

Styling

- -

We have seen that different HTML elements are drawn differently. Some are displayed as blocks, others inline. Some add styling—<strong> makes its content bold, and <a> makes it blue and underlines it.

- -

The way an <img> tag shows an image or an <a> tag causes a link to be followed when it is clicked is strongly tied to the element type. But we can change the styling associated with an element, such as the text color or underline. Here is an example that uses the style property:

- -
<p><a href=".">Normal link</a></p>
-<p><a href="." style="color: green">Green link</a></p>
- -

A style attribute may contain one or more declarations, which are a property (such as color) followed by a colon and a value (such as green). When there is more than one declaration, they must be separated by semicolons, as in "color: red; border: none".

- -

A lot of aspects of the document can be influenced by styling. For example, the display property controls whether an element is displayed as a block or an inline element.

- -
This text is displayed <strong>inline</strong>,
-<strong style="display: block">as a block</strong>, and
-<strong style="display: none">not at all</strong>.
- -

The block tag will end up on its own line since block elements are not displayed inline with the text around them. The last tag is not displayed at all—display: none prevents an element from showing up on the screen. This is a way to hide elements. It is often preferable to removing them from the document entirely because it makes it easy to reveal them again later.

- -

JavaScript code can directly manipulate the style of an element through the element’s style property. This property holds an object that has properties for all possible style properties. The values of these properties are strings, which we can write to in order to change a particular aspect of the element’s style.

- -
<p id="para" style="color: purple">
-  Nice text
-</p>
-
-<script>
-  let para = document.getElementById("para");
-  console.log(para.style.color);
-  para.style.color = "magenta";
-</script>
- -

Some style property names contain hyphens, such as font-family. Because such property names are awkward to work with in JavaScript (you’d have to say style["font-family"]), the property names in the style object for such properties have their hyphens removed and the letters after them capitalized (style.fontFamily).

- -

Cascading styles

- -

The styling system for HTML is called CSS, for Cascading Style Sheets. A style sheet is a set of rules for how to style elements in a document. It can be given inside a <style> tag.

- -
<style>
-  strong {
-    font-style: italic;
-    color: gray;
-  }
-</style>
-<p>Now <strong>strong text</strong> is italic and gray.</p>
- -

The cascading in the name refers to the fact that multiple such rules are combined to produce the final style for an element. In the example, the default styling for <strong> tags, which gives them font-weight: bold, is overlaid by the rule in the <style> tag, which adds font-style and color.

- -

When multiple rules define a value for the same property, the most recently read rule gets a higher precedence and wins. So if the rule in the <style> tag included font-weight: normal, contradicting the default font-weight rule, the text would be normal, not bold. Styles in a style attribute applied directly to the node have the highest precedence and always win.

- -

It is possible to target things other than tag names in CSS rules. A rule for .abc applies to all elements with "abc" in their class attribute. A rule for #xyz applies to the element with an id attribute of "xyz" (which should be unique within the document).

- -
.subtle {
-  color: gray;
-  font-size: 80%;
-}
-#header {
-  background: blue;
-  color: white;
-}
-/* p elements with id main and with classes a and b */
-p#main.a.b {
-  margin-bottom: 20px;
-}
- -

The precedence rule favoring the most recently defined rule applies only when the rules have the same specificity. A rule’s specificity is a measure of how precisely it describes matching elements, determined by the number and kind (tag, class, or ID) of element aspects it requires. For example, a rule that targets p.a is more specific than rules that target p or just .a and would thus take precedence over them.

- -

The notation p > a {…} applies the given styles to all <a> tags that are direct children of <p> tags. Similarly, p a {…} applies to all <a> tags inside <p> tags, whether they are direct or indirect children.

- -

Query selectors

- -

We won’t be using style sheets all that much in this book. Understanding them is helpful when programming in the browser, but they are complicated enough to warrant a separate book.

- -

The main reason I introduced selector syntax—the notation used in style sheets to determine which elements a set of styles apply to—is that we can use this same mini-language as an effective way to find DOM elements.

- -

The querySelectorAll method, which is defined both on the document object and on element nodes, takes a selector string and returns a NodeList containing all the elements that it matches.

- -
<p>And if you go chasing
-  <span class="animal">rabbits</span></p>
-<p>And you know you're going to fall</p>
-<p>Tell 'em a <span class="character">hookah smoking
-  <span class="animal">caterpillar</span></span></p>
-<p>Has given you the call</p>
-
-<script>
-  function count(selector) {
-    return document.querySelectorAll(selector).length;
-  }
-  console.log(count("p"));           // All <p> elements
-  // → 4
-  console.log(count(".animal"));     // Class animal
-  // → 2
-  console.log(count("p .animal"));   // Animal inside of <p>
-  // → 2
-  console.log(count("p > .animal")); // Direct child of <p>
-  // → 1
-</script>
- -

Unlike methods such as getElementsByTagName, the object returned by querySelectorAll is not live. It won’t change when you change the document. It is still not a real array, though, so you still need to call Array.from if you want to treat it like one.

- -

The querySelector method (without the All part) works in a similar way. This one is useful if you want a specific, single element. It will return only the first matching element or null when no element matches.

- -

Positioning and animating

- -

The position style property influences layout in a powerful way. By default it has a value of static, meaning the element sits in its normal place in the document. When it is set to relative, the element still takes up space in the document, but now the top and left style properties can be used to move it relative to that normal place. When position is set to absolute, the element is removed from the normal document flow—that is, it no longer takes up space and may overlap with other elements. Also, its top and left properties can be used to absolutely position it relative to the top-left corner of the nearest enclosing element whose position property isn’t static, or relative to the document if no such enclosing element exists.

- -

We can use this to create an animation. The following document displays a picture of a cat that moves around in an ellipse:

- -
<p style="text-align: center">
-  <img src="img/cat.png" style="position: relative">
-</p>
-<script>
-  let cat = document.querySelector("img");
-  let angle = Math.PI / 2;
-  function animate(time, lastTime) {
-    if (lastTime != null) {
-      angle += (time - lastTime) * 0.001;
-    }
-    cat.style.top = (Math.sin(angle) * 20) + "px";
-    cat.style.left = (Math.cos(angle) * 200) + "px";
-    requestAnimationFrame(newTime => animate(newTime, time));
-  }
-  requestAnimationFrame(animate);
-</script>
- -

Our picture is centered on the page and given a position of relative. We’ll repeatedly update that picture’s top and left styles to move it.

- -

The script uses requestAnimationFrame to schedule the animate function to run whenever the browser is ready to repaint the screen. The animate function itself again calls requestAnimationFrame to schedule the next update. When the browser window (or tab) is active, this will cause updates to happen at a rate of about 60 per second, which tends to produce a good-looking animation.

- -

If we just updated the DOM in a loop, the page would freeze, and nothing would show up on the screen. Browsers do not update their display while a JavaScript program is running, nor do they allow any interaction with the page. This is why we need requestAnimationFrame—it lets the browser know that we are done for now, and it can go ahead and do the things that browsers do, such as updating the screen and responding to user actions.

- -

The animation function is passed the current time as an argument. To ensure that the motion of the cat per millisecond is stable, it bases the speed at which the angle changes on the difference between the current time and the last time the function ran. If it just moved the angle by a fixed amount per step, the motion would stutter if, for example, another heavy task running on the same computer were to prevent the function from running for a fraction of a second.

- -

Moving in circles is done using the trigonometry functions Math.cos and Math.sin. For those who aren’t familiar with these, I’ll briefly introduce them since we will occasionally use them in this book.

- -

Math.cos and Math.sin are useful for finding points that lie on a circle around point (0,0) with a radius of one. Both functions interpret their argument as the position on this circle, with zero denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. Math.cos tells you the x-coordinate of the point that corresponds to the given position, and Math.sin yields the y-coordinate. Positions (or angles) greater than 2π or less than 0 are valid—the rotation repeats so that a+2π refers to the same angle as a.

- -

This unit for measuring angles is called radians—a full circle is 2π radians, similar to how it is 360 degrees when measuring in degrees. The constant π is available as Math.PI in JavaScript.

Using cosine and sine to compute coordinates
- -

The cat animation code keeps a counter, angle, for the current angle of the animation and increments it every time the animate function is called. It can then use this angle to compute the current position of the image element. The top style is computed with Math.sin and multiplied by 20, which is the vertical radius of our ellipse. The left style is based on Math.cos and multiplied by 200 so that the ellipse is much wider than it is high.

- -

Note that styles usually need units. In this case, we have to append "px" to the number to tell the browser that we are counting in pixels (as opposed to centimeters, “ems”, or other units). This is easy to forget. Using numbers without units will result in your style being ignored—unless the number is 0, which always means the same thing, regardless of its unit.

- -

Summary

- -

JavaScript programs may inspect and interfere with the document that the browser is displaying through a data structure called the DOM. This data structure represents the browser’s model of the document, and a JavaScript program can modify it to change the visible document.

- -

The DOM is organized like a tree, in which elements are arranged hierarchically according to the structure of the document. The objects representing elements have properties such as parentNode and childNodes, which can be used to navigate through this tree.

- -

The way a document is displayed can be influenced by styling, both by attaching styles to nodes directly and by defining rules that match certain nodes. There are many different style properties, such as color or display. JavaScript code can manipulate an element’s style directly through its style property.

- -

Exercises

- -

Build a table

- -

An HTML table is built with the following tag structure:

- -
<table>
-  <tr>
-    <th>name</th>
-    <th>height</th>
-    <th>place</th>
-  </tr>
-  <tr>
-    <td>Kilimanjaro</td>
-    <td>5895</td>
-    <td>Tanzania</td>
-  </tr>
-</table>
- -

For each row, the <table> tag contains a <tr> tag. Inside of these <tr> tags, we can put cell elements: either heading cells (<th>) or regular cells (<td>).

- -

Given a data set of mountains, an array of objects with name, height, and place properties, generate the DOM structure for a table that enumerates the objects. It should have one column per key and one row per object, plus a header row with <th> elements at the top, listing the column names.

- -

Write this so that the columns are automatically derived from the objects, by taking the property names of the first object in the data.

- -

Add the resulting table to the element with an id attribute of "mountains" so that it becomes visible in the document.

- -

Once you have this working, right-align cells that contain number values by setting their style.textAlign property to "right".

- -
<h1>Mountains</h1>
-
-<div id="mountains"></div>
-
-<script>
-  const MOUNTAINS = [
-    {name: "Kilimanjaro", height: 5895, place: "Tanzania"},
-    {name: "Everest", height: 8848, place: "Nepal"},
-    {name: "Mount Fuji", height: 3776, place: "Japan"},
-    {name: "Vaalserberg", height: 323, place: "Netherlands"},
-    {name: "Denali", height: 6168, place: "United States"},
-    {name: "Popocatepetl", height: 5465, place: "Mexico"},
-    {name: "Mont Blanc", height: 4808, place: "Italy/France"}
-  ];
-
-  // Your code here
-</script>
- -
- -

You can use document.createElement to create new element nodes, document.createTextNode to create text nodes, and the appendChild method to put nodes into other nodes.

- -

You’ll want to loop over the key names once to fill in the top row and then again for each object in the array to construct the data rows. To get an array of key names from the first object, Object.keys will be useful.

- -

To add the table to the correct parent node, you can use document.getElementById or document.querySelector to find the node with the proper id attribute.

- -
- -

Elements by tag name

- -

The document.getElementsByTagName method returns all child elements with a given tag name. Implement your own version of this as a function that takes a node and a string (the tag name) as arguments and returns an array containing all descendant element nodes with the given tag name.

- -

To find the tag name of an element, use its nodeName property. But note that this will return the tag name in all uppercase. Use the toLowerCase or toUpperCase string methods to compensate for this.

- -
<h1>Heading with a <span>span</span> element.</h1>
-<p>A paragraph with <span>one</span>, <span>two</span>
-  spans.</p>
-
-<script>
-  function byTagName(node, tagName) {
-    // Your code here.
-  }
-
-  console.log(byTagName(document.body, "h1").length);
-  // → 1
-  console.log(byTagName(document.body, "span").length);
-  // → 3
-  let para = document.querySelector("p");
-  console.log(byTagName(para, "span").length);
-  // → 2
-</script>
- -
- -

The solution is most easily expressed with a recursive function, similar to the talksAbout function defined earlier in this chapter.

- -

You could call byTagname itself recursively, concatenating the resulting arrays to produce the output. Or you could create an inner function that calls itself recursively and that has access to an array binding defined in the outer function, to which it can add the matching elements it finds. Don’t forget to call the inner -function once from the outer function to start the process.

- -

The recursive function must check the node type. Here we are interested only in node type 1 (Node.ELEMENT_NODE). For such nodes, we must loop over their children and, for each child, see whether the child matches the query while also doing a recursive call on it to inspect its own children.

- -
- -

The cat’s hat

- -

Extend the cat animation defined earlier so that both the cat and his hat (<img src="img/hat.png">) orbit at opposite sides of the ellipse.

- -

Or make the hat circle around the cat. Or alter the animation in some other interesting way.

- -

To make positioning multiple objects easier, it is probably a good idea to switch to absolute positioning. This means that top and left are counted relative to the top left of the document. To avoid using negative coordinates, which would cause the image to move outside of the visible page, you can add a fixed number of pixels to the position values.

- -
<style>body { min-height: 200px }</style>
-<img src="img/cat.png" id="cat" style="position: absolute">
-<img src="img/hat.png" id="hat" style="position: absolute">
-
-<script>
-  let cat = document.querySelector("#cat");
-  let hat = document.querySelector("#hat");
-
-  let angle = 0;
-  let lastTime = null;
-  function animate(time) {
-    if (lastTime != null) angle += (time - lastTime) * 0.001;
-    lastTime = time;
-    cat.style.top = (Math.sin(angle) * 40 + 40) + "px";
-    cat.style.left = (Math.cos(angle) * 200 + 230) + "px";
-
-    // Your extensions here.
-
-    requestAnimationFrame(animate);
-  }
-  requestAnimationFrame(animate);
-</script>
- -
- -

Math.cos and Math.sin measure angles in radians, where a full circle is 2π. For a given angle, you can get the opposite angle by adding half of this, which is Math.PI. This can be useful for putting the hat on the opposite side of the orbit.

- -
-
diff --git a/docs/15_event.html b/docs/15_event.html deleted file mode 100644 index 9c829e6cc..000000000 --- a/docs/15_event.html +++ /dev/null @@ -1,582 +0,0 @@ - - - - - Handling Events :: Eloquent JavaScript - - - - - - -
- - -

Chapter 15Handling Events

- -
- -

You have power over your mind—not outside events. Realize this, and you will find strength.

- -
Marcus Aurelius, Meditations
- -
Picture a Rube Goldberg machine
- -

Some programs work with direct user input, such as mouse and keyboard actions. That kind of input isn’t available as a well-organized data structure—it comes in piece by piece, in real time, and the program is expected to respond to it as it happens.

- -

Event handlers

- -

Imagine an interface where the only way to find out whether a key on the keyboard is being pressed is to read the current state of that key. To be able to react to keypresses, you would have to constantly read the key’s state so that you’d catch it before it’s released again. It would be dangerous to perform other time-intensive computations since you might miss a keypress.

- -

Some primitive machines do handle input like that. A step up from this would be for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there.

- -

Of course, it has to remember to look at the queue, and to do it often, because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called polling. Most programmers prefer to avoid it.

- -

A better mechanism is for the system to actively notify our code when an event occurs. Browsers do this by allowing us to register functions as handlers for specific events.

- -
<p>Click this document to activate the handler.</p>
-<script>
-  window.addEventListener("click", () => {
-    console.log("You knocked?");
-  });
-</script>
- -

The window binding refers to a built-in object provided by the browser. It represents the browser window that contains the document. Calling its addEventListener method registers the second argument to be called whenever the event described by its first argument occurs.

- -

Events and DOM nodes

- -

Each browser event handler is registered in a context. In the previous example we called addEventListener on the window object to register a handler for the whole window. Such a method can also be found on DOM elements and some other types of objects. Event listeners are called only when the event happens in the context of the object they are registered on.

- -
<button>Click me</button>
-<p>No handler here.</p>
-<script>
-  let button = document.querySelector("button");
-  button.addEventListener("click", () => {
-    console.log("Button clicked.");
-  });
-</script>
- -

That example attaches a handler to the button node. Clicks on the button cause that handler to run, but clicks on the rest of the document do not.

- -

Giving a node an onclick attribute has a similar effect. This works for most types of events—you can attach a handler through the attribute whose name is the event name with on in front of it.

- -

But a node can have only one onclick attribute, so you can register only one handler per node that way. The addEventListener method allows you to add any number of handlers so that it is safe to add handlers even if there is already another handler on the element.

- -

The removeEventListener method, called with arguments similar to addEventListener, removes a handler.

- -
<button>Act-once button</button>
-<script>
-  let button = document.querySelector("button");
-  function once() {
-    console.log("Done.");
-    button.removeEventListener("click", once);
-  }
-  button.addEventListener("click", once);
-</script>
- -

The function given to removeEventListener has to be the same function value that was given to addEventListener. So, to unregister a handler, you’ll want to give the function a name (once, in the example) to be able to pass the same function value to both methods.

- -

Event objects

- -

Though we have ignored it so far, event handler functions are passed an argument: the event object. This object holds additional information about the event. For example, if we want to know which mouse button was pressed, we can look at the event object’s button property.

- -
<button>Click me any way you want</button>
-<script>
-  let button = document.querySelector("button");
-  button.addEventListener("mousedown", event => {
-    if (event.button == 0) {
-      console.log("Left button");
-    } else if (event.button == 1) {
-      console.log("Middle button");
-    } else if (event.button == 2) {
-      console.log("Right button");
-    }
-  });
-</script>
- -

The information stored in an event object differs per type of event. We’ll discuss different types later in the chapter. The object’s type property always holds a string identifying the event (such as "click" or "mousedown").

- -

Propagation

- -

For most event types, handlers registered on nodes with children will also receive events that happen in the children. If a button inside a paragraph is clicked, event handlers on the paragraph will also see the click event.

- -

But if both the paragraph and the button have a handler, the more specific handler—the one on the button—gets to go first. The event is said to propagate outward, from the node where it happened to that node’s parent node and on to the root of the document. Finally, after all handlers registered on a specific node have had their turn, handlers registered on the whole window get a chance to respond to the event.

- -

At any point, an event handler can call the stopPropagation method on the event object to prevent handlers further up from receiving the event. This can be useful when, for example, you have a button inside another clickable element and you don’t want clicks on the button to activate the outer element’s click behavior.

- -

The following example registers "mousedown" handlers on both a button and the paragraph around it. When clicked with the right mouse button, the handler for the button calls stopPropagation, which will prevent the handler on the paragraph from running. When the button is clicked with another mouse button, both handlers will run.

- -
<p>A paragraph with a <button>button</button>.</p>
-<script>
-  let para = document.querySelector("p");
-  let button = document.querySelector("button");
-  para.addEventListener("mousedown", () => {
-    console.log("Handler for paragraph.");
-  });
-  button.addEventListener("mousedown", event => {
-    console.log("Handler for button.");
-    if (event.button == 2) event.stopPropagation();
-  });
-</script>
- -

Most event objects have a target property that refers to the node where they originated. You can use this property to ensure that you’re not accidentally handling something that propagated up from a node you do not want to handle.

- -

It is also possible to use the target property to cast a wide net for a specific type of event. For example, if you have a node containing a long list of buttons, it may be more convenient to register a single click handler on the outer node and have it use the target property to figure out whether a button was clicked, rather than register individual handlers on all of the buttons.

- -
<button>A</button>
-<button>B</button>
-<button>C</button>
-<script>
-  document.body.addEventListener("click", event => {
-    if (event.target.nodeName == "BUTTON") {
-      console.log("Clicked", event.target.textContent);
-    }
-  });
-</script>
- -

Default actions

- -

Many events have a default action associated with them. If you click a link, you will be taken to the link’s target. If you press the down arrow, the browser will scroll the page down. If you right-click, you’ll get a context menu. And so on.

- -

For most types of events, the JavaScript event handlers are called before the default behavior takes place. If the handler doesn’t want this normal behavior to happen, typically because it has already taken care of handling the event, it can call the preventDefault method on the event object.

- -

This can be used to implement your own keyboard shortcuts or context menu. It can also be used to obnoxiously interfere with the behavior that users expect. For example, here is a link that cannot be followed:

- -
<a href="https://developer.mozilla.org/">MDN</a>
-<script>
-  let link = document.querySelector("a");
-  link.addEventListener("click", event => {
-    console.log("Nope.");
-    event.preventDefault();
-  });
-</script>
- -

Try not to do such things unless you have a really good reason to. It’ll be unpleasant for people who use your page when expected behavior is broken.

- -

Depending on the browser, some events can’t be intercepted at all. On Chrome, for example, the keyboard shortcut to close the current tab (control-W or command-W) cannot be handled by JavaScript.

- -

Key events

- -

When a key on the keyboard is pressed, your browser fires a "keydown" event. When it is released, you get a "keyup" event.

- -
<p>This page turns violet when you hold the V key.</p>
-<script>
-  window.addEventListener("keydown", event => {
-    if (event.key == "v") {
-      document.body.style.background = "violet";
-    }
-  });
-  window.addEventListener("keyup", event => {
-    if (event.key == "v") {
-      document.body.style.background = "";
-    }
-  });
-</script>
- -

Despite its name, "keydown" fires not only when the key is physically pushed down. When a key is pressed and held, the event fires again every time the key repeats. Sometimes you have to be careful about this. For example, if you add a button to the DOM when a key is pressed and remove it again when the key is released, you might accidentally add hundreds of buttons when the key is held down longer.

- -

The example looked at the key property of the event object to see which key the event is about. This property holds a string that, for most keys, corresponds to the thing that pressing that key would type. For special keys such as enter, it holds a string that names the key ("Enter", in this case). If you hold shift while pressing a key, that might also influence the name of the key—"v" becomes "V", and "1" may become "!", if that is what pressing shift-1 produces on your keyboard.

- -

Modifier keys such as shift, control, alt, and meta (command on Mac) generate key events just like normal keys. But when looking for key combinations, you can also find out whether these keys are held down by looking at the shiftKey, ctrlKey, altKey, and metaKey properties of keyboard and mouse events.

- -
<p>Press Control-Space to continue.</p>
-<script>
-  window.addEventListener("keydown", event => {
-    if (event.key == " " && event.ctrlKey) {
-      console.log("Continuing!");
-    }
-  });
-</script>
- -

The DOM node where a key event originates depends on the element that has focus when the key is pressed. Most nodes cannot have focus unless you give them a tabindex attribute, but things like links, buttons, and form fields can. We’ll come back to form fields in Chapter 18. When nothing in particular has focus, document.body acts as the target node of key events.

- -

When the user is typing text, using key events to figure out what is being typed is problematic. Some platforms, most notably the virtual -keyboard on Android phones, don’t fire key events. But even when you have an old-fashioned keyboard, some types of text input don’t match key presses in a straightforward way, such as input method editor (IME) software used by people whose scripts don’t fit on a keyboard, where multiple key strokes are combined to create characters.

- -

To notice when something was typed, elements that you can type into, such as the <input> and <textarea> tags, fire "input" events whenever the user changes their content. To get the actual content that was typed, it is best to directly read it from the focused field. Chapter 18 will show how.

- -

Pointer events

- -

There are currently two widely used ways to point at things on a screen: mice (including devices that act like mice, such as touchpads and trackballs) and touchscreens. These produce different kinds of events.

- -

Mouse clicks

- -

Pressing a mouse button causes a number of events to fire. The "mousedown" and "mouseup" events are similar to "keydown" and "keyup" and fire when the button is pressed and released. These happen on the DOM nodes that are immediately below the mouse pointer when the event occurs.

- -

After the "mouseup" event, a "click" event fires on the most specific node that contained both the press and the release of the button. For example, if I press down the mouse button on one paragraph and then move the pointer to another paragraph and release the button, the "click" event will happen on the element that contains both those paragraphs.

- -

If two clicks happen close together, a "dblclick" (double-click) event also fires, after the second click event.

- -

To get precise information about the place where a mouse event happened, you can look at its clientX and clientY properties, which contain the event’s coordinates (in pixels) relative to the top-left corner of the window, or pageX and pageY, which are relative to the top-left corner of the whole document (which may be different when the window has been scrolled).

- -

The following implements a primitive drawing program. Every time you click the document, it adds a dot under your mouse pointer. See Chapter 19 for a less primitive drawing program.

- -
<style>
-  body {
-    height: 200px;
-    background: beige;
-  }
-  .dot {
-    height: 8px; width: 8px;
-    border-radius: 4px; /* rounds corners */
-    background: blue;
-    position: absolute;
-  }
-</style>
-<script>
-  window.addEventListener("click", event => {
-    let dot = document.createElement("div");
-    dot.className = "dot";
-    dot.style.left = (event.pageX - 4) + "px";
-    dot.style.top = (event.pageY - 4) + "px";
-    document.body.appendChild(dot);
-  });
-</script>
- -

Mouse motion

- -

Every time the mouse pointer moves, a "mousemove" event is fired. This event can be used to track the position of the mouse. A common situation in which this is useful is when implementing some form of mouse-dragging functionality.

- -

As an example, the following program displays a bar and sets up event handlers so that dragging to the left or right on this bar makes it narrower or wider:

- -
<p>Drag the bar to change its width:</p>
-<div style="background: orange; width: 60px; height: 20px">
-</div>
-<script>
-  let lastX; // Tracks the last observed mouse X position
-  let bar = document.querySelector("div");
-  bar.addEventListener("mousedown", event => {
-    if (event.button == 0) {
-      lastX = event.clientX;
-      window.addEventListener("mousemove", moved);
-      event.preventDefault(); // Prevent selection
-    }
-  });
-
-  function moved(event) {
-    if (event.buttons == 0) {
-      window.removeEventListener("mousemove", moved);
-    } else {
-      let dist = event.clientX - lastX;
-      let newWidth = Math.max(10, bar.offsetWidth + dist);
-      bar.style.width = newWidth + "px";
-      lastX = event.clientX;
-    }
-  }
-</script>
- -

Note that the "mousemove" handler is registered on the whole window. Even if the mouse goes outside of the bar during resizing, as long as the button is held we still want to update its size.

- -

We must stop resizing the bar when the mouse button is released. For that, we can use the buttons property (note the plural), which tells us about the buttons that are currently held down. When this is zero, no buttons are down. When buttons are held, its value is the sum of the codes for those buttons—the left button has code 1, the right button 2, and the middle one 4. That way, you can check whether a given button is pressed by taking the remainder of the value of buttons and its code.

- -

Note that the order of these codes is different from the one used by button, where the middle button came before the right one. As mentioned, consistency isn’t really a strong point of the browser’s programming interface.

- -

Touch events

- -

The style of graphical browser that we use was designed with mouse interfaces in mind, at a time where touchscreens were rare. To make the Web “work” on early touchscreen phones, browsers for those devices pretended, to a certain extent, that touch events were mouse events. If you tap your screen, you’ll get "mousedown", "mouseup", and "click" events.

- -

But this illusion isn’t very robust. A touchscreen works differently from a mouse: it doesn’t have multiple buttons, you can’t track the finger when it isn’t on the screen (to simulate "mousemove"), and it allows multiple fingers to be on the screen at the same time.

- -

Mouse events cover touch interaction only in straightforward cases—if you add a "click" handler to a button, touch users will still be able to use it. But something like the resizeable bar in the previous example does not work on a touchscreen.

- -

There are specific event types fired by touch interaction. When a finger starts touching the screen, you get a "touchstart" event. When it is moved while touching, "touchmove" events fire. Finally, when it stops touching the screen, you’ll see a "touchend" event.

- -

Because many touchscreens can detect multiple fingers at the same time, these events don’t have a single set of coordinates associated with them. Rather, their event objects have a touches property, which holds an array-like object of points, each of which has its own clientX, clientY, pageX, and pageY properties.

- -

You could do something like this to show red circles around every touching finger:

- -
<style>
-  dot { position: absolute; display: block;
-        border: 2px solid red; border-radius: 50px;
-        height: 100px; width: 100px; }
-</style>
-<p>Touch this page</p>
-<script>
-  function update(event) {
-    for (let dot; dot = document.querySelector("dot");) {
-      dot.remove();
-    }
-    for (let i = 0; i < event.touches.length; i++) {
-      let {pageX, pageY} = event.touches[i];
-      let dot = document.createElement("dot");
-      dot.style.left = (pageX - 50) + "px";
-      dot.style.top = (pageY - 50) + "px";
-      document.body.appendChild(dot);
-    }
-  }
-  window.addEventListener("touchstart", update);
-  window.addEventListener("touchmove", update);
-  window.addEventListener("touchend", update);
-</script>
- -

You’ll often want to call preventDefault in touch event handlers to override the browser’s default behavior (which may include scrolling the page on swiping) and to prevent the mouse events from being fired, for which you may also have a handler.

- -

Scroll events

- -

Whenever an element is scrolled, a "scroll" event is fired on it. This has various uses, such as knowing what the user is currently looking at (for disabling off-screen animations or sending spy reports to your evil headquarters) or showing some indication of progress (by highlighting part of a table of contents or showing a page number).

- -

The following example draws a progress bar above the document and updates it to fill up as you scroll down:

- -
<style>
-  #progress {
-    border-bottom: 2px solid blue;
-    width: 0;
-    position: fixed;
-    top: 0; left: 0;
-  }
-</style>
-<div id="progress"></div>
-<script>
-  // Create some content
-  document.body.appendChild(document.createTextNode(
-    "supercalifragilisticexpialidocious ".repeat(1000)));
-
-  let bar = document.querySelector("#progress");
-  window.addEventListener("scroll", () => {
-    let max = document.body.scrollHeight - innerHeight;
-    bar.style.width = `${(pageYOffset / max) * 100}%`;
-  });
-</script>
- -

Giving an element a position of fixed acts much like an absolute position but also prevents it from scrolling along with the rest of the document. The effect is to make our progress bar stay at the top. Its width is changed to indicate the current progress. We use %, rather than px, as a unit when setting the width so that the element is sized relative to the page width.

- -

The global innerHeight binding gives us the height of the window, which we have to subtract from the total scrollable height—you can’t keep scrolling when you hit the bottom of the document. There’s also an innerWidth for the window width. By dividing pageYOffset, the current scroll position, by the maximum scroll position and multiplying by 100, we get the percentage for the progress bar.

- -

Calling preventDefault on a scroll event does not prevent the scrolling from happening. In fact, the event handler is called only after the scrolling takes place.

- -

Focus events

- -

When an element gains focus, the browser fires a "focus" event on it. When it loses focus, the element gets a "blur" event.

- -

Unlike the events discussed earlier, these two events do not propagate. A handler on a parent element is not notified when a child element gains or loses focus.

- -

The following example displays help text for the text field that currently has focus:

- -
<p>Name: <input type="text" data-help="Your full name"></p>
-<p>Age: <input type="text" data-help="Your age in years"></p>
-<p id="help"></p>
-
-<script>
-  let help = document.querySelector("#help");
-  let fields = document.querySelectorAll("input");
-  for (let field of Array.from(fields)) {
-    field.addEventListener("focus", event => {
-      let text = event.target.getAttribute("data-help");
-      help.textContent = text;
-    });
-    field.addEventListener("blur", event => {
-      help.textContent = "";
-    });
-  }
-</script>
- -

The window object will receive "focus" and "blur" events when the user moves from or to the browser tab or window in which the document is shown.

- -

Load event

- -

When a page finishes loading, the "load" event fires on the window and the document body objects. This is often used to schedule initialization actions that require the whole document to have been built. Remember that the content of <script> tags is run immediately when the tag is encountered. This may be too soon, for example when the script needs to do something with parts of the document that appear after the <script> tag.

- -

Elements such as images and script tags that load an external file also have a "load" event that indicates the files they reference were loaded. Like the focus-related events, loading events do not propagate.

- -

When a page is closed or navigated away from (for example, by following a link), a "beforeunload" event fires. The main use of this event is to prevent the user from accidentally losing work by closing a document. If you prevent the default behavior on this event and set the returnValue property on the event object to a string, the browser will show the user a dialog asking if they really want to leave the page. That dialog might include your string, but because some malicious sites try to use these dialogs to confuse people into staying on their page to look at dodgy weight loss ads, most browsers no longer display them.

- -

Events and the event loop

- -

In the context of the event loop, as discussed in Chapter 11, browser event handlers behave like other asynchronous notifications. They are scheduled when the event occurs but must wait for other scripts that are running to finish before they get a chance to run.

- -

The fact that events can be processed only when nothing else is running means that, if the event loop is tied up with other work, any interaction with the page (which happens through events) will be delayed until there’s time to process it. So if you schedule too much work, either with long-running event handlers or with lots of short-running ones, the page will become slow and cumbersome to use.

- -

For cases where you really do want to do some time-consuming thing in the background without freezing the page, browsers provide something called web workers. A worker is a JavaScript process that runs alongside the main script, on its own timeline.

- -

Imagine that squaring a number is a heavy, long-running computation that we want to perform in a separate thread. We could write a file called code/squareworker.js that responds to messages by computing a square and sending a message back.

- -
addEventListener("message", event => {
-  postMessage(event.data * event.data);
-});
- -

To avoid the problems of having multiple threads touching the same data, workers do not share their global scope or any other data with the main script’s environment. Instead, you have to communicate with them by sending messages back and forth.

- -

This code spawns a worker running that script, sends it a few messages, and outputs the responses.

- -
let squareWorker = new Worker("code/squareworker.js");
-squareWorker.addEventListener("message", event => {
-  console.log("The worker responded:", event.data);
-});
-squareWorker.postMessage(10);
-squareWorker.postMessage(24);
- -

The postMessage function sends a message, which will cause a "message" event to fire in the receiver. The script that created the worker sends and receives messages through the Worker object, whereas the worker talks to the script that created it by sending and listening directly on its global scope. Only values that can be represented as JSON can be sent as messages—the other side will receive a copy of them, rather than the value itself.

- -

Timers

- -

We saw the setTimeout function in Chapter 11. It schedules another function to be called later, after a given number of milliseconds.

- -

Sometimes you need to cancel a function you have scheduled. This is done by storing the value returned by setTimeout and calling clearTimeout on it.

- -
let bombTimer = setTimeout(() => {
-  console.log("BOOM!");
-}, 500);
-
-if (Math.random() < 0.5) { // 50% chance
-  console.log("Defused.");
-  clearTimeout(bombTimer);
-}
- -

The cancelAnimationFrame function works in the same way as clearTimeout—calling it on a value returned by requestAnimationFrame will cancel that frame (assuming it hasn’t already been called).

- -

A similar set of functions, setInterval and clearInterval, are used to set timers that should repeat every X milliseconds.

- -
let ticks = 0;
-let clock = setInterval(() => {
-  console.log("tick", ticks++);
-  if (ticks == 10) {
-    clearInterval(clock);
-    console.log("stop.");
-  }
-}, 200);
- -

Debouncing

- -

Some types of events have the potential to fire rapidly, many times in a row (the "mousemove" and "scroll" events, for example). When handling such events, you must be careful not to do anything too time-consuming or your handler will take up so much time that interaction with the document starts to feel slow.

- -

If you do need to do something nontrivial in such a handler, you can use setTimeout to make sure you are not doing it too often. This is usually called debouncing the event. There are several slightly different approaches to this.

- -

In the first example, we want to react when the user has typed something, but we don’t want to do it immediately for every input event. When they are typing quickly, we just want to wait until a pause occurs. Instead of immediately performing an action in the event handler, we set a timeout. We also clear the previous timeout (if any) so that when events occur close together (closer than our timeout delay), the timeout from the previous event will be canceled.

- -
<textarea>Type something here...</textarea>
-<script>
-  let textarea = document.querySelector("textarea");
-  let timeout;
-  textarea.addEventListener("input", () => {
-    clearTimeout(timeout);
-    timeout = setTimeout(() => console.log("Typed!"), 500);
-  });
-</script>
- -

Giving an undefined value to clearTimeout or calling it on a timeout that has already fired has no effect. Thus, we don’t have to be careful about when to call it, and we simply do so for every event.

- -

We can use a slightly different pattern if we want to space responses so that they’re separated by at least a certain length of time but want to fire them during a series of events, not just afterward. For example, we might want to respond to "mousemove" events by showing the current coordinates of the mouse but only every 250 milliseconds.

- -
<script>
-  let scheduled = null;
-  window.addEventListener("mousemove", event => {
-    if (!scheduled) {
-      setTimeout(() => {
-        document.body.textContent =
-          `Mouse at ${scheduled.pageX}, ${scheduled.pageY}`;
-        scheduled = null;
-      }, 250);
-    }
-    scheduled = event;
-  });
-</script>
- -

Summary

- -

Event handlers make it possible to detect and react to events happening in our web page. The addEventListener method is used to register such a handler.

- -

Each event has a type ("keydown", "focus", and so on) that identifies it. Most events are called on a specific DOM element and then propagate to that element’s ancestors, allowing handlers associated with those elements to handle them.

- -

When an event handler is called, it is passed an event object with additional information about the event. This object also has methods that allow us to stop further propagation (stopPropagation) and prevent the browser’s default handling of the event (preventDefault).

- -

Pressing a key fires "keydown" and "keyup" events. Pressing a mouse button fires "mousedown", "mouseup", and "click" events. Moving the mouse fires "mousemove" events. Touchscreen interaction will result in "touchstart", "touchmove", and "touchend" events.

- -

Scrolling can be detected with the "scroll" event, and focus changes can be detected with the "focus" and "blur" events. When the document finishes loading, a "load" event fires on the window.

- -

Exercises

- -

Balloon

- -

Write a page that displays a balloon (using the balloon emoji, 🎈). When you press the up arrow, it should inflate (grow) 10 percent, and when you press the down arrow, it should deflate (shrink) 10 percent.

- -

You can control the size of text (emoji are text) by setting the font-size CSS property (style.fontSize) on its parent element. Remember to include a unit in the value—for example, pixels (10px).

- -

The key names of the arrow keys are "ArrowUp" and "ArrowDown". Make sure the keys change only the balloon, without scrolling the page.

- -

When that works, add a feature where, if you blow up the balloon past a certain size, it explodes. In this case, exploding means that it is replaced with an 💥 emoji, and the event handler is removed (so that you can’t inflate or deflate the explosion).

- -
<p>🎈</p>
-
-<script>
-  // Your code here
-</script>
- -
- -

You’ll want to register a handler for the "keydown" event and look at event.key to figure out whether the up or down arrow key was pressed.

- -

The current size can be kept in a binding so that you can base the new size on it. It’ll be helpful to define a function that updates the size—both the binding and the style of the balloon in the DOM—so that you can call it from your event handler, and possibly also once when starting, to set the initial size.

- -

You can change the balloon to an explosion by replacing the text node with another one (using replaceChild) or by setting the textContent property of its parent node to a new string.

- -
- -

Mouse trail

- -

In JavaScript’s early days, which was the high time of gaudy home -pages with lots of animated images, people came up with some truly inspiring ways to use the language.

- -

One of these was the mouse trail—a series of elements that would follow the mouse pointer as you moved it across the page.

- -

In this exercise, I want you to implement a mouse trail. Use absolutely positioned <div> elements with a fixed size and background color (refer to the code in the “Mouse Clicks” section for an example). Create a bunch of such elements and, when the mouse moves, display them in the wake of the mouse pointer.

- -

There are various possible approaches here. You can make your solution as simple or as complex as you want. A simple solution to start with is to keep a fixed number of trail elements and cycle through them, moving the next one to the mouse’s current position every time a "mousemove" event occurs.

- -
<style>
-  .trail { /* className for the trail elements */
-    position: absolute;
-    height: 6px; width: 6px;
-    border-radius: 3px;
-    background: teal;
-  }
-  body {
-    height: 300px;
-  }
-</style>
-
-<script>
-  // Your code here.
-</script>
- -
- -

Creating the elements is best done with a loop. Append them to the document to make them show up. To be able to access them later to change their position, you’ll want to store the elements in an array.

- -

Cycling through them can be done by keeping a counter variable and adding 1 to it every time the "mousemove" event fires. The remainder operator (% elements.length) can then be used to get a valid array index to pick the element you want to position during a given event.

- -

Another interesting effect can be achieved by modeling a simple physics system. Use the "mousemove" event only to update a pair of bindings that track the mouse position. Then use requestAnimationFrame to simulate the trailing elements being attracted to the position of the mouse pointer. At every animation step, update their position based on their position relative to the pointer (and, optionally, a speed that is stored for each element). Figuring out a good way to do this is up to you.

- -
- -

Tabs

- -

Tabbed panels are widely used in user interfaces. They allow you to select an interface panel by choosing from a number of tabs “sticking out” above an element.

- -

In this exercise you must implement a simple tabbed interface. Write a function, asTabs, that takes a DOM node and creates a tabbed interface showing the child elements of that node. It should insert a list of <button> elements at the top of the node, one for each child element, containing text retrieved from the data-tabname attribute of the child. All but one of the original children should be hidden (given a display style of none). The currently visible node can be selected by clicking the buttons.

- -

When that works, extend it to style the button for the currently selected tab differently so that it is obvious which tab is selected.

- -
<tab-panel>
-  <div data-tabname="one">Tab one</div>
-  <div data-tabname="two">Tab two</div>
-  <div data-tabname="three">Tab three</div>
-</tab-panel>
-<script>
-  function asTabs(node) {
-    // Your code here.
-  }
-  asTabs(document.querySelector("tab-panel"));
-</script>
- -
- -

One pitfall you might run into is that you can’t directly use the node’s childNodes property as a collection of tab nodes. For one thing, when you add the buttons, they will also become child nodes and end up in this object because it is a live data structure. For another, the text nodes created for the whitespace between the nodes are also in childNodes but should not get their own tabs. You can use children instead of childNodes to ignore text nodes.

- -

You could start by building up an array of tabs so that you have easy access to them. To implement the styling of the buttons, you could store objects that contain both the tab panel and its button.

- -

I recommend writing a separate function for changing tabs. You can either store the previously selected tab and change only the styles needed to hide that and show the new one, or you can just update the style of all tabs every time a new tab is selected.

- -

You might want to call this function immediately to make the interface start with the first tab visible.

- -
-
diff --git a/docs/16_game.html b/docs/16_game.html deleted file mode 100644 index 697a1a327..000000000 --- a/docs/16_game.html +++ /dev/null @@ -1,795 +0,0 @@ - - - - - Project: A Platform Game :: Eloquent JavaScript - - - - - - -
- - -

Chapter 16Project: A Platform Game

- -
- -

All reality is a game.

- -
Iain Banks, The Player of Games
- -
Picture of a game character jumping over lava
- -

Much of my initial fascination with computers, like that of many nerdy kids, had to do with computer games. I was drawn into the tiny simulated worlds that I could manipulate and in which stories (sort of) unfolded—more, I suppose, because of the way I projected my imagination into them than because of the possibilities they actually offered.

- -

I don’t wish a career in game programming on anyone. Much like the music industry, the discrepancy between the number of eager young people wanting to work in it and the actual demand for such people creates a rather unhealthy environment. But writing games for fun is amusing.

- -

This chapter will walk through the implementation of a small platform game. Platform games (or “jump and run” games) are games that expect the player to move a figure through a world, which is usually two-dimensional and viewed from the side, while jumping over and onto things.

- -

The game

- -

Our game will be roughly based on Dark Blue by Thomas Palef. I chose that game because it is both entertaining and minimalist and because it can be built without too much code. It looks like this:

The game Dark Blue
- -

The dark box represents the player, whose task is to collect the yellow boxes (coins) while avoiding the red stuff (lava). A level is completed when all coins have been collected.

- -

The player can walk around with the left and right arrow keys and can jump with the up arrow. Jumping is a specialty of this game character. It can reach several times its own height and can change direction in midair. This may not be entirely realistic, but it helps give the player the feeling of being in direct control of the on-screen avatar.

- -

The game consists of a static background, laid out like a grid, with the moving elements overlaid on that background. Each field on the grid is either empty, solid, or lava. The moving elements are the player, coins, and certain pieces of lava. The positions of these elements are not constrained to the grid—their coordinates may be fractional, allowing smooth motion.

- -

The technology

- -

We will use the browser DOM to display the game, and we’ll read user input by handling key events.

- -

The screen- and keyboard-related code is only a small part of the work we need to do to build this game. Since everything looks like colored boxes, drawing is uncomplicated: we create DOM elements and use styling to give them a background color, size, and position.

- -

We can represent the background as a table since it is an unchanging grid of squares. The free-moving elements can be overlaid using absolutely positioned elements.

- -

In games and other programs that should animate graphics and respond to user input without noticeable delay, efficiency is important. Although the DOM was not originally designed for high-performance graphics, it is actually better at this than you would expect. You saw some animations in Chapter 14. On a modern machine, a simple game like this performs well, even if we don’t worry about optimization very much.

- -

In the next chapter, we will explore another browser technology, the <canvas> tag, which provides a more traditional way to draw graphics, working in terms of shapes and pixels rather than DOM elements.

- -

Levels

- -

We’ll want a human-readable, human-editable way to specify levels. Since it is okay for everything to start out on a grid, we could use big strings in which each character represents an element—either a part of the background grid or a moving element.

- -

The plan for a small level might look like this:

- -
let simpleLevelPlan = `
-......................
-..#................#..
-..#..............=.#..
-..#.........o.o....#..
-..#.@......#####...#..
-..#####............#..
-......#++++++++++++#..
-......##############..
-......................`;
- -

Periods are empty space, hash (#) characters are walls, and plus signs are lava. The player’s starting position is the at sign (@). Every O character is a coin, and the equal sign (=) at the top is a block of lava that moves back and forth horizontally.

- -

We’ll support two additional kinds of moving lava: the pipe character (|) creates vertically moving blobs, and v indicates dripping lava—vertically moving lava that doesn’t bounce back and forth but only moves down, jumping back to its start position when it hits the floor.

- -

A whole game consists of multiple levels that the player must complete. A level is completed when all coins have been collected. If the player touches lava, the current level is restored to its starting position, and the player may try again.

- -

Reading a level

- -

The following class stores a level object. Its argument should be the string that defines the level.

- -
class Level {
-  constructor(plan) {
-    let rows = plan.trim().split("\n").map(l => [...l]);
-    this.height = rows.length;
-    this.width = rows[0].length;
-    this.startActors = [];
-
-    this.rows = rows.map((row, y) => {
-      return row.map((ch, x) => {
-        let type = levelChars[ch];
-        if (typeof type == "string") return type;
-        this.startActors.push(
-          type.create(new Vec(x, y), ch));
-        return "empty";
-      });
-    });
-  }
-}
- -

The trim method is used to remove whitespace at the start and end of the plan string. This allows our example plan to start with a newline so that all the lines are directly below each other. The remaining string is split on newline characters, and each line is spread into an array, producing arrays of characters.

- -

So rows holds an array of arrays of characters, the rows of the plan. We can derive the level’s width and height from these. But we must still separate the moving elements from the background grid. We’ll call moving elements actors. They’ll be stored in an array of objects. The background will be an array of arrays of strings, holding field types such as "empty", "wall", or "lava".

- -

To create these arrays, we map over the rows and then over their content. Remember that map passes the array index as a second argument to the mapping function, which tells us the x- and y-coordinates of a given character. Positions in the game will be stored as pairs of coordinates, with the top left being 0,0 and each background square being 1 unit high and wide.

- -

To interpret the characters in the plan, the Level constructor uses the levelChars object, which maps background elements to strings and actor characters to classes. When type is an actor class, its static create method is used to create an object, which is added to startActors, and the mapping function returns "empty" for this background square.

- -

The position of the actor is stored as a Vec object. This is a two-dimensional vector, an object with x and y properties, as seen in the exercises of Chapter 6.

- -

As the game runs, actors will end up in different places or even disappear entirely (as coins do when collected). We’ll use a State class to track the state of a running game.

- -
class State {
-  constructor(level, actors, status) {
-    this.level = level;
-    this.actors = actors;
-    this.status = status;
-  }
-
-  static start(level) {
-    return new State(level, level.startActors, "playing");
-  }
-
-  get player() {
-    return this.actors.find(a => a.type == "player");
-  }
-}
- -

The status property will switch to "lost" or "won" when the game has ended.

- -

This is again a persistent data structure—updating the game state creates a new state and leaves the old one intact.

- -

Actors

- -

Actor objects represent the current position and state of a given moving element in our game. All actor objects conform to the same interface. Their pos property holds the coordinates of the element’s top-left corner, and their size property holds its size.

- -

Then they have an update method, which is used to compute their new state and position after a given time step. It simulates the thing the actor does—moving in response to the arrow keys for the player and bouncing back and forth for the lava—and returns a new, updated actor object.

- -

A type property contains a string that identifies the type of the actor—"player", "coin", or "lava". This is useful when drawing the game—the look of the rectangle drawn for an actor is based on its type.

- -

Actor classes have a static create method that is used by the Level constructor to create an actor from a character in the level plan. It is given the coordinates of the character and the character itself, which is needed because the Lava class handles several different characters.

- -

This is the Vec class that we’ll use for our two-dimensional values, such as the position and size of actors.

- -
class Vec {
-  constructor(x, y) {
-    this.x = x; this.y = y;
-  }
-  plus(other) {
-    return new Vec(this.x + other.x, this.y + other.y);
-  }
-  times(factor) {
-    return new Vec(this.x * factor, this.y * factor);
-  }
-}
- -

The times method scales a vector by a given number. It will be useful when we need to multiply a speed vector by a time interval to get the distance traveled during that time.

- -

The different types of actors get their own classes since their behavior is very different. Let’s define these classes. We’ll get to their update methods later.

- -

The player class has a property speed that stores its current speed to simulate momentum and gravity.

- -
class Player {
-  constructor(pos, speed) {
-    this.pos = pos;
-    this.speed = speed;
-  }
-
-  get type() { return "player"; }
-
-  static create(pos) {
-    return new Player(pos.plus(new Vec(0, -0.5)),
-                      new Vec(0, 0));
-  }
-}
-
-Player.prototype.size = new Vec(0.8, 1.5);
- -

Because a player is one-and-a-half squares high, its initial position is set to be half a square above the position where the @ character appeared. This way, its bottom aligns with the bottom of the square it appeared in.

- -

The size property is the same for all instances of Player, so we store it on the prototype rather than on the instances themselves. We could have used a getter like type, but that would create and return a new Vec object every time the property is read, which would be wasteful. (Strings, being immutable, don’t have to be re-created every time they are evaluated.)

- -

When constructing a Lava actor, we need to initialize the object differently depending on the character it is based on. Dynamic lava moves along at its current speed until it hits an obstacle. At that point, if it has a reset property, it will jump back to its start position (dripping). If it does not, it will invert its speed and continue in the other direction (bouncing).

- -

The create method looks at the character that the Level constructor passes and creates the appropriate lava actor.

- -
class Lava {
-  constructor(pos, speed, reset) {
-    this.pos = pos;
-    this.speed = speed;
-    this.reset = reset;
-  }
-
-  get type() { return "lava"; }
-
-  static create(pos, ch) {
-    if (ch == "=") {
-      return new Lava(pos, new Vec(2, 0));
-    } else if (ch == "|") {
-      return new Lava(pos, new Vec(0, 2));
-    } else if (ch == "v") {
-      return new Lava(pos, new Vec(0, 3), pos);
-    }
-  }
-}
-
-Lava.prototype.size = new Vec(1, 1);
- -

Coin actors are relatively simple. They mostly just sit in their place. But to liven up the game a little, they are given a “wobble”, a slight vertical back-and-forth motion. To track this, a coin object stores a base position as well as a wobble property that tracks the phase of the bouncing motion. Together, these determine the coin’s actual position (stored in the pos property).

- -
class Coin {
-  constructor(pos, basePos, wobble) {
-    this.pos = pos;
-    this.basePos = basePos;
-    this.wobble = wobble;
-  }
-
-  get type() { return "coin"; }
-
-  static create(pos) {
-    let basePos = pos.plus(new Vec(0.2, 0.1));
-    return new Coin(basePos, basePos,
-                    Math.random() * Math.PI * 2);
-  }
-}
-
-Coin.prototype.size = new Vec(0.6, 0.6);
- -

In Chapter 14, we saw that Math.sin gives us the y-coordinate of a point on a circle. That coordinate goes back and forth in a smooth waveform as we move along the circle, which makes the sine function useful for modeling a wavy motion.

- -

To avoid a situation where all coins move up and down synchronously, the starting phase of each coin is randomized. The phase of Math.sin’s wave, the width of a wave it produces, is 2π. We multiply the value returned by Math.random by that number to give the coin a random starting position on the wave.

- -

We can now define the levelChars object that maps plan characters to either background grid types or actor classes.

- -
const levelChars = {
-  ".": "empty", "#": "wall", "+": "lava",
-  "@": Player, "o": Coin,
-  "=": Lava, "|": Lava, "v": Lava
-};
- -

That gives us all the parts needed to create a Level instance.

- -
let simpleLevel = new Level(simpleLevelPlan);
-console.log(`${simpleLevel.width} by ${simpleLevel.height}`);
-// → 22 by 9
- -

The task ahead is to display such levels on the screen and to model time and motion inside them.

- -

Encapsulation as a burden

- -

Most of the code in this chapter does not worry about encapsulation very much for two reasons. First, encapsulation takes extra effort. It makes programs bigger and requires additional concepts and interfaces to be introduced. Since there is only so much code you can throw at a reader before their eyes glaze over, I’ve made an effort to keep the program small.

- -

Second, the various elements in this game are so closely tied together that if the behavior of one of them changed, it is unlikely that any of the others would be able to stay the same. Interfaces between the elements would end up encoding a lot of assumptions about the way the game works. This makes them a lot less effective—whenever you change one part of the system, you still have to worry about the way it impacts the other parts because their interfaces wouldn’t cover the new situation.

- -

Some cutting points in a system lend themselves well to separation through rigorous interfaces, but others don’t. Trying to encapsulate something that isn’t a suitable boundary is a sure way to waste a lot of energy. When you are making this mistake, you’ll usually notice that your interfaces are getting awkwardly large and detailed and that they need to be changed often, as the program evolves.

- -

There is one thing that we will encapsulate, and that is the drawing subsystem. The reason for this is that we’ll display the same game in a different way in the next chapter. By putting the drawing behind an interface, we can load the same game program there and plug in a new display module.

- -

Drawing

- -

The encapsulation of the drawing code is done by defining a display object, which displays a given level and state. The display type we define in this chapter is called DOMDisplay because it uses DOM elements to show the level.

- -

We’ll be using a style sheet to set the actual colors and other fixed properties of the elements that make up the game. It would also be possible to directly assign to the elements’ style property when we create them, but that would produce more verbose programs.

- -

The following helper function provides a succinct way to create an element and give it some attributes and child nodes:

- -
function elt(name, attrs, ...children) {
-  let dom = document.createElement(name);
-  for (let attr of Object.keys(attrs)) {
-    dom.setAttribute(attr, attrs[attr]);
-  }
-  for (let child of children) {
-    dom.appendChild(child);
-  }
-  return dom;
-}
- -

A display is created by giving it a parent element to which it should append itself and a level object.

- -
class DOMDisplay {
-  constructor(parent, level) {
-    this.dom = elt("div", {class: "game"}, drawGrid(level));
-    this.actorLayer = null;
-    parent.appendChild(this.dom);
-  }
-
-  clear() { this.dom.remove(); }
-}
- -

The level’s background grid, which never changes, is drawn once. Actors are redrawn every time the display is updated with a given state. The actorLayer property will be used to track the element that holds the actors so that they can be easily removed and replaced.

- -

Our coordinates and sizes are tracked in grid units, where a size or distance of 1 means one grid block. When setting pixel sizes, we will have to scale these coordinates up—everything in the game would be ridiculously small at a single pixel per square. The scale constant gives the number of pixels that a single unit takes up on the screen.

- -
const scale = 20;
-
-function drawGrid(level) {
-  return elt("table", {
-    class: "background",
-    style: `width: ${level.width * scale}px`
-  }, ...level.rows.map(row =>
-    elt("tr", {style: `height: ${scale}px`},
-        ...row.map(type => elt("td", {class: type})))
-  ));
-}
- -

As mentioned, the background is drawn as a <table> element. This nicely corresponds to the structure of the rows property of the level—each row of the grid is turned into a table row (<tr> element). The strings in the grid are used as class names for the table cell (<td>) elements. The spread (triple dot) operator is used to pass arrays of child nodes to elt as separate arguments.

- -

The following CSS makes the table look like the background we want:

- -
.background    { background: rgb(52, 166, 251);
-                 table-layout: fixed;
-                 border-spacing: 0;              }
-.background td { padding: 0;                     }
-.lava          { background: rgb(255, 100, 100); }
-.wall          { background: white;              }
- -

Some of these (table-layout, border-spacing, and padding) are used to suppress unwanted default behavior. We don’t want the layout of the table to depend upon the contents of its cells, and we don’t want space between the table cells or padding inside them.

- -

The background rule sets the background color. CSS allows colors to be specified both as words (white) or with a format such as rgb(R, G, B), where the red, green, and blue components of the color are separated into three numbers from 0 to 255. So, in rgb(52, 166, 251), the red component is 52, green is 166, and blue is 251. Since the blue component is the largest, the resulting color will be bluish. You can see that in the .lava rule, the first number (red) is the largest.

- -

We draw each actor by creating a DOM element for it and setting that element’s position and size based on the actor’s properties. The values have to be multiplied by scale to go from game units to pixels.

- -
function drawActors(actors) {
-  return elt("div", {}, ...actors.map(actor => {
-    let rect = elt("div", {class: `actor ${actor.type}`});
-    rect.style.width = `${actor.size.x * scale}px`;
-    rect.style.height = `${actor.size.y * scale}px`;
-    rect.style.left = `${actor.pos.x * scale}px`;
-    rect.style.top = `${actor.pos.y * scale}px`;
-    return rect;
-  }));
-}
- -

To give an element more than one class, we separate the class names by spaces. In the CSS code shown next, the actor class gives the actors their absolute position. Their type name is used as an extra class to give them a color. We don’t have to define the lava class again because we’re reusing the class for the lava grid squares we defined earlier.

- -
.actor  { position: absolute;            }
-.coin   { background: rgb(241, 229, 89); }
-.player { background: rgb(64, 64, 64);   }
- -

The syncState method is used to make the display show a given state. It first removes the old actor graphics, if any, and then redraws the actors in their new positions. It may be tempting to try to reuse the DOM elements for actors, but to make that work, we would need a lot of additional bookkeeping to associate actors with DOM elements and to make sure we remove elements when their actors vanish. Since there will typically be only a handful of actors in the game, redrawing all of them is not expensive.

- -
DOMDisplay.prototype.syncState = function(state) {
-  if (this.actorLayer) this.actorLayer.remove();
-  this.actorLayer = drawActors(state.actors);
-  this.dom.appendChild(this.actorLayer);
-  this.dom.className = `game ${state.status}`;
-  this.scrollPlayerIntoView(state);
-};
- -

By adding the level’s current status as a class name to the wrapper, we can style the player actor slightly differently when the game is won or lost by adding a CSS rule that takes effect only when the player has an ancestor element with a given class.

- -
.lost .player {
-  background: rgb(160, 64, 64);
-}
-.won .player {
-  box-shadow: -4px -7px 8px white, 4px -7px 8px white;
-}
- -

After touching lava, the player’s color turns dark red, suggesting scorching. When the last coin has been collected, we add two blurred white shadows—one to the top left and one to the top right—to create a white halo effect.

- -

We can’t assume that the level always fits in the viewport—the element into which we draw the game. That is why the scrollPlayerIntoView call is needed. It ensures that if the level is protruding outside the viewport, we scroll that viewport to make sure the player is near its center. The following CSS gives the game’s wrapping DOM element a maximum size and ensures that anything that sticks out of the element’s box is not visible. We also give it a relative position so that the actors inside it are positioned relative to the level’s top-left corner.

- -
.game {
-  overflow: hidden;
-  max-width: 600px;
-  max-height: 450px;
-  position: relative;
-}
- -

In the scrollPlayerIntoView method, we find the player’s position and update the wrapping element’s scroll position. We change the scroll position by manipulating that element’s scrollLeft and scrollTop properties when the player is too close to the edge.

- -
DOMDisplay.prototype.scrollPlayerIntoView = function(state) {
-  let width = this.dom.clientWidth;
-  let height = this.dom.clientHeight;
-  let margin = width / 3;
-
-  // The viewport
-  let left = this.dom.scrollLeft, right = left + width;
-  let top = this.dom.scrollTop, bottom = top + height;
-
-  let player = state.player;
-  let center = player.pos.plus(player.size.times(0.5))
-                         .times(scale);
-
-  if (center.x < left + margin) {
-    this.dom.scrollLeft = center.x - margin;
-  } else if (center.x > right - margin) {
-    this.dom.scrollLeft = center.x + margin - width;
-  }
-  if (center.y < top + margin) {
-    this.dom.scrollTop = center.y - margin;
-  } else if (center.y > bottom - margin) {
-    this.dom.scrollTop = center.y + margin - height;
-  }
-};
- -

The way the player’s center is found shows how the methods on our Vec type allow computations with objects to be written in a relatively readable way. To find the actor’s center, we add its position (its top-left corner) and half its size. That is the center in level coordinates, but we need it in pixel coordinates, so we then multiply the resulting vector by our display scale.

- -

Next, a series of checks verifies that the player position isn’t outside of the allowed range. Note that sometimes this will set nonsense scroll coordinates that are below zero or beyond the element’s scrollable area. This is okay—the DOM will constrain them to acceptable values. Setting scrollLeft to -10 will cause it to become 0.

- -

It would have been slightly simpler to always try to scroll the player to the center of the viewport. But this creates a rather jarring effect. As you are jumping, the view will constantly shift up and down. It is more pleasant to have a “neutral” area in the middle of the screen where you can move around without causing any scrolling.

- -

We are now able to display our tiny level.

- -
<link rel="stylesheet" href="css/game.css">
-
-<script>
-  let simpleLevel = new Level(simpleLevelPlan);
-  let display = new DOMDisplay(document.body, simpleLevel);
-  display.syncState(State.start(simpleLevel));
-</script>
- -

The <link> tag, when used with rel="stylesheet", is a way to load a CSS file into a page. The file game.css contains the styles necessary for our game.

- -

Motion and collision

- -

Now we’re at the point where we can start adding motion—the most interesting aspect of the game. The basic approach, taken by most games like this, is to split time into small steps and, for each step, move the actors by a distance corresponding to their speed multiplied by the size of the time step. We’ll measure time in seconds, so speeds are expressed in units per second.

- -

Moving things is easy. The difficult part is dealing with the interactions between the elements. When the player hits a wall or floor, they should not simply move through it. The game must notice when a given motion causes an object to hit another object and respond accordingly. For walls, the motion must be stopped. When hitting a coin, it must be collected. When touching lava, the game should be lost.

- -

Solving this for the general case is a big task. You can find libraries, usually called physics engines, that simulate interaction between physical objects in two or three dimensions. We’ll take a more modest approach in this chapter, handling only collisions between rectangular objects and handling them in a rather simplistic way.

- -

Before moving the player or a block of lava, we test whether the motion would take it inside of a wall. If it does, we simply cancel the motion altogether. The response to such a collision depends on the type of actor—the player will stop, whereas a lava block will bounce back.

- -

This approach requires our time steps to be rather small since it will cause motion to stop before the objects actually touch. If the time steps (and thus the motion steps) are too big, the player would end up hovering a noticeable distance above the ground. Another approach, arguably better but more complicated, would be to find the exact collision spot and move there. We will take the simple approach and hide its problems by ensuring the animation proceeds in small steps.

- -

This method tells us whether a rectangle (specified by a position and a size) touches a grid element of the given type.

- -
Level.prototype.touches = function(pos, size, type) {
-  var xStart = Math.floor(pos.x);
-  var xEnd = Math.ceil(pos.x + size.x);
-  var yStart = Math.floor(pos.y);
-  var yEnd = Math.ceil(pos.y + size.y);
-
-  for (var y = yStart; y < yEnd; y++) {
-    for (var x = xStart; x < xEnd; x++) {
-      let isOutside = x < 0 || x >= this.width ||
-                      y < 0 || y >= this.height;
-      let here = isOutside ? "wall" : this.rows[y][x];
-      if (here == type) return true;
-    }
-  }
-  return false;
-};
- -

The method computes the set of grid squares that the body overlaps with by using Math.floor and Math.ceil on its coordinates. Remember that grid squares are 1 by 1 units in size. By rounding the sides of a box up and down, we get the range of background squares that the box touches.

Finding collisions on a grid
- -

We loop over the block of grid squares found by rounding the coordinates and return true when a matching square is found. Squares outside of the level are always treated as "wall" to ensure that the player can’t leave the world and that we won’t accidentally try to read outside of the bounds of our rows array.

- -

The state update method uses touches to figure out whether the player is touching lava.

- -
State.prototype.update = function(time, keys) {
-  let actors = this.actors
-    .map(actor => actor.update(time, this, keys));
-  let newState = new State(this.level, actors, this.status);
-
-  if (newState.status != "playing") return newState;
-
-  let player = newState.player;
-  if (this.level.touches(player.pos, player.size, "lava")) {
-    return new State(this.level, actors, "lost");
-  }
-
-  for (let actor of actors) {
-    if (actor != player && overlap(actor, player)) {
-      newState = actor.collide(newState);
-    }
-  }
-  return newState;
-};
- -

The method is passed a time step and a data structure that tells it which keys are being held down. The first thing it does is call the update method on all actors, producing an array of updated actors. The actors also get the time step, the keys, and the state, so that they can base their update on those. Only the player will actually read keys, since that’s the only actor that’s controlled by the keyboard.

- -

If the game is already over, no further processing has to be done (the game can’t be won after being lost, or vice versa). Otherwise, the method tests whether the player is touching background lava. If so, the game is lost, and we’re done. Finally, if the game really is still going on, it sees whether any other actors overlap the player.

- -

Overlap between actors is detected with the overlap function. It takes two actor objects and returns true when they touch—which is the case when they overlap both along the x-axis and along the y-axis.

- -
function overlap(actor1, actor2) {
-  return actor1.pos.x + actor1.size.x > actor2.pos.x &&
-         actor1.pos.x < actor2.pos.x + actor2.size.x &&
-         actor1.pos.y + actor1.size.y > actor2.pos.y &&
-         actor1.pos.y < actor2.pos.y + actor2.size.y;
-}
- -

If any actor does overlap, its collide method gets a chance to update the state. Touching a lava actor sets the game status to "lost". Coins vanish when you touch them and set the status to "won" when they are the last coin of the level.

- -
Lava.prototype.collide = function(state) {
-  return new State(state.level, state.actors, "lost");
-};
-
-Coin.prototype.collide = function(state) {
-  let filtered = state.actors.filter(a => a != this);
-  let status = state.status;
-  if (!filtered.some(a => a.type == "coin")) status = "won";
-  return new State(state.level, filtered, status);
-};
- -

Actor updates

- -

Actor objects’ update methods take as arguments the time step, the state object, and a keys object. The one for the Lava actor type ignores the keys object.

- -
Lava.prototype.update = function(time, state) {
-  let newPos = this.pos.plus(this.speed.times(time));
-  if (!state.level.touches(newPos, this.size, "wall")) {
-    return new Lava(newPos, this.speed, this.reset);
-  } else if (this.reset) {
-    return new Lava(this.reset, this.speed, this.reset);
-  } else {
-    return new Lava(this.pos, this.speed.times(-1));
-  }
-};
- -

This update method computes a new position by adding the product of the time step and the current speed to its old position. If no obstacle blocks that new position, it moves there. If there is an obstacle, the behavior depends on the type of the lava block—dripping lava has a reset position, to which it jumps back when it hits something. Bouncing lava inverts its speed by multiplying it by -1 so that it starts moving in the opposite direction.

- -

Coins use their update method to wobble. They ignore collisions with the grid since they are simply wobbling around inside of their own square.

- -
const wobbleSpeed = 8, wobbleDist = 0.07;
-
-Coin.prototype.update = function(time) {
-  let wobble = this.wobble + time * wobbleSpeed;
-  let wobblePos = Math.sin(wobble) * wobbleDist;
-  return new Coin(this.basePos.plus(new Vec(0, wobblePos)),
-                  this.basePos, wobble);
-};
- -

The wobble property is incremented to track time and then used as an argument to Math.sin to find the new position on the wave. The coin’s current position is then computed from its base position and an offset based on this wave.

- -

That leaves the player itself. Player motion is handled separately per axis because hitting the floor should not prevent horizontal motion, and hitting a wall should not stop falling or jumping motion.

- -
const playerXSpeed = 7;
-const gravity = 30;
-const jumpSpeed = 17;
-
-Player.prototype.update = function(time, state, keys) {
-  let xSpeed = 0;
-  if (keys.ArrowLeft) xSpeed -= playerXSpeed;
-  if (keys.ArrowRight) xSpeed += playerXSpeed;
-  let pos = this.pos;
-  let movedX = pos.plus(new Vec(xSpeed * time, 0));
-  if (!state.level.touches(movedX, this.size, "wall")) {
-    pos = movedX;
-  }
-
-  let ySpeed = this.speed.y + time * gravity;
-  let movedY = pos.plus(new Vec(0, ySpeed * time));
-  if (!state.level.touches(movedY, this.size, "wall")) {
-    pos = movedY;
-  } else if (keys.ArrowUp && ySpeed > 0) {
-    ySpeed = -jumpSpeed;
-  } else {
-    ySpeed = 0;
-  }
-  return new Player(pos, new Vec(xSpeed, ySpeed));
-};
- -

The horizontal motion is computed based on the state of the left and right arrow keys. When there’s no wall blocking the new position created by this motion, it is used. Otherwise, the old position is kept.

- -

Vertical motion works in a similar way but has to simulate jumping and gravity. The player’s vertical speed (ySpeed) is first accelerated to account for gravity.

- -

We check for walls again. If we don’t hit any, the new position is used. If there is a wall, there are two possible outcomes. When the up arrow is pressed and we are moving down (meaning the thing we hit is below us), the speed is set to a relatively large, negative value. This causes the player to jump. If that is not the case, the player simply bumped into something, and the speed is set to zero.

- -

The gravity strength, jumping speed, and pretty much all other constants in this game have been set by trial and error. I tested values until I found a combination I liked.

- -

Tracking keys

- -

For a game like this, we do not want keys to take effect once per keypress. Rather, we want their effect (moving the player figure) to stay active as long as they are held.

- -

We need to set up a key handler that stores the current state of the left, right, and up arrow keys. We will also want to call preventDefault for those keys so that they don’t end up scrolling the page.

- -

The following function, when given an array of key names, will return an object that tracks the current position of those keys. It registers event handlers for "keydown" and "keyup" events and, when the key code in the event is present in the set of codes that it is tracking, updates the object.

- -
function trackKeys(keys) {
-  let down = Object.create(null);
-  function track(event) {
-    if (keys.includes(event.key)) {
-      down[event.key] = event.type == "keydown";
-      event.preventDefault();
-    }
-  }
-  window.addEventListener("keydown", track);
-  window.addEventListener("keyup", track);
-  return down;
-}
-
-const arrowKeys =
-  trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]);
- -

The same handler function is used for both event types. It looks at the event object’s type property to determine whether the key state should be updated to true ("keydown") or false ("keyup").

- -

Running the game

- -

The requestAnimationFrame function, which we saw in Chapter 14, provides a good way to animate a game. But its interface is quite primitive—using it requires us to track the time at which our function was called the last time around and call requestAnimationFrame again after every frame.

- -

Let’s define a helper function that wraps those boring parts in a convenient interface and allows us to simply call runAnimation, giving it a function that expects a time difference as an argument and draws a single frame. When the frame function returns the value false, the animation stops.

- -
function runAnimation(frameFunc) {
-  let lastTime = null;
-  function frame(time) {
-    if (lastTime != null) {
-      let timeStep = Math.min(time - lastTime, 100) / 1000;
-      if (frameFunc(timeStep) === false) return;
-    }
-    lastTime = time;
-    requestAnimationFrame(frame);
-  }
-  requestAnimationFrame(frame);
-}
- -

I have set a maximum frame step of 100 milliseconds (one-tenth of a second). When the browser tab or window with our page is hidden, requestAnimationFrame calls will be suspended until the tab or window is shown again. In this case, the difference between lastTime and time will be the entire time in which the page was hidden. Advancing the game by that much in a single step would look silly and might cause weird side effects, such as the player falling through the floor.

- -

The function also converts the time steps to seconds, which are an easier quantity to think about than milliseconds.

- -

The runLevel function takes a Level object and a display constructor and returns a promise. It displays the level (in document.body) and lets the user play through it. When the level is finished (lost or won), runLevel waits one more second (to let the user see what happens) and then clears the display, stops the animation, and resolves the promise to the game’s end status.

- -
function runLevel(level, Display) {
-  let display = new Display(document.body, level);
-  let state = State.start(level);
-  let ending = 1;
-  return new Promise(resolve => {
-    runAnimation(time => {
-      state = state.update(time, arrowKeys);
-      display.syncState(state);
-      if (state.status == "playing") {
-        return true;
-      } else if (ending > 0) {
-        ending -= time;
-        return true;
-      } else {
-        display.clear();
-        resolve(state.status);
-        return false;
-      }
-    });
-  });
-}
- -

A game is a sequence of levels. Whenever the player dies, the current level is restarted. When a level is completed, we move on to the next level. This can be expressed by the following function, which takes an array of level plans (strings) and a display constructor:

- -
async function runGame(plans, Display) {
-  for (let level = 0; level < plans.length;) {
-    let status = await runLevel(new Level(plans[level]),
-                                Display);
-    if (status == "won") level++;
-  }
-  console.log("You've won!");
-}
- -

Because we made runLevel return a promise, runGame can be written using an async function, as shown in Chapter 11. It returns another promise, which resolves when the player finishes the game.

- -

There is a set of level plans available in the GAME_LEVELS binding in this chapter’s sandbox. This page feeds them to runGame, starting an actual game.

- -
<link rel="stylesheet" href="css/game.css">
-
-<body>
-  <script>
-    runGame(GAME_LEVELS, DOMDisplay);
-  </script>
-</body>
- -

See if you can beat those. I had quite a lot of fun building them.

- -

Exercises

- -

Game over

- -

It’s traditional for platform games to have the player start with a limited number of lives and subtract one life each time they die. When the player is out of lives, the game restarts from the beginning.

- -

Adjust runGame to implement lives. Have the player start with three. Output the current number of lives (using console.log) every time a level starts.

- -
<link rel="stylesheet" href="css/game.css">
-
-<body>
-<script>
-  // The old runGame function. Modify it...
-  async function runGame(plans, Display) {
-    for (let level = 0; level < plans.length;) {
-      let status = await runLevel(new Level(plans[level]),
-                                  Display);
-      if (status == "won") level++;
-    }
-    console.log("You've won!");
-  }
-  runGame(GAME_LEVELS, DOMDisplay);
-</script>
-</body>
- -

Pausing the game

- -

Make it possible to pause (suspend) and unpause the game by pressing the Esc key.

- -

This can be done by changing the runLevel function to use another keyboard event handler and interrupting or resuming the animation whenever the Esc key is hit.

- -

The runAnimation interface may not look like it is suitable for this at first glance, but it is if you rearrange the way runLevel calls it.

- -

When you have that working, there is something else you could try. The way we have been registering keyboard event handlers is somewhat problematic. The arrowKeys object is currently a global binding, and its event handlers are kept around even when no game is running. You could say they leak out of our system. Extend trackKeys to provide a way to unregister its handlers and then change runLevel to register its handlers when it starts and unregister them again when it is finished.

- -
<link rel="stylesheet" href="css/game.css">
-
-<body>
-<script>
-  // The old runLevel function. Modify this...
-  function runLevel(level, Display) {
-    let display = new Display(document.body, level);
-    let state = State.start(level);
-    let ending = 1;
-    return new Promise(resolve => {
-      runAnimation(time => {
-        state = state.update(time, arrowKeys);
-        display.syncState(state);
-        if (state.status == "playing") {
-          return true;
-        } else if (ending > 0) {
-          ending -= time;
-          return true;
-        } else {
-          display.clear();
-          resolve(state.status);
-          return false;
-        }
-      });
-    });
-  }
-  runGame(GAME_LEVELS, DOMDisplay);
-</script>
-</body>
- -
- -

An animation can be interrupted by returning false from the function given to runAnimation. It can be continued by calling runAnimation again.

- -

So we need to communicate the fact that we are pausing the game to the function given to runAnimation. For that, you can use a binding that both the event handler and that function have access to.

- -

When finding a way to unregister the handlers registered by trackKeys, remember that the exact same function value that was passed to addEventListener must be passed to removeEventListener to successfully remove a handler. Thus, the handler function value created in trackKeys must be available to the code that unregisters the handlers.

- -

You can add a property to the object returned by trackKeys, containing either that function value or a method that handles the unregistering directly.

- -
- -

A monster

- -

It is traditional for platform games to have enemies that you can jump on top of to defeat. This exercise asks you to add such an actor type to the game.

- -

We’ll call it a monster. Monsters move only horizontally. You can make them move in the direction of the player, bounce back and forth like horizontal lava, or have any movement pattern you want. The class doesn’t have to handle falling, but it should make sure the monster doesn’t walk through walls.

- -

When a monster touches the player, the effect depends on whether the player is jumping on top of them or not. You can approximate this by checking whether the player’s bottom is near the monster’s top. If this is the case, the monster disappears. If not, the game is lost.

- -
<link rel="stylesheet" href="css/game.css">
-<style>.monster { background: purple }</style>
-
-<body>
-  <script>
-    // Complete the constructor, update, and collide methods
-    class Monster {
-      constructor(pos, /* ... */) {}
-
-      get type() { return "monster"; }
-
-      static create(pos) {
-        return new Monster(pos.plus(new Vec(0, -1)));
-      }
-
-      update(time, state) {}
-
-      collide(state) {}
-    }
-
-    Monster.prototype.size = new Vec(1.2, 2);
-
-    levelChars["M"] = Monster;
-
-    runLevel(new Level(`
-..................................
-.################################.
-.#..............................#.
-.#..............................#.
-.#..............................#.
-.#...........................o..#.
-.#..@...........................#.
-.##########..............########.
-..........#..o..o..o..o..#........
-..........#...........M..#........
-..........################........
-..................................
-`), DOMDisplay);
-  </script>
-</body>
- -
- -

If you want to implement a type of motion that is stateful, such as bouncing, make sure you store the necessary state in the actor object—include it as constructor argument and add it as a property.

- -

Remember that update returns a new object, rather than changing the old one.

- -

When handling collision, find the player in state.actors and compare its position to the monster’s position. To get the bottom of the player, you have to add its vertical size to its vertical position. The creation of an updated state will resemble either Coin’s collide method (removing the actor) or Lava’s (changing the status to "lost"), depending on the player position.

- -
-
diff --git a/docs/17_canvas.html b/docs/17_canvas.html deleted file mode 100644 index 8e598a9c9..000000000 --- a/docs/17_canvas.html +++ /dev/null @@ -1,746 +0,0 @@ - - - - - Drawing on Canvas :: Eloquent JavaScript - - - - - - -
- - -

Chapter 17Drawing on Canvas

- -
- -

Drawing is deception.

- -
M.C. Escher, cited by Bruno Ernst in The Magic Mirror of M.C. Escher
- -
Picture of a robot arm drawing on paper
- -

Browsers give us several ways to display graphics. The simplest way is to use styles to position and color regular DOM elements. This can get you quite far, as the game in the previous chapter showed. By adding partially transparent background images to the nodes, we can make them look exactly the way we want. It is even possible to rotate or skew nodes with the transform style.

- -

But we’d be using the DOM for something that it wasn’t originally designed for. Some tasks, such as drawing a line between arbitrary points, are extremely awkward to do with regular HTML elements.

- -

There are two alternatives. The first is DOM-based but utilizes Scalable Vector Graphics (SVG), rather than HTML. Think of SVG as a document-markup dialect that focuses on shapes rather than text. You can embed an SVG document directly in an HTML document or include it with an <img> tag.

- -

The second alternative is called a canvas. A canvas is a single DOM element that encapsulates a picture. It provides a programming interface for drawing shapes onto the space taken up by the node. The main difference between a canvas and an SVG picture is that in SVG the original description of the shapes is preserved so that they can be moved or resized at any time. A canvas, on the other hand, converts the shapes to pixels (colored dots on a raster) as soon as they are drawn and does not remember what these pixels represent. The only way to move a shape on a canvas is to clear the canvas (or the part of the canvas around the shape) and redraw it with the shape in a new position.

- -

SVG

- -

This book will not go into SVG in detail, but I will briefly explain how it works. At the end of the chapter, I’ll come back to the trade-offs that you must consider when deciding which drawing mechanism is appropriate for a given application.

- -

This is an HTML document with a simple SVG picture in it:

- -
<p>Normal HTML here.</p>
-<svg xmlns="http://www.w3.org/2000/svg">
-  <circle r="50" cx="50" cy="50" fill="red"/>
-  <rect x="120" y="5" width="90" height="90"
-        stroke="blue" fill="none"/>
-</svg>
- -

The xmlns attribute changes an element (and its children) to a different XML namespace. This namespace, identified by a URL, specifies the dialect that we are currently speaking. The <circle> and <rect> tags, which do not exist in HTML, do have a meaning in SVG—they draw shapes using the style and position specified by their attributes.

- -

These tags create DOM elements, just like HTML tags, that scripts can interact with. For example, this changes the <circle> element to be colored cyan instead:

- -
let circle = document.querySelector("circle");
-circle.setAttribute("fill", "cyan");
- -

The canvas element

- -

Canvas graphics can be drawn onto a <canvas> element. You can give such an element width and height attributes to determine its size in pixels.

- -

A new canvas is empty, meaning it is entirely transparent and thus shows up as empty space in the document.

- -

The <canvas> tag is intended to allow different styles of drawing. To get access to an actual drawing interface, we first need to create a context, an object whose methods provide the drawing interface. There are currently two widely supported drawing styles: "2d" for two-dimensional graphics and "webgl" for three-dimensional graphics through the OpenGL interface.

- -

This book won’t discuss WebGL—we’ll stick to two dimensions. But if you are interested in three-dimensional graphics, I do encourage you to look into WebGL. It provides a direct interface to graphics hardware and allows you to render even complicated scenes efficiently, using JavaScript.

- -

You create a context with the getContext method on the <canvas> DOM element.

- -
<p>Before canvas.</p>
-<canvas width="120" height="60"></canvas>
-<p>After canvas.</p>
-<script>
-  let canvas = document.querySelector("canvas");
-  let context = canvas.getContext("2d");
-  context.fillStyle = "red";
-  context.fillRect(10, 10, 100, 50);
-</script>
- -

After creating the context object, the example draws a red rectangle 100 pixels wide and 50 pixels high, with its top-left corner at coordinates (10,10).

- -

Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-axis goes down from there. So (10,10) is 10 pixels below and to the right of the top-left corner.

- -

Lines and surfaces

- -

In the canvas interface, a shape can be filled, meaning its area is given a certain color or pattern, or it can be stroked, which means a line is drawn along its edge. The same terminology is used by SVG.

- -

The fillRect method fills a rectangle. It takes first the x- and y-coordinates of the rectangle’s top-left corner, then its width, and then its height. A similar method, strokeRect, draws the outline of a rectangle.

- -

Neither method takes any further parameters. The color of the fill, thickness of the stroke, and so on, are not determined by an argument to the method (as you might reasonably expect) but rather by properties of the context object.

- -

The fillStyle property controls the way shapes are filled. It can be set to a string that specifies a color, using the color notation used by CSS.

- -

The strokeStyle property works similarly but determines the color used for a stroked line. The width of that line is determined by the lineWidth property, which may contain any positive number.

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  cx.strokeStyle = "blue";
-  cx.strokeRect(5, 5, 50, 50);
-  cx.lineWidth = 5;
-  cx.strokeRect(135, 5, 50, 50);
-</script>
- -

When no width or height attribute is specified, as in the example, a canvas element gets a default width of 300 pixels and height of 150 pixels.

- -

Paths

- -

A path is a sequence of lines. The 2D canvas interface takes a peculiar approach to describing such a path. It is done entirely through side effects. Paths are not values that can be stored and passed around. Instead, if you want to do something with a path, you make a sequence of method calls to describe its shape.

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  cx.beginPath();
-  for (let y = 10; y < 100; y += 10) {
-    cx.moveTo(10, y);
-    cx.lineTo(90, y);
-  }
-  cx.stroke();
-</script>
- -

This example creates a path with a number of horizontal line segments and then strokes it using the stroke method. Each segment created with lineTo starts at the path’s current position. That position is usually the end of the last segment, unless moveTo was called. In that case, the next segment would start at the position passed to moveTo.

- -

When filling a path (using the fill method), each shape is filled separately. A path can contain multiple shapes—each moveTo motion starts a new one. But the path needs to be closed (meaning its start and end are in the same position) before it can be filled. If the path is not already closed, a line is added from its end to its start, and the shape enclosed by the completed path is filled.

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  cx.beginPath();
-  cx.moveTo(50, 10);
-  cx.lineTo(10, 70);
-  cx.lineTo(90, 70);
-  cx.fill();
-</script>
- -

This example draws a filled triangle. Note that only two of the triangle’s sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn’t be there when you stroke the path.

- -

You could also use the closePath method to explicitly close a path by adding an actual line segment back to the path’s start. This segment is drawn when stroking the path.

- -

Curves

- -

A path may also contain curved lines. These are unfortunately a bit more involved to draw.

- -

The quadraticCurveTo method draws a curve to a given point. To determine the curvature of the line, the method is given a control -point as well as a destination point. Imagine this control point as attracting the line, giving it its curve. The line won’t go through the control point, but its direction at the start and end points will be such that a straight line in that direction would point toward the control point. The following example illustrates this:

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  cx.beginPath();
-  cx.moveTo(10, 90);
-  // control=(60,10) goal=(90,90)
-  cx.quadraticCurveTo(60, 10, 90, 90);
-  cx.lineTo(60, 10);
-  cx.closePath();
-  cx.stroke();
-</script>
- -

We draw a quadratic curve from the left to the right, with (60,10) as control point, and then draw two line segments going through that control point and back to the start of the line. The result somewhat resembles a Star Trek insignia. You can see the effect of the control point: the lines leaving the lower corners start off in the direction of the control point and then curve toward their target.

- -

The bezierCurveTo method draws a similar kind of curve. Instead of a single control point, this one has two—one for each of the line’s endpoints. Here is a similar sketch to illustrate the behavior of such a curve:

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  cx.beginPath();
-  cx.moveTo(10, 90);
-  // control1=(10,10) control2=(90,10) goal=(50,90)
-  cx.bezierCurveTo(10, 10, 90, 10, 50, 90);
-  cx.lineTo(90, 10);
-  cx.lineTo(10, 10);
-  cx.closePath();
-  cx.stroke();
-</script>
- -

The two control points specify the direction at both ends of the curve. The farther they are away from their corresponding point, the more the curve will “bulge” in that direction.

- -

Such curves can be hard to work with—it’s not always clear how to find the control points that provide the shape you are looking for. Sometimes you can compute them, and sometimes you’ll just have to find a suitable value by trial and error.

- -

The arc method is a way to draw a line that curves along the edge of a circle. It takes a pair of coordinates for the arc’s center, a radius, and then a start angle and end angle.

- -

Those last two parameters make it possible to draw only part of the circle. The angles are measured in radians, not degrees. This means a full circle has an angle of 2π, or 2 * Math.PI, which is about 6.28. The angle starts counting at the point to the right of the circle’s center and goes clockwise from there. You can use a start of 0 and an end bigger than 2π (say, 7) to draw a full circle.

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  cx.beginPath();
-  // center=(50,50) radius=40 angle=0 to 7
-  cx.arc(50, 50, 40, 0, 7);
-  // center=(150,50) radius=40 angle=0 to ½π
-  cx.arc(150, 50, 40, 0, 0.5 * Math.PI);
-  cx.stroke();
-</script>
- -

The resulting picture contains a line from the right of the full circle (first call to arc) to the right of the quarter-circle (second call). Like other path-drawing methods, a line drawn with arc is connected to the previous path segment. You can call moveTo or start a new path to avoid this.

- -

Drawing a pie chart

- -

Imagine you’ve just taken a job at EconomiCorp, Inc., and your first assignment is to draw a pie chart of its customer satisfaction survey results.

- -

The results binding contains an array of objects that represent the survey responses.

- -
const results = [
-  {name: "Satisfied", count: 1043, color: "lightblue"},
-  {name: "Neutral", count: 563, color: "lightgreen"},
-  {name: "Unsatisfied", count: 510, color: "pink"},
-  {name: "No comment", count: 175, color: "silver"}
-];
- -

To draw a pie chart, we draw a number of pie slices, each made up of an arc and a pair of lines to the center of that arc. We can compute the angle taken up by each arc by dividing a full circle (2π) by the total number of responses and then multiplying that number (the angle per response) by the number of people who picked a given choice.

- -
<canvas width="200" height="200"></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  let total = results
-    .reduce((sum, {count}) => sum + count, 0);
-  // Start at the top
-  let currentAngle = -0.5 * Math.PI;
-  for (let result of results) {
-    let sliceAngle = (result.count / total) * 2 * Math.PI;
-    cx.beginPath();
-    // center=100,100, radius=100
-    // from current angle, clockwise by slice's angle
-    cx.arc(100, 100, 100,
-           currentAngle, currentAngle + sliceAngle);
-    currentAngle += sliceAngle;
-    cx.lineTo(100, 100);
-    cx.fillStyle = result.color;
-    cx.fill();
-  }
-</script>
- -

But a chart that doesn’t tell us what the slices mean isn’t very helpful. We need a way to draw text to the canvas.

- -

Text

- -

A 2D canvas drawing context provides the methods fillText and strokeText. The latter can be useful for outlining letters, but usually fillText is what you need. It will fill the outline of the given text with the current fillStyle.

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  cx.font = "28px Georgia";
-  cx.fillStyle = "fuchsia";
-  cx.fillText("I can draw text, too!", 10, 50);
-</script>
- -

You can specify the size, style, and font of the text with the font property. This example just gives a font size and family name. It is also possible to add italic or bold to the start of the string to select a style.

- -

The last two arguments to fillText and strokeText provide the position at which the font is drawn. By default, they indicate the position of the start of the text’s alphabetic baseline, which is the line that letters “stand” on, not counting hanging parts in letters such as j or p. You can change the horizontal position by setting the textAlign property to "end" or "center" and the vertical position by setting textBaseline to "top", "middle", or "bottom".

- -

We’ll come back to our pie chart, and the problem of labeling the slices, in the exercises at the end of the chapter.

- -

Images

- -

In computer graphics, a distinction is often made between vector graphics and bitmap graphics. The first is what we have been doing so far in this chapter—specifying a picture by giving a logical description of shapes. Bitmap graphics, on the other hand, don’t specify actual shapes but rather work with pixel data (rasters of colored dots).

- -

The drawImage method allows us to draw pixel data onto a canvas. This pixel data can originate from an <img> element or from another canvas. The following example creates a detached <img> element and loads an image file into it. But it cannot immediately start drawing from this picture because the browser may not have loaded it yet. To deal with this, we register a "load" event handler and do the drawing after the image has loaded.

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  let img = document.createElement("img");
-  img.src = "img/hat.png";
-  img.addEventListener("load", () => {
-    for (let x = 10; x < 200; x += 30) {
-      cx.drawImage(img, x, 10);
-    }
-  });
-</script>
- -

By default, drawImage will draw the image at its original size. You can also give it two additional arguments to set a different width and height.

- -

When drawImage is given nine arguments, it can be used to draw only a fragment of an image. The second through fifth arguments indicate the rectangle (x, y, width, and height) in the source image that should be copied, and the sixth to ninth arguments give the rectangle (on the canvas) into which it should be copied.

- -

This can be used to pack multiple sprites (image elements) into a single image file and then draw only the part you need. For example, we have this picture containing a game character in multiple poses:

Various poses of a game character
- -

By alternating which pose we draw, we can show an animation that looks like a walking character.

- -

To animate a picture on a canvas, the clearRect method is useful. It resembles fillRect, but instead of coloring the rectangle, it makes it transparent, removing the previously drawn pixels.

- -

We know that each sprite, each subpicture, is 24 pixels wide and 30 pixels high. The following code loads the image and then sets up an interval (repeated timer) to draw the next frame:

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  let img = document.createElement("img");
-  img.src = "img/player.png";
-  let spriteW = 24, spriteH = 30;
-  img.addEventListener("load", () => {
-    let cycle = 0;
-    setInterval(() => {
-      cx.clearRect(0, 0, spriteW, spriteH);
-      cx.drawImage(img,
-                   // source rectangle
-                   cycle * spriteW, 0, spriteW, spriteH,
-                   // destination rectangle
-                   0,               0, spriteW, spriteH);
-      cycle = (cycle + 1) % 8;
-    }, 120);
-  });
-</script>
- -

The cycle binding tracks our position in the animation. For each frame, it is incremented and then clipped back to the 0 to 7 range by using the remainder operator. This binding is then used to compute the x-coordinate that the sprite for the current pose has in the picture.

- -

Transformation

- -

But what if we want our character to walk to the left instead of to the right? We could draw another set of sprites, of course. But we can also instruct the canvas to draw the picture the other way round.

- -

Calling the scale method will cause anything drawn after it to be scaled. This method takes two parameters, one to set a horizontal scale and one to set a vertical scale.

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  cx.scale(3, .5);
-  cx.beginPath();
-  cx.arc(50, 50, 40, 0, 7);
-  cx.lineWidth = 3;
-  cx.stroke();
-</script>
- -

Scaling will cause everything about the drawn image, including the line width, to be stretched out or squeezed together as specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0,0), which means it will also flip the direction of the coordinate system. When a horizontal scaling of -1 is applied, a shape drawn at x position 100 will end up at what used to be position -100.

- -

So to turn a picture around, we can’t simply add cx.scale(-1, 1) before the call to drawImage because that would move our picture outside of the canvas, where it won’t be visible. You could adjust the coordinates given to drawImage to compensate for this by drawing the image at x position -50 instead of 0. Another solution, which doesn’t require the code that does the drawing to know about the scale change, is to adjust the axis around which the scaling happens.

- -

There are several other methods besides scale that influence the coordinate system for a canvas. You can rotate subsequently drawn shapes with the rotate method and move them with the translate method. The interesting—and confusing—thing is that these transformations stack, meaning that each one happens relative to the previous transformations.

- -

So if we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50,50) and then rotate by 20 degrees (about 0.1π radians), that rotation will happen around point (50,50).

Stacking transformations
- -

But if we first rotate by 20 degrees and then translate by (50,50), the translation will happen in the rotated coordinate system and thus produce a different orientation. The order in which transformations are applied matters.

- -

To flip a picture around the vertical line at a given x position, we can do the following:

- -
function flipHorizontally(context, around) {
-  context.translate(around, 0);
-  context.scale(-1, 1);
-  context.translate(-around, 0);
-}
- -

We move the y-axis to where we want our mirror to be, apply the mirroring, and finally move the y-axis back to its proper place in the mirrored universe. The following picture explains why this works:

Mirroring around a vertical line
- -

This shows the coordinate systems before and after mirroring across the central line. The triangles are numbered to illustrate each step. If we draw a triangle at a positive x position, it would, by default, be in the place where triangle 1 is. A call to flipHorizontally first does a translation to the right, which gets us to triangle 2. It then scales, flipping the triangle over to position 3. This is not where it should be, if it were mirrored in the given line. The second translate call fixes this—it “cancels” the initial translation and makes triangle 4 appear exactly where it should.

- -

We can now draw a mirrored character at position (100,0) by flipping the world around the character’s vertical center.

- -
<canvas></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  let img = document.createElement("img");
-  img.src = "img/player.png";
-  let spriteW = 24, spriteH = 30;
-  img.addEventListener("load", () => {
-    flipHorizontally(cx, 100 + spriteW / 2);
-    cx.drawImage(img, 0, 0, spriteW, spriteH,
-                 100, 0, spriteW, spriteH);
-  });
-</script>
- -

Storing and clearing transformations

- -

Transformations stick around. Everything else we draw after drawing that mirrored character would also be mirrored. That might be inconvenient.

- -

It is possible to save the current transformation, do some drawing and transforming, and then restore the old transformation. This is usually the proper thing to do for a function that needs to temporarily transform the coordinate system. First, we save whatever transformation the code that called the function was using. Then the function does its thing, adding more transformations on top of the current transformation. Finally, we revert to the transformation we started with.

- -

The save and restore methods on the 2D canvas context do this transformation management. They conceptually keep a stack of transformation states. When you call save, the current state is pushed onto the stack, and when you call restore, the state on top of the stack is taken off and used as the context’s current transformation. You can also call resetTransform to fully reset the transformation.

- -

The branch function in the following example illustrates what you can do with a function that changes the transformation and then calls a function (in this case itself), which continues drawing with the given transformation.

- -

This function draws a treelike shape by drawing a line, moving the center of the coordinate system to the end of the line, and calling itself twice—first rotated to the left and then rotated to the right. Every call reduces the length of the branch drawn, and the recursion stops when the length drops below 8.

- -
<canvas width="600" height="300"></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  function branch(length, angle, scale) {
-    cx.fillRect(0, 0, 1, length);
-    if (length < 8) return;
-    cx.save();
-    cx.translate(0, length);
-    cx.rotate(-angle);
-    branch(length * scale, angle, scale);
-    cx.rotate(2 * angle);
-    branch(length * scale, angle, scale);
-    cx.restore();
-  }
-  cx.translate(300, 0);
-  branch(60, 0.5, 0.8);
-</script>
- -

If the calls to save and restore were not there, the second recursive call to branch would end up with the position and rotation created by the first call. It wouldn’t be connected to the current branch but rather to the innermost, rightmost branch drawn by the first call. The resulting shape might also be interesting, but it is definitely not a tree.

- -

Back to the game

- -

We now know enough about canvas drawing to start working on a canvas-based display system for the game from the previous chapter. The new display will no longer be showing just colored boxes. Instead, we’ll use drawImage to draw pictures that represent the game’s elements.

- -

We define another display object type called CanvasDisplay, supporting the same interface as DOMDisplay from Chapter 16, namely, the methods syncState and clear.

- -

This object keeps a little more information than DOMDisplay. Rather than using the scroll position of its DOM element, it tracks its own viewport, which tells us what part of the level we are currently looking at. Finally, it keeps a flipPlayer property so that even when the player is standing still, it keeps facing the direction it last moved in.

- -
class CanvasDisplay {
-  constructor(parent, level) {
-    this.canvas = document.createElement("canvas");
-    this.canvas.width = Math.min(600, level.width * scale);
-    this.canvas.height = Math.min(450, level.height * scale);
-    parent.appendChild(this.canvas);
-    this.cx = this.canvas.getContext("2d");
-
-    this.flipPlayer = false;
-
-    this.viewport = {
-      left: 0,
-      top: 0,
-      width: this.canvas.width / scale,
-      height: this.canvas.height / scale
-    };
-  }
-
-  clear() {
-    this.canvas.remove();
-  }
-}
- -

The syncState method first computes a new viewport and then draws the game scene at the appropriate position.

- -
CanvasDisplay.prototype.syncState = function(state) {
-  this.updateViewport(state);
-  this.clearDisplay(state.status);
-  this.drawBackground(state.level);
-  this.drawActors(state.actors);
-};
- -

Contrary to DOMDisplay, this display style does have to redraw the background on every update. Because shapes on a canvas are just pixels, after we draw them there is no good way to move them (or remove them). The only way to update the canvas display is to clear it and redraw the scene. We may also have scrolled, which requires the background to be in a different position.

- -

The updateViewport method is similar to DOMDisplay’s scrollPlayerIntoView method. It checks whether the player is too close to the edge of the screen and moves the viewport when this is the case.

- -
CanvasDisplay.prototype.updateViewport = function(state) {
-  let view = this.viewport, margin = view.width / 3;
-  let player = state.player;
-  let center = player.pos.plus(player.size.times(0.5));
-
-  if (center.x < view.left + margin) {
-    view.left = Math.max(center.x - margin, 0);
-  } else if (center.x > view.left + view.width - margin) {
-    view.left = Math.min(center.x + margin - view.width,
-                         state.level.width - view.width);
-  }
-  if (center.y < view.top + margin) {
-    view.top = Math.max(center.y - margin, 0);
-  } else if (center.y > view.top + view.height - margin) {
-    view.top = Math.min(center.y + margin - view.height,
-                        state.level.height - view.height);
-  }
-};
- -

The calls to Math.max and Math.min ensure that the viewport does not end up showing space outside of the level. Math.max(x, 0) makes sure the resulting number is not less than zero. Math.min similarly guarantees that a value stays below a given bound.

- -

When clearing the display, we’ll use a slightly different color depending on whether the game is won (brighter) or lost (darker).

- -
CanvasDisplay.prototype.clearDisplay = function(status) {
-  if (status == "won") {
-    this.cx.fillStyle = "rgb(68, 191, 255)";
-  } else if (status == "lost") {
-    this.cx.fillStyle = "rgb(44, 136, 214)";
-  } else {
-    this.cx.fillStyle = "rgb(52, 166, 251)";
-  }
-  this.cx.fillRect(0, 0,
-                   this.canvas.width, this.canvas.height);
-};
- -

To draw the background, we run through the tiles that are visible in the current viewport, using the same trick used in the touches method from the previous chapter.

- -
let otherSprites = document.createElement("img");
-otherSprites.src = "img/sprites.png";
-
-CanvasDisplay.prototype.drawBackground = function(level) {
-  let {left, top, width, height} = this.viewport;
-  let xStart = Math.floor(left);
-  let xEnd = Math.ceil(left + width);
-  let yStart = Math.floor(top);
-  let yEnd = Math.ceil(top + height);
-
-  for (let y = yStart; y < yEnd; y++) {
-    for (let x = xStart; x < xEnd; x++) {
-      let tile = level.rows[y][x];
-      if (tile == "empty") continue;
-      let screenX = (x - left) * scale;
-      let screenY = (y - top) * scale;
-      let tileX = tile == "lava" ? scale : 0;
-      this.cx.drawImage(otherSprites,
-                        tileX,         0, scale, scale,
-                        screenX, screenY, scale, scale);
-    }
-  }
-};
- -

Tiles that are not empty are drawn with drawImage. The otherSprites image contains the pictures used for elements other than the player. It contains, from left to right, the wall tile, the lava tile, and the sprite for a coin.

Sprites for our game
- -

Background tiles are 20 by 20 pixels since we will use the same scale that we used in DOMDisplay. Thus, the offset for lava tiles is 20 (the value of the scale binding), and the offset for walls is 0.

- -

We don’t bother waiting for the sprite image to load. Calling drawImage with an image that hasn’t been loaded yet will simply do nothing. Thus, we might fail to draw the game properly for the first few frames, while the image is still loading, but that is not a serious problem. Since we keep updating the screen, the correct scene will appear as soon as the loading finishes.

- -

The walking character shown earlier will be used to represent the player. The code that draws it needs to pick the right sprite and direction based on the player’s current motion. The first eight sprites contain a walking animation. When the player is moving along a floor, we cycle through them based on the current time. We want to switch frames every 60 milliseconds, so the time is divided by 60 first. When the player is standing still, we draw the ninth sprite. During jumps, which are recognized by the fact that the vertical speed is not zero, we use the tenth, rightmost sprite.

- -

Because the sprites are slightly wider than the player object—24 instead of 16 pixels to allow some space for feet and arms—the method has to adjust the x-coordinate and width by a given amount (playerXOverlap).

- -
let playerSprites = document.createElement("img");
-playerSprites.src = "img/player.png";
-const playerXOverlap = 4;
-
-CanvasDisplay.prototype.drawPlayer = function(player, x, y,
-                                              width, height){
-  width += playerXOverlap * 2;
-  x -= playerXOverlap;
-  if (player.speed.x != 0) {
-    this.flipPlayer = player.speed.x < 0;
-  }
-
-  let tile = 8;
-  if (player.speed.y != 0) {
-    tile = 9;
-  } else if (player.speed.x != 0) {
-    tile = Math.floor(Date.now() / 60) % 8;
-  }
-
-  this.cx.save();
-  if (this.flipPlayer) {
-    flipHorizontally(this.cx, x + width / 2);
-  }
-  let tileX = tile * width;
-  this.cx.drawImage(playerSprites, tileX, 0, width, height,
-                                   x,     y, width, height);
-  this.cx.restore();
-};
- -

The drawPlayer method is called by drawActors, which is responsible for drawing all the actors in the game.

- -
CanvasDisplay.prototype.drawActors = function(actors) {
-  for (let actor of actors) {
-    let width = actor.size.x * scale;
-    let height = actor.size.y * scale;
-    let x = (actor.pos.x - this.viewport.left) * scale;
-    let y = (actor.pos.y - this.viewport.top) * scale;
-    if (actor.type == "player") {
-      this.drawPlayer(actor, x, y, width, height);
-    } else {
-      let tileX = (actor.type == "coin" ? 2 : 1) * scale;
-      this.cx.drawImage(otherSprites,
-                        tileX, 0, width, height,
-                        x,     y, width, height);
-    }
-  }
-};
- -

When drawing something that is not the player, we look at its type to find the offset of the correct sprite. The lava tile is found at offset 20, and the coin sprite is found at 40 (two times scale).

- -

We have to subtract the viewport’s position when computing the actor’s position since (0,0) on our canvas corresponds to the top left of the viewport, not the top left of the level. We could also have used translate for this. Either way works.

- -

This document plugs the new display into runGame:

- -
<body>
-  <script>
-    runGame(GAME_LEVELS, CanvasDisplay);
-  </script>
-</body>
- -

Choosing a graphics interface

- -

So when you need to generate graphics in the browser, you can choose between plain HTML, SVG, and canvas. There is no single best approach that works in all situations. Each option has strengths and weaknesses.

- -

Plain HTML has the advantage of being simple. It also integrates well with text. Both SVG and canvas allow you to draw text, but they won’t help you position that text or wrap it when it takes up more than one line. In an HTML-based picture, it is much easier to include blocks of text.

- -

SVG can be used to produce crisp graphics that look good at any zoom level. Unlike HTML, it is designed for drawing and is thus more suitable for that purpose.

- -

Both SVG and HTML build up a data structure (the DOM) that represents your picture. This makes it possible to modify elements after they are drawn. If you need to repeatedly change a small part of a big picture in response to what the user is doing or as part of an animation, doing it in a canvas can be needlessly expensive. The DOM also allows us to register mouse event handlers on every element in the picture (even on shapes drawn with SVG). You can’t do that with canvas.

- -

But canvas’s pixel-oriented approach can be an advantage when drawing a huge number of tiny elements. The fact that it does not build up a data structure but only repeatedly draws onto the same pixel surface gives canvas a lower cost per shape.

- -

There are also effects, such as rendering a scene one pixel at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that can be realistically handled only by a pixel-based approach.

- -

In some cases, you may want to combine several of these techniques. For example, you might draw a graph with SVG or canvas but show textual information by positioning an HTML element on top of the picture.

- -

For nondemanding applications, it really doesn’t matter much which interface you choose. The display we built for our game in this chapter could have been implemented using any of these three graphics technologies since it does not need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements.

- -

Summary

- -

In this chapter we discussed techniques for drawing graphics in the browser, focusing on the <canvas> element.

- -

A canvas node represents an area in a document that our program may draw on. This drawing is done through a drawing context object, created with the getContext method.

- -

The 2D drawing interface allows us to fill and stroke various shapes. The context’s fillStyle property determines how shapes are filled. The strokeStyle and lineWidth properties control the way lines are drawn.

- -

Rectangles and pieces of text can be drawn with a single method call. The fillRect and strokeRect methods draw rectangles, and the fillText and strokeText methods draw text. To create custom shapes, we must first build up a path.

- -

Calling beginPath starts a new path. A number of other methods add lines and curves to the current path. For example, lineTo can add a straight line. When a path is finished, it can be filled with the fill method or stroked with the stroke method.

- -

Moving pixels from an image or another canvas onto our canvas is done with the drawImage method. By default, this method draws the whole source image, but by giving it more parameters, you can copy a specific area of the image. We used this for our game by copying individual poses of the game character out of an image that contained many such poses.

- -

Transformations allow you to draw a shape in multiple orientations. A 2D drawing context has a current transformation that can be changed with the translate, scale, and rotate methods. These will affect all subsequent drawing operations. A transformation state can be saved with the save method and restored with the restore method.

- -

When showing an animation on a canvas, the clearRect method can be used to clear part of the canvas before redrawing it.

- -

Exercises

- -

Shapes

- -

Write a program that draws the following shapes on a canvas:

- -
    - -
  1. - -

    A trapezoid (a rectangle that is wider on one side)

  2. - -
  3. - -

    A red diamond (a rectangle rotated 45 degrees or ¼π radians)

  4. - -
  5. - -

    A zigzagging line

  6. - -
  7. - -

    A spiral made up of 100 straight line segments

  8. - -
  9. - -

    A yellow star

  10. - -
The shapes to draw
- -

When drawing the last two, you may want to refer to the explanation of Math.cos and Math.sin in Chapter 14, which describes how to get coordinates on a circle using these functions.

- -

I recommend creating a function for each shape. Pass the position, and optionally other properties such as the size or the number of points, as parameters. The alternative, which is to hard-code numbers all over your code, tends to make the code needlessly hard to read and modify.

- -
<canvas width="600" height="200"></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-
-  // Your code here.
-</script>
- -
- -

The trapezoid (1) is easiest to draw using a path. Pick suitable center coordinates and add each of the four corners around the center.

- -

The diamond (2) can be drawn the straightforward way, with a path, or the interesting way, with a rotate transformation. To use rotation, you will have to apply a trick similar to what we did in the flipHorizontally function. Because you want to rotate around the center of your rectangle and not around the point (0,0), you must first translate to there, then rotate, and then translate back.

- -

Make sure you reset the transformation after drawing any shape that creates one.

- -

For the zigzag (3) it becomes impractical to write a new call to lineTo for each line segment. Instead, you should use a loop. You can have each iteration draw either two line segments (right and then left again) or one, in which case you must use the evenness (% 2) of the loop index to determine whether to go left or right.

- -

You’ll also need a loop for the spiral (4). If you draw a series of points, with each point moving further along a circle around the spiral’s center, you get a circle. If, during the loop, you vary the radius of the circle on which you are putting the current point and go around more than once, the result is a spiral.

- -

The star (5) depicted is built out of quadraticCurveTo lines. You could also draw one with straight lines. Divide a circle into eight pieces for a star with eight points, or however many pieces you want. Draw lines between these points, making them curve toward the center of the star. With quadraticCurveTo, you can use the center as the control point.

- -
- -

The pie chart

- -

Earlier in the chapter, we saw an example program that drew a pie chart. Modify this program so that the name of each category is shown next to the slice that represents it. Try to find a pleasing-looking way to automatically position this text that would work for other data sets as well. You may assume that categories are big enough to leave ample room for their labels.

- -

You might need Math.sin and Math.cos again, which are described in Chapter 14.

- -
<canvas width="600" height="300"></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-  let total = results
-    .reduce((sum, {count}) => sum + count, 0);
-  let currentAngle = -0.5 * Math.PI;
-  let centerX = 300, centerY = 150;
-
-  // Add code to draw the slice labels in this loop.
-  for (let result of results) {
-    let sliceAngle = (result.count / total) * 2 * Math.PI;
-    cx.beginPath();
-    cx.arc(centerX, centerY, 100,
-           currentAngle, currentAngle + sliceAngle);
-    currentAngle += sliceAngle;
-    cx.lineTo(centerX, centerY);
-    cx.fillStyle = result.color;
-    cx.fill();
-  }
-</script>
- -
- -

You will need to call fillText and set the context’s textAlign and textBaseline properties in such a way that the text ends up where you want it.

- -

A sensible way to position the labels would be to put the text on the line going from the center of the pie through the middle of the slice. You don’t want to put the text directly against the side of the pie but rather move the text out to the side of the pie by a given number of pixels.

- -

The angle of this line is currentAngle + 0.5 * sliceAngle. The following code finds a position on this line 120 pixels from the center:

- -
let middleAngle = currentAngle + 0.5 * sliceAngle;
-let textX = Math.cos(middleAngle) * 120 + centerX;
-let textY = Math.sin(middleAngle) * 120 + centerY;
- -

For textBaseline, the value "middle" is probably appropriate when using this approach. What to use for textAlign depends on which side of the circle we are on. On the left, it should be "right", and on the right, it should be "left", so that the text is positioned away from the pie.

- -

If you are not sure how to find out which side of the circle a given angle is on, look to the explanation of Math.cos in Chapter 14. The cosine of an angle tells us which x-coordinate it corresponds to, which in turn tells us exactly which side of the circle we are on.

- -
- -

A bouncing ball

- -

Use the requestAnimationFrame technique that we saw in Chapter 14 and Chapter 16 to draw a box with a bouncing ball in it. The ball moves at a constant speed and bounces off the box’s sides when it hits them.

- -
<canvas width="400" height="400"></canvas>
-<script>
-  let cx = document.querySelector("canvas").getContext("2d");
-
-  let lastTime = null;
-  function frame(time) {
-    if (lastTime != null) {
-      updateAnimation(Math.min(100, time - lastTime) / 1000);
-    }
-    lastTime = time;
-    requestAnimationFrame(frame);
-  }
-  requestAnimationFrame(frame);
-
-  function updateAnimation(step) {
-    // Your code here.
-  }
-</script>
- -
- -

A box is easy to draw with strokeRect. Define a binding that holds its size or define two bindings if your box’s width and height differ. To create a round ball, start a path and call arc(x, y, radius, 0, 7), which creates an arc going from zero to more than a whole circle. Then fill the path.

- -

To model the ball’s position and speed, you can use the Vec class from Chapter 16 (which is available on this page). Give it a starting speed, preferably one that is not purely vertical or horizontal, and for every frame multiply that speed by the amount of time that elapsed. When the ball gets too close to a vertical wall, invert the x component in its speed. Likewise, invert the y component when it hits a horizontal wall.

- -

After finding the ball’s new position and speed, use clearRect to delete the scene and redraw it using the new position.

- -
- -

Precomputed mirroring

- -

One unfortunate thing about transformations is that they slow down the drawing of bitmaps. The position and size of each pixel has to be transformed, and though it is possible that browsers will get cleverer about transformation in the future, they currently cause a measurable increase in the time it takes to draw a bitmap.

- -

In a game like ours, where we are drawing only a single transformed sprite, this is a nonissue. But imagine that we need to draw hundreds of characters or thousands of rotating particles from an explosion.

- -

Think of a way to allow us to draw an inverted character without loading additional image files and without having to make transformed drawImage calls every frame.

- -
- -

The key to the solution is the fact that we can use a canvas element as a source image when using drawImage. It is possible to create an extra <canvas> element, without adding it to the document, and draw our inverted sprites to it, once. When drawing an actual frame, we just copy the already inverted sprites to the main canvas.

- -

Some care would be required because images do not load instantly. We do the inverted drawing only once, and if we do it before the image loads, it won’t draw anything. A "load" handler on the image can be used to draw the inverted images to the extra canvas. This canvas can be used as a drawing source immediately (it’ll simply be blank until we draw the character onto it).

- -
-
diff --git a/docs/18_http.html b/docs/18_http.html deleted file mode 100644 index 2ae7edf4e..000000000 --- a/docs/18_http.html +++ /dev/null @@ -1,695 +0,0 @@ - - - - - HTTP and Forms :: Eloquent JavaScript - - - - - - -
- - -

Chapter 18HTTP and Forms

- -
- -

Communication must be stateless in nature [...] such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server.

- -
Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures
- -
Picture of a web form on a medieval scroll
- -

The Hypertext Transfer Protocol, already mentioned in Chapter 13, is the mechanism through which data is requested and provided on the World Wide Web. This chapter describes the protocol in more detail and explains the way browser JavaScript has access to it.

- -

The protocol

- -

If you type eloquentjavascript.net/18_http.html into your browser’s address bar, the browser first looks up the address of the server associated with eloquentjavascript.net and tries to open a TCP connection to it on port 80, the default port for HTTP traffic. If the server exists and accepts the connection, the browser might send something like this:

- -
GET /18_http.html HTTP/1.1
-Host: eloquentjavascript.net
-User-Agent: Your browser's name
- -

Then the server responds, through that same connection.

- -
HTTP/1.1 200 OK
-Content-Length: 65585
-Content-Type: text/html
-Last-Modified: Mon, 08 Jan 2018 10:29:45 GMT
-
-<!doctype html>
-... the rest of the document
- -

The browser takes the part of the response after the blank line, its body (not to be confused with the HTML <body> tag), and displays it as an HTML document.

- -

The information sent by the client is called the request. It starts with this line:

- -
GET /18_http.html HTTP/1.1
- -

The first word is the method of the request. GET means that we want to get the specified resource. Other common methods are DELETE to delete a resource, PUT to create or replace it, and POST to send information to it. Note that the server is not obliged to carry out every request it gets. If you walk up to a random website and tell it to DELETE its main page, it’ll probably refuse.

- -

The part after the method name is the path of the resource the request applies to. In the simplest case, a resource is simply a file on the server, but the protocol doesn’t require it to be. A resource may be anything that can be transferred as if it is a file. Many servers generate the responses they produce on the fly. For example, if you open https://github.com/marijnh, the server looks in its database for a user named “marijnh”, and if it finds one, it will generate a profile page for that user.

- -

After the resource path, the first line of the request mentions HTTP/1.1 to indicate the version of the HTTP protocol it is using.

- -

In practice, many sites use HTTP version 2, which supports the same concepts as version 1.1 but is a lot more complicated so that it can be faster. Browsers will automatically switch to the appropriate protocol version when talking to a given server, and the outcome of a request is the same regardless of which version is used. Because version 1.1 is more straightforward and easier to play around with, we’ll focus on that.

- -

The server’s response will start with a version as well, followed by the status of the response, first as a three-digit status code and then as a human-readable string.

- -
HTTP/1.1 200 OK
- -

Status codes starting with a 2 indicate that the request succeeded. Codes starting with 4 mean there was something wrong with the request. 404 is probably the most famous HTTP status code—it means that the resource could not be found. Codes that start with 5 mean an error happened on the server and the request is not to blame.

- -

The first line of a request or response may be followed by any number of headers. These are lines in the form name: value that specify extra information about the request or response. These headers were part of the example response:

- -
Content-Length: 65585
-Content-Type: text/html
-Last-Modified: Thu, 04 Jan 2018 14:05:30 GMT
- -

This tells us the size and type of the response document. In this case, it is an HTML document of 65,585 bytes. It also tells us when that document was last modified.

- -

For most headers, the client and server are free to decide whether to include them in a request or response. But a few are required. For example, the Host header, which specifies the hostname, should be included in a request because a server might be serving multiple hostnames on a single IP address, and without that header, the server won’t know which hostname the client is trying to talk to.

- -

After the headers, both requests and responses may include a blank line followed by a body, which contains the data being sent. GET and DELETE requests don’t send along any data, but PUT and POST requests do. Similarly, some response types, such as error responses, do not require a body.

- -

Browsers and HTTP

- -

As we saw in the example, a browser will make a request when we enter a URL in its address bar. When the resulting HTML page references other files, such as images and JavaScript files, those are also retrieved.

- -

A moderately complicated website can easily include anywhere from 10 to 200 resources. To be able to fetch those quickly, browsers will make several GET requests simultaneously, rather than waiting for the responses one at a time.

- -

HTML pages may include forms, which allow the user to fill out information and send it to the server. This is an example of a form:

- -
<form method="GET" action="example/message.html">
-  <p>Name: <input type="text" name="name"></p>
-  <p>Message:<br><textarea name="message"></textarea></p>
-  <p><button type="submit">Send</button></p>
-</form>
- -

This code describes a form with two fields: a small one asking for a name and a larger one to write a message in. When you click the Send button, the form is submitted, meaning that the content of its field is packed into an HTTP request and the browser navigates to the result of that request.

- -

When the <form> element’s method attribute is GET (or is omitted), the information in the form is added to the end of the action URL as a query string. The browser might make a request to this URL:

- -
GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1
- -

The question mark indicates the end of the path part of the URL and the start of the query. It is followed by pairs of names and values, corresponding to the name attribute on the form field elements and the content of those elements, respectively. An ampersand character (&) is used to separate the pairs.

- -

The actual message encoded in the URL is “Yes?”, but the question mark is replaced by a strange code. Some characters in query strings must be escaped. The question mark, represented as %3F, is one of those. There seems to be an unwritten rule that every format needs its own way of escaping characters. This one, called URL encoding, uses a percent sign followed by two hexadecimal (base 16) digits that encode the character code. In this case, 3F, which is 63 in decimal notation, is the code of a question mark character. JavaScript provides the encodeURIComponent and decodeURIComponent functions to encode and decode this format.

- -
console.log(encodeURIComponent("Yes?"));
-// → Yes%3F
-console.log(decodeURIComponent("Yes%3F"));
-// → Yes?
- -

If we change the method attribute of the HTML form in the example we saw earlier to POST, the HTTP request made to submit the form will use the POST method and put the query string in the body of the request, rather than adding it to the URL.

- -
POST /example/message.html HTTP/1.1
-Content-length: 24
-Content-type: application/x-www-form-urlencoded
-
-name=Jean&message=Yes%3F
- -

GET requests should be used for requests that do not have side -effects but simply ask for information. Requests that change something on the server, for example creating a new account or posting a message, should be expressed with other methods, such as POST. Client-side software such as a browser knows that it shouldn’t blindly make POST requests but will often implicitly make GET requests—for example to prefetch a resource it believes the user will soon need.

- -

We’ll come back to forms and how to interact with them from JavaScript later in the chapter.

- -

Fetch

- -

The interface through which browser JavaScript can make HTTP requests is called fetch. Since it is relatively new, it conveniently uses promises (which is rare for browser interfaces).

- -
fetch("example/data.txt").then(response => {
-  console.log(response.status);
-  // → 200
-  console.log(response.headers.get("Content-Type"));
-  // → text/plain
-});
- -

Calling fetch returns a promise that resolves to a Response object holding information about the server’s response, such as its status code and its headers. The headers are wrapped in a Map-like object that treats its keys (the header names) as case insensitive because header names are not supposed to be case sensitive. This means headers.get("Content-Type") and headers.get("content-TYPE") will return the same value.

- -

Note that the promise returned by fetch resolves successfully even if the server responded with an error code. It might also be rejected if there is a network error or if the server that the request is addressed to can’t be found.

- -

The first argument to fetch is the URL that should be requested. When that URL doesn’t start with a protocol name (such as http:), it is treated as relative, which means it is interpreted relative to the current document. When it starts with a slash (/), it replaces the current path, which is the part after the server name. When it does not, the part of the current path up to and including its last slash character is put in front of the relative URL.

- -

To get at the actual content of a response, you can use its text method. Because the initial promise is resolved as soon as the response’s headers have been received and because reading the response body might take a while longer, this again returns a promise.

- -
fetch("example/data.txt")
-  .then(resp => resp.text())
-  .then(text => console.log(text));
-// → This is the content of data.txt
- -

A similar method, called json, returns a promise that resolves to the value you get when parsing the body as JSON or rejects if it’s not valid JSON.

- -

By default, fetch uses the GET method to make its request and does not include a request body. You can configure it differently by passing an object with extra options as a second argument. For example, this request tries to delete example/data.txt:

- -
fetch("example/data.txt", {method: "DELETE"}).then(resp => {
-  console.log(resp.status);
-  // → 405
-});
- -

The 405 status code means “method not allowed”, an HTTP server’s way of saying “I can’t do that”.

- -

To add a request body, you can include a body option. To set headers, there’s the headers option. For example, this request includes a Range header, which instructs the server to return only part of a response.

- -
fetch("example/data.txt", {headers: {Range: "bytes=8-19"}})
-  .then(resp => resp.text())
-  .then(console.log);
-// → the content
- -

The browser will automatically add some request headers, such as “Host” and those needed for the server to figure out the size of the body. But adding your own headers is often useful to include things such as authentication information or to tell the server which file format you’d like to receive.

- -

HTTP sandboxing

- -

Making HTTP requests in web page scripts once again raises concerns about security. The person who controls the script might not have the same interests as the person on whose computer it is running. More specifically, if I visit themafia.org, I do not want its scripts to be able to make a request to mybank.com, using identifying information from my browser, with instructions to transfer all my money to some random account.

- -

For this reason, browsers protect us by disallowing scripts to make HTTP requests to other domains (names such as themafia.org and mybank.com).

- -

This can be an annoying problem when building systems that want to access several domains for legitimate reasons. Fortunately, servers can include a header like this in their response to explicitly indicate to the browser that it is okay for the request to come from another domain:

- -
Access-Control-Allow-Origin: *
- -

Appreciating HTTP

- -

When building a system that requires communication between a JavaScript program running in the browser (client-side) and a program on a server (server-side), there are several different ways to model this communication.

- -

A commonly used model is that of remote procedure calls. In this model, communication follows the patterns of normal function calls, except that the function is actually running on another machine. Calling it involves making a request to the server that includes the function’s name and arguments. The response to that request contains the returned value.

- -

When thinking in terms of remote procedure calls, HTTP is just a vehicle for communication, and you will most likely write an abstraction layer that hides it entirely.

- -

Another approach is to build your communication around the concept of resources and HTTP methods. Instead of a remote procedure called addUser, you use a PUT request to /users/larry. Instead of encoding that user’s properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the PUT request to create a new resource is then such a document. A resource is fetched by making a GET request to the resource’s URL (for example, /user/larry), which again returns the document representing the resource.

- -

This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy on the client for fast access). The concepts used in HTTP, which are well designed, can provide a helpful set of principles to design your server interface around.

- -

Security and HTTPS

- -

Data traveling over the Internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from coffee shop Wi-Fi hotspots to networks controlled by various companies and states. At any point along its route it may be inspected or even modified.

- -

If it is important that something remain secret, such as the password to your email account, or that it arrive at its destination unmodified, such as the account number you transfer money to via your bank’s website, plain HTTP is not good enough.

- -

The secure HTTP protocol, used for URLs starting with https://, wraps HTTP traffic in a way that makes it harder to read and tamper with. Before exchanging data, the client verifies that the server is who it claims to be by asking it to prove that it has a cryptographic certificate issued by a certificate authority that the browser recognizes. Next, all data going over the connection is encrypted in a way that should prevent eavesdropping and tampering.

- -

Thus, when it works right, HTTPS prevents other people from impersonating the website you are trying to talk to and from snooping on your communication. It is not perfect, and there have been various incidents where HTTPS failed because of forged or stolen certificates and broken software, but it is a lot safer than plain HTTP.

- -

Form fields

- -

Forms were originally designed for the pre-JavaScript Web to allow web sites to send user-submitted information in an HTTP request. This design assumes that interaction with the server always happens by navigating to a new page.

- -

But their elements are part of the DOM like the rest of the page, and the DOM elements that represent form fields support a number of properties and events that are not present on other elements. These make it possible to inspect and control such input fields with JavaScript programs and do things such as adding new functionality to a form or using forms and fields as building blocks in a JavaScript application.

- -

A web form consists of any number of input fields grouped in a <form> tag. HTML allows several different styles of fields, ranging from simple on/off checkboxes to drop-down menus and fields for text input. This book won’t try to comprehensively discuss all field types, but we’ll start with a rough overview.

- -

A lot of field types use the <input> tag. This tag’s type attribute is used to select the field’s style. These are some commonly used <input> types:

- - - - - - - - - - - - - - - - - - - - - - - -
textA single-line text field
passwordSame as text but hides the text that is typed
checkboxAn on/off switch
radio(Part of) a multiple-choice field
fileAllows the user to choose a file from their computer
- -

Form fields do not necessarily have to appear in a <form> tag. You can put them anywhere in a page. Such form-less fields cannot be submitted (only a form as a whole can), but when responding to input with JavaScript, we often don’t want to submit our fields normally anyway.

- -
<p><input type="text" value="abc"> (text)</p>
-<p><input type="password" value="abc"> (password)</p>
-<p><input type="checkbox" checked> (checkbox)</p>
-<p><input type="radio" value="A" name="choice">
-   <input type="radio" value="B" name="choice" checked>
-   <input type="radio" value="C" name="choice"> (radio)</p>
-<p><input type="file"> (file)</p>
- -

The JavaScript interface for such elements differs with the type of the element.

- -

Multiline text fields have their own tag, <textarea>, mostly because using an attribute to specify a multiline starting value would be awkward. The <textarea> tag requires a matching </textarea> closing tag and uses the text between those two, instead of the value attribute, as starting text.

- -
<textarea>
-one
-two
-three
-</textarea>
- -

Finally, the <select> tag is used to create a field that allows the user to select from a number of predefined options.

- -
<select>
-  <option>Pancakes</option>
-  <option>Pudding</option>
-  <option>Ice cream</option>
-</select>
- -

Whenever the value of a form field changes, it will fire a "change" event.

- -

Focus

- -

Unlike most elements in HTML documents, form fields can get keyboard focus. When clicked or activated in some other way, they become the currently active element and the recipient of keyboard input.

- -

Thus, you can type into a text field only when it is focused. Other fields respond differently to keyboard events. For example, a <select> menu tries to move to the option that contains the text the user typed and responds to the arrow keys by moving its selection up and down.

- -

We can control focus from JavaScript with the focus and blur methods. The first moves focus to the DOM element it is called on, and the second removes focus. The value in document.activeElement corresponds to the currently focused element.

- -
<input type="text">
-<script>
-  document.querySelector("input").focus();
-  console.log(document.activeElement.tagName);
-  // → INPUT
-  document.querySelector("input").blur();
-  console.log(document.activeElement.tagName);
-  // → BODY
-</script>
- -

For some pages, the user is expected to want to interact with a form field immediately. JavaScript can be used to focus this field when the document is loaded, but HTML also provides the autofocus attribute, which produces the same effect while letting the browser know what we are trying to achieve. This gives the browser the option to disable the behavior when it is not appropriate, such as when the user has put the focus on something else.

- -

Browsers traditionally also allow the user to move the focus through the document by pressing the tab key. We can influence the order in which elements receive focus with the tabindex attribute. The following example document will let the focus jump from the text input to the OK button, rather than going through the help link first:

- -
<input type="text" tabindex=1> <a href=".">(help)</a>
-<button onclick="console.log('ok')" tabindex=2>OK</button>
- -

By default, most types of HTML elements cannot be focused. But you can add a tabindex attribute to any element that will make it focusable. A tabindex of -1 makes tabbing skip over an element, even if it is normally focusable.

- -

Disabled fields

- -

All form fields can be disabled through their disabled attribute. It is an attribute that can be specified without value—the fact that it is present at all disables the element.

- -
<button>I'm all right</button>
-<button disabled>I'm out</button>
- -

Disabled fields cannot be focused or changed, and browsers make them look gray and faded.

- -

When a program is in the process of handling an action caused by some button or other control that might require communication with the server and thus take a while, it can be a good idea to disable the control until the action finishes. That way, when the user gets impatient and clicks it again, they don’t accidentally repeat their action.

- -

The form as a whole

- -

When a field is contained in a <form> element, its DOM element will have a form property linking back to the form’s DOM element. The <form> element, in turn, has a property called elements that contains an array-like collection of the fields inside it.

- -

The name attribute of a form field determines the way its value will be identified when the form is submitted. It can also be used as a property name when accessing the form’s elements property, which acts both as an array-like object (accessible by number) and a map (accessible by name).

- -
<form action="example/submit.html">
-  Name: <input type="text" name="name"><br>
-  Password: <input type="password" name="password"><br>
-  <button type="submit">Log in</button>
-</form>
-<script>
-  let form = document.querySelector("form");
-  console.log(form.elements[1].type);
-  // → password
-  console.log(form.elements.password.type);
-  // → password
-  console.log(form.elements.name.form == form);
-  // → true
-</script>
- -

A button with a type attribute of submit will, when pressed, cause the form to be submitted. Pressing enter when a form field is focused has the same effect.

- -

Submitting a form normally means that the browser navigates to the page indicated by the form’s action attribute, using either a GET or a POST request. But before that happens, a "submit" event is fired. You can handle this event with JavaScript and prevent this default behavior by calling preventDefault on the event object.

- -
<form action="example/submit.html">
-  Value: <input type="text" name="value">
-  <button type="submit">Save</button>
-</form>
-<script>
-  let form = document.querySelector("form");
-  form.addEventListener("submit", event => {
-    console.log("Saving value", form.elements.value.value);
-    event.preventDefault();
-  });
-</script>
- -

Intercepting "submit" events in JavaScript has various uses. We can write code to verify that the values the user entered make sense and immediately show an error message instead of submitting the form. Or we can disable the regular way of submitting the form entirely, as in the example, and have our program handle the input, possibly using fetch to send it to a server without reloading the page.

- -

Text fields

- -

Fields created by <textarea> tags, or <input> tags with a type of text or password, share a common interface. Their DOM elements have a value property that holds their current content as a string value. Setting this property to another string changes the field’s content.

- -

The selectionStart and selectionEnd properties of text fields give us information about the cursor and selection in the text. When nothing is selected, these two properties hold the same number, indicating the position of the cursor. For example, 0 indicates the start of the text, and 10 indicates the cursor is after the 10th character. When part of the field is selected, the two properties will differ, giving us the start and end of the selected text. Like value, these properties may also be written to.

- -

Imagine you are writing an article about Khasekhemwy but have some trouble spelling his name. The following code wires up a <textarea> tag with an event handler that, when you press F2, inserts the string “Khasekhemwy” for you.

- -
<textarea></textarea>
-<script>
-  let textarea = document.querySelector("textarea");
-  textarea.addEventListener("keydown", event => {
-    // The key code for F2 happens to be 113
-    if (event.keyCode == 113) {
-      replaceSelection(textarea, "Khasekhemwy");
-      event.preventDefault();
-    }
-  });
-  function replaceSelection(field, word) {
-    let from = field.selectionStart, to = field.selectionEnd;
-    field.value = field.value.slice(0, from) + word +
-                  field.value.slice(to);
-    // Put the cursor after the word
-    field.selectionStart = from + word.length;
-    field.selectionEnd = from + word.length;
-  }
-</script>
- -

The replaceSelection function replaces the currently selected part of a text field’s content with the given word and then moves the cursor after that word so that the user can continue typing.

- -

The "change" event for a text -field does not fire every time something is typed. Rather, it fires when the field loses focus after its content was changed. To respond immediately to changes in a text field, you should register a handler for the "input" event instead, which fires for every time the user types a character, deletes text, or otherwise manipulates the field’s content.

- -

The following example shows a text field and a counter displaying the current length of the text in the field:

- -
<input type="text"> length: <span id="length">0</span>
-<script>
-  let text = document.querySelector("input");
-  let output = document.querySelector("#length");
-  text.addEventListener("input", () => {
-    output.textContent = text.value.length;
-  });
-</script>
- -

Checkboxes and radio buttons

- -

A checkbox field is a binary toggle. Its value can be extracted or changed through its checked property, which holds a Boolean value.

- -
<label>
-  <input type="checkbox" id="purple"> Make this page purple
-</label>
-<script>
-  let checkbox = document.querySelector("#purple");
-  checkbox.addEventListener("change", () => {
-    document.body.style.background =
-      checkbox.checked ? "mediumpurple" : "";
-  });
-</script>
- -

The <label> tag associates a piece of document with an input field. Clicking anywhere on the label will activate the field, which focuses it and toggles its value when it is a checkbox or radio button.

- -

A radio button is similar to a checkbox, but it’s implicitly linked to other radio buttons with the same name attribute so that only one of them can be active at any time.

- -
Color:
-<label>
-  <input type="radio" name="color" value="orange"> Orange
-</label>
-<label>
-  <input type="radio" name="color" value="lightgreen"> Green
-</label>
-<label>
-  <input type="radio" name="color" value="lightblue"> Blue
-</label>
-<script>
-  let buttons = document.querySelectorAll("[name=color]");
-  for (let button of Array.from(buttons)) {
-    button.addEventListener("change", () => {
-      document.body.style.background = button.value;
-    });
-  }
-</script>
- -

The square brackets in the CSS query given to querySelectorAll are used to match attributes. It selects elements whose name attribute is "color".

- -

Select fields

- -

Select fields are conceptually similar to radio buttons—they also allow the user to choose from a set of options. But where a radio button puts the layout of the options under our control, the appearance of a <select> tag is determined by the browser.

- -

Select fields also have a variant that is more akin to a list of checkboxes, rather than radio boxes. When given the multiple attribute, a <select> tag will allow the user to select any number of options, rather than just a single option. This will, in most browsers, show up differently than a normal select field, which is typically drawn as a drop-down control that shows the options only when you open it.

- -

Each <option> tag has a value. This value can be defined with a value attribute. When that is not given, the text inside the option will count as its value. The value property of a <select> element reflects the currently selected option. For a multiple field, though, this property doesn’t mean much since it will give the value of only one of the currently selected options.

- -

The <option> tags for a <select> field can be accessed as an array-like object through the field’s options property. Each option has a property called selected, which indicates whether that option is currently selected. The property can also be written to select or deselect an option.

- -

This example extracts the selected values from a multiple select field and uses them to compose a binary number from individual bits. Hold control (or command on a Mac) to select multiple options.

- -
<select multiple>
-  <option value="1">0001</option>
-  <option value="2">0010</option>
-  <option value="4">0100</option>
-  <option value="8">1000</option>
-</select> = <span id="output">0</span>
-<script>
-  let select = document.querySelector("select");
-  let output = document.querySelector("#output");
-  select.addEventListener("change", () => {
-    let number = 0;
-    for (let option of Array.from(select.options)) {
-      if (option.selected) {
-        number += Number(option.value);
-      }
-    }
-    output.textContent = number;
-  });
-</script>
- -

File fields

- -

File fields were originally designed as a way to upload files from the user’s machine through a form. In modern browsers, they also provide a way to read such files from JavaScript programs. The field acts as a kind of gatekeeper. The script cannot simply start reading private files from the user’s computer, but if the user selects a file in such a field, the browser interprets that action to mean that the script may read the file.

- -

A file field usually looks like a button labeled with something like “choose file” or “browse”, with information about the chosen file next to it.

- -
<input type="file">
-<script>
-  let input = document.querySelector("input");
-  input.addEventListener("change", () => {
-    if (input.files.length > 0) {
-      let file = input.files[0];
-      console.log("You chose", file.name);
-      if (file.type) console.log("It has type", file.type);
-    }
-  });
-</script>
- -

The files property of a file field element is an array-like object (again, not a real array) containing the files chosen in the field. It is initially empty. The reason there isn’t simply a file property is that file fields also support a multiple attribute, which makes it possible to select multiple files at the same time.

- -

Objects in the files object have properties such as name (the filename), size (the file’s size in bytes, which are chunks of 8 bits), and type (the media type of the file, such as text/plain or image/jpeg).

- -

What it does not have is a property that contains the content of the file. Getting at that is a little more involved. Since reading a file from disk can take time, the interface must be asynchronous to avoid freezing the document.

- -
<input type="file" multiple>
-<script>
-  let input = document.querySelector("input");
-  input.addEventListener("change", () => {
-    for (let file of Array.from(input.files)) {
-      let reader = new FileReader();
-      reader.addEventListener("load", () => {
-        console.log("File", file.name, "starts with",
-                    reader.result.slice(0, 20));
-      });
-      reader.readAsText(file);
-    }
-  });
-</script>
- -

Reading a file is done by creating a FileReader object, registering a "load" event handler for it, and calling its readAsText method, giving it the file we want to read. Once loading finishes, the reader’s result property contains the file’s content.

- -

FileReaders also fire an "error" event when reading the file fails for any reason. The error object itself will end up in the reader’s error property. This interface was designed before promises became part of the language. You could wrap it in a promise like this:

- -
function readFileText(file) {
-  return new Promise((resolve, reject) => {
-    let reader = new FileReader();
-    reader.addEventListener(
-      "load", () => resolve(reader.result));
-    reader.addEventListener(
-      "error", () => reject(reader.error));
-    reader.readAsText(file);
-  });
-}
- -

Storing data client-side

- -

Simple HTML pages with a bit of JavaScript can be a great format for “mini applications”—small helper programs that automate basic tasks. By connecting a few form fields with event handlers, you can do anything from converting between centimeters and inches to computing passwords from a master password and a website name.

- -

When such an application needs to remember something between sessions, you cannot use JavaScript bindings—those are thrown away every time the page is closed. You could set up a server, connect it to the Internet, and have your application store something there. We will see how to do that in Chapter 20. But that’s a lot of extra work and complexity. Sometimes it is enough to just keep the data in the browser.

- -

The localStorage object can be used to store data in a way that survives page reloads. This object allows you to file string values under names.

- -
localStorage.setItem("username", "marijn");
-console.log(localStorage.getItem("username"));
-// → marijn
-localStorage.removeItem("username");
- -

A value in localStorage sticks around until it is overwritten, it is removed with removeItem, or the user clears their local data.

- -

Sites from different domains get different storage compartments. That means data stored in localStorage by a given website can, in principle, be read (and overwritten) only by scripts on that same site.

- -

Browsers do enforce a limit on the size of the data a site can store in localStorage. That restriction, along with the fact that filling up people’s hard drives with junk is not really profitable, prevents the feature from eating up too much space.

- -

The following code implements a crude note-taking application. It keeps a set of named notes and allows the user to edit notes and create new ones.

- -
Notes: <select></select> <button>Add</button><br>
-<textarea style="width: 100%"></textarea>
-
-<script>
-  let list = document.querySelector("select");
-  let note = document.querySelector("textarea");
-
-  let state;
-  function setState(newState) {
-    list.textContent = "";
-    for (let name of Object.keys(newState.notes)) {
-      let option = document.createElement("option");
-      option.textContent = name;
-      if (newState.selected == name) option.selected = true;
-      list.appendChild(option);
-    }
-    note.value = newState.notes[newState.selected];
-
-    localStorage.setItem("Notes", JSON.stringify(newState));
-    state = newState;
-  }
-  setState(JSON.parse(localStorage.getItem("Notes")) || {
-    notes: {"shopping list": "Carrots\nRaisins"},
-    selected: "shopping list"
-  });
-
-  list.addEventListener("change", () => {
-    setState({notes: state.notes, selected: list.value});
-  });
-  note.addEventListener("change", () => {
-    setState({
-      notes: Object.assign({}, state.notes,
-                           {[state.selected]: note.value}),
-      selected: state.selected
-    });
-  });
-  document.querySelector("button")
-    .addEventListener("click", () => {
-      let name = prompt("Note name");
-      if (name) setState({
-        notes: Object.assign({}, state.notes, {[name]: ""}),
-        selected: name
-      });
-    });
-</script>
- -

The script gets its starting state from the "Notes" value stored in localStorage or, if that is missing, creates an example state that has only a shopping list in it. Reading a field that does not exist from localStorage will yield null. Passing null to JSON.parse will make it parse the string "null" and return null. Thus, the || operator can be used to provide a default value in a situation like this.

- -

The setState method makes sure the DOM is showing a given state and stores the new state to localStorage. Event handlers call this function to move to a new state.

- -

The use of Object.assign in the example is intended to create a new object that is a clone of the old state.notes, but with one property added or overwritten. Object.assign takes its first argument and adds all properties from any further arguments to it. Thus, giving it an empty object will cause it to fill a fresh object. The square -brackets notation in the third argument is used to create a property whose name is based on some dynamic value.

- -

There is another object, similar to localStorage, called sessionStorage. The difference between the two is that the content of sessionStorage is forgotten at the end of each session, which for most browsers means whenever the browser is closed.

- -

Summary

- -

In this chapter, we discussed how the HTTP protocol works. A client sends a request, which contains a method (usually GET) and a path that identifies a resource. The server then decides what to do with the request and responds with a status code and a response body. Both requests and responses may contain headers that provide additional information.

- -

The interface through which browser JavaScript can make HTTP requests is called fetch. Making a request looks like this:

- -
fetch("/18_http.html").then(r => r.text()).then(text => {
-  console.log(`The page starts with ${text.slice(0, 15)}`);
-});
- -

Browsers make GET requests to fetch the resources needed to display a web page. A page may also contain forms, which allow information entered by the user to be sent as a request for a new page when the form is submitted.

- -

HTML can represent various types of form fields, such as text fields, checkboxes, multiple-choice fields, and file pickers.

- -

Such fields can be inspected and manipulated with JavaScript. They fire the "change" event when changed, fire the "input" event when text is typed, and receive keyboard events when they have keyboard focus. Properties like value (for text and select fields) or checked (for checkboxes and radio buttons) are used to read or set the field’s content.

- -

When a form is submitted, a "submit" event is fired on it. A JavaScript handler can call preventDefault on that event to disable the browser’s default behavior. Form field elements may also occur outside of a form tag.

- -

When the user has selected a file from their local file system in a file picker field, the FileReader interface can be used to access the content of this file from a JavaScript program.

- -

The localStorage and sessionStorage objects can be used to save information in a way that survives page reloads. The first object saves the data forever (or until the user decides to clear it), and the second saves it until the browser is closed.

- -

Exercises

- -

Content negotiation

- -

One of the things HTTP can do is called content negotiation. The Accept request header is used to tell the server what type of document the client would like to get. Many servers ignore this header, but when a server knows of various ways to encode a resource, it can look at this header and send the one that the client prefers.

- -

The URL https://eloquentjavascript.net/author is configured to respond with either plaintext, HTML, or JSON, depending on what the client asks for. These formats are identified by the standardized media types text/plain, text/html, and application/json.

- -

Send requests to fetch all three formats of this resource. Use the headers property in the options object passed to fetch to set the header named Accept to the desired media type.

- -

Finally, try asking for the media type application/rainbows+unicorns and see which status code that produces.

- -
// Your code here.
- -
- -

Base your code on the fetch examples earlier in the chapter.

- -

Asking for a bogus media type will return a response with code 406, “Not acceptable”, which is the code a server should return when it can’t fulfill the Accept header.

- -
- -

A JavaScript workbench

- -

Build an interface that allows people to type and run pieces of JavaScript code.

- -

Put a button next to a <textarea> field that, when pressed, uses the Function constructor we saw in Chapter 10 to wrap the text in a function and call it. Convert the return value of the function, or any error it raises, to a string and display it below the text field.

- -
<textarea id="code">return "hi";</textarea>
-<button id="button">Run</button>
-<pre id="output"></pre>
-
-<script>
-  // Your code here.
-</script>
- -
- -

Use document.querySelector or document.getElementById to get access to the elements defined in your HTML. An event handler for "click" or "mousedown" events on the button can get the value property of the text field and call Function on it.

- -

Make sure you wrap both the call to Function and the call to its result in a try block so you can catch the exceptions it produces. In this case, we really don’t know what type of exception we are looking for, so catch everything.

- -

The textContent property of the output element can be used to fill it with a string message. Or, if you want to keep the old content around, create a new text node using document.createTextNode and append it to the element. Remember to add a newline character to the end so that not all output appears on a single line.

- -
- -

Conway’s Game of Life

- -

Conway’s Game of Life is a simple simulation that creates artificial “life” on a grid, each cell of which is either alive or not. Each generation (turn), the following rules are applied:

- -
    - -
  • - -

    Any live cell with fewer than two or more than three live neighbors dies.

  • - -
  • - -

    Any live cell with two or three live neighbors lives on to the next generation.

  • - -
  • - -

    Any dead cell with exactly three live neighbors becomes a live cell.

- -

A neighbor is defined as any adjacent cell, including diagonally adjacent ones.

- -

Note that these rules are applied to the whole grid at once, not one square at a time. That means the counting of neighbors is based on the situation at the start of the generation, and changes happening to neighbor cells during this generation should not influence the new state of a given cell.

- -

Implement this game using whichever data structure you find appropriate. Use Math.random to populate the grid with a random pattern initially. Display it as a grid of checkbox fields, with a button next to it to advance to the next generation. When the user checks or unchecks the checkboxes, their changes should be included when computing the next generation.

- -
<div id="grid"></div>
-<button id="next">Next generation</button>
-
-<script>
-  // Your code here.
-</script>
- -
- -

To solve the problem of having the changes conceptually happen at the same time, try to see the computation of a generation as a pure -function, which takes one grid and produces a new grid that represents the next turn.

- -

Representing the matrix can be done in the way shown in Chapter 6. You can count live neighbors with two nested loops, looping over adjacent coordinates in both dimensions. Take care not to count cells outside of the field and to ignore the cell in the center, whose neighbors we are counting.

- -

Ensuring that changes to checkboxes take effect on the next generation can be done in two ways. An event handler could notice these changes and update the current grid to reflect them, or you could generate a fresh grid from the values in the checkboxes before computing the next turn.

- -

If you choose to go with event handlers, you might want to attach attributes that identify the position that each checkbox corresponds to so that it is easy to find out which cell to change.

- -

To draw the grid of checkboxes, you can either use a <table> element (see Chapter 14) or simply put them all in the same element and put <br> (line break) elements between the rows.

- -
-
diff --git a/docs/19_paint.html b/docs/19_paint.html deleted file mode 100644 index 6cae4a0b5..000000000 --- a/docs/19_paint.html +++ /dev/null @@ -1,767 +0,0 @@ - - - - - Project: A Pixel Art Editor :: Eloquent JavaScript - - - - - - -
- - -

Chapter 19Project: A Pixel Art Editor

- -
- -

I look at the many colors before me. I look at my blank canvas. Then, I try to apply colors like words that shape poems, like notes that shape music.

- -
Joan Miro
- -
Picture of a tiled mosaic
- -

The material from the previous chapters gives you all the elements you need to build a basic web application. In this chapter, we will do just that.

- -

Our application will be a pixel drawing program, where you can modify a picture pixel by pixel by manipulating a zoomed-in view of it, shown as a grid of colored squares. You can use the program to open image files, scribble on them with your mouse or other pointer device, and save them. This is what it will look like:

The pixel editor interface, with colored pixels at the top and a number of controls below that
- -

Painting on a computer is great. You don’t need to worry about materials, skill, or talent. You just start smearing.

- -

Components

- -

The interface for the application shows a big <canvas> element on top, with a number of form fields below it. The user draws on the picture by selecting a tool from a <select> field and then clicking, touching, or dragging across the canvas. There are tools for drawing single pixels or rectangles, for filling an area, and for picking a color from the picture.

- -

We will structure the editor interface as a number of components, objects that are responsible for a piece of the DOM and that may contain other components inside them.

- -

The state of the application consists of the current picture, the selected tool, and the selected color. We’ll set things up so that the state lives in a single value, and the interface components always base the way they look on the current state.

- -

To see why this is important, let’s consider the alternative—distributing pieces of state throughout the interface. Up to a certain point, this is easier to program. We can just put in a color field and read its value when we need to know the current color.

- -

But then we add the color picker—a tool that lets you click the picture to select the color of a given pixel. To keep the color field showing the correct color, that tool would have to know that it exists and update it whenever it picks a new color. If you ever add another place that makes the color visible (maybe the mouse cursor could show it), you have to update your color-changing code to keep that synchronized.

- -

In effect, this creates a problem where each part of the interface needs to know about all other parts, which is not very modular. For small applications like the one in this chapter, that may not be a problem. For bigger projects, it can turn into a real nightmare.

- -

To avoid this nightmare on principle, we’re going to be strict about data flow. There is a state, and the interface is drawn based on that state. An interface component may respond to user actions by updating the state, at which point the components get a chance to synchronize themselves with this new state.

- -

In practice, each component is set up so that when it is given a new state, it also notifies its child components, insofar as those need to be updated. Setting this up is a bit of a hassle. Making this more convenient is the main selling point of many browser programming libraries. But for a small application like this, we can do it without such infrastructure.

- -

Updates to the state are represented as objects, which we’ll call actions. Components may create such actions and dispatch them—give them to a central state management function. That function computes the next state, after which the interface components update themselves to this new state.

- -

We’re taking the messy task of running a user interface and applying some structure to it. Though the DOM-related pieces are still full of side effects, they are held up by a conceptually simple backbone: the state update cycle. The state determines what the DOM looks like, and the only way DOM events can change the state is by dispatching actions to the state.

- -

There are many variants of this approach, each with its own benefits and problems, but their central idea is the same: state changes should go through a single well-defined channel, not happen all over the place.

- -

Our components will be classes conforming to an interface. Their constructor is given a state—which may be the whole application state or some smaller value if it doesn’t need access to everything—and uses that to build up a dom property. This is the DOM element that represents the component. Most constructors will also take some other values that won’t change over time, such as the function they can use to dispatch an action.

- -

Each component has a syncState method that is used to synchronize it to a new state value. The method takes one argument, the state, which is of the same type as the first argument to its constructor.

- -

The state

- -

The application state will be an object with picture, tool, and color properties. The picture is itself an object that stores the width, height, and pixel content of the picture. The pixels are stored in an array, in the same way as the matrix class from Chapter 6—row by row, from top to bottom.

- -
class Picture {
-  constructor(width, height, pixels) {
-    this.width = width;
-    this.height = height;
-    this.pixels = pixels;
-  }
-  static empty(width, height, color) {
-    let pixels = new Array(width * height).fill(color);
-    return new Picture(width, height, pixels);
-  }
-  pixel(x, y) {
-    return this.pixels[x + y * this.width];
-  }
-  draw(pixels) {
-    let copy = this.pixels.slice();
-    for (let {x, y, color} of pixels) {
-      copy[x + y * this.width] = color;
-    }
-    return new Picture(this.width, this.height, copy);
-  }
-}
- -

We want to be able to treat a picture as an immutable value, for reasons that we’ll get back to later in the chapter. But we also sometimes need to update a whole bunch of pixels at a time. To be able to do that, the class has a draw method that expects an array of updated pixels—objects with x, y, and color properties—and creates a new picture with those pixels overwritten. This method uses slice without arguments to copy the entire pixel array—the start of the slice defaults to 0, and the end defaults to the array’s length.

- -

The empty method uses two pieces of array functionality that we haven’t seen before. The Array constructor can be called with a number to create an empty array of the given length. The fill method can then be used to fill this array with a given value. These are used to create an array in which all pixels have the same color.

- -

Colors are stored as strings containing traditional CSS color -codes made up of a hash sign (#) followed by six hexadecimal (base-16) digits—two for the red component, two for the green component, and two for the blue component. This is a somewhat cryptic and inconvenient way to write colors, but it is the format the HTML color input field uses, and it can be used in the fillColor property of a canvas drawing context, so for the ways we’ll use colors in this program, it is practical enough.

- -

Black, where all components are zero, is written "#000000", and bright pink looks like "#ff00ff", where the red and blue components have the maximum value of 255, written ff in hexadecimal digits (which use a to f to represent digits 10 to 15).

- -

We’ll allow the interface to dispatch actions as objects whose properties overwrite the properties of the previous state. The color field, when the user changes it, could dispatch an object like {color: field.value}, from which this update function can compute a new state.

- -
function updateState(state, action) {
-  return Object.assign({}, state, action);
-}
- -

This rather cumbersome pattern, in which Object.assign is used to first add the properties of state to an empty object and then overwrite some of those with the properties from action, is common in JavaScript code that uses immutable objects. A more convenient notation for this, in which the triple-dot operator is used to include all properties from another object in an object expression, is in the final stages of being standardized. With that addition, you could write {...state, ...action} instead. At the time of writing, this doesn’t yet work in all browsers.

- -

DOM building

- -

One of the main things that interface components do is creating DOM structure. We again don’t want to directly use the verbose DOM methods for that, so here’s a slightly expanded version of the elt function:

- -
function elt(type, props, ...children) {
-  let dom = document.createElement(type);
-  if (props) Object.assign(dom, props);
-  for (let child of children) {
-    if (typeof child != "string") dom.appendChild(child);
-    else dom.appendChild(document.createTextNode(child));
-  }
-  return dom;
-}
- -

The main difference between this version and the one we used in Chapter 16 is that it assigns properties to DOM nodes, not attributes. This means we can’t use it to set arbitrary attributes, but we can use it to set properties whose value isn’t a string, such as onclick, which can be set to a function to register a click event handler.

- -

This allows the following style of registering event handlers:

- -
<body>
-  <script>
-    document.body.appendChild(elt("button", {
-      onclick: () => console.log("click")
-    }, "The button"));
-  </script>
-</body>
- -

The canvas

- -

The first component we’ll define is the part of the interface that displays the picture as a grid of colored boxes. This component is responsible for two things: showing a picture and communicating pointer events on that picture to the rest of the application.

- -

As such, we can define it as a component that knows about only the current picture, not the whole application state. Because it doesn’t know how the application as a whole works, it cannot directly dispatch actions. Rather, when responding to pointer events, it calls a callback function provided by the code that created it, which will handle the application-specific parts.

- -
const scale = 10;
-
-class PictureCanvas {
-  constructor(picture, pointerDown) {
-    this.dom = elt("canvas", {
-      onmousedown: event => this.mouse(event, pointerDown),
-      ontouchstart: event => this.touch(event, pointerDown)
-    });
-    this.syncState(picture);
-  }
-  syncState(picture) {
-    if (this.picture == picture) return;
-    this.picture = picture;
-    drawPicture(this.picture, this.dom, scale);
-  }
-}
- -

We draw each pixel as a 10-by-10 square, as determined by the scale constant. To avoid unnecessary work, the component keeps track of its current picture and does a redraw only when syncState is given a new picture.

- -

The actual drawing function sets the size of the canvas based on the scale and picture size and fills it with a series of squares, one for each pixel.

- -
function drawPicture(picture, canvas, scale) {
-  canvas.width = picture.width * scale;
-  canvas.height = picture.height * scale;
-  let cx = canvas.getContext("2d");
-
-  for (let y = 0; y < picture.height; y++) {
-    for (let x = 0; x < picture.width; x++) {
-      cx.fillStyle = picture.pixel(x, y);
-      cx.fillRect(x * scale, y * scale, scale, scale);
-    }
-  }
-}
- -

When the left mouse button is pressed while the mouse is over the picture canvas, the component calls the pointerDown callback, giving it the position of the pixel that was clicked—in picture coordinates. This will be used to implement mouse interaction with the picture. The callback may return another callback function to be notified when the pointer is moved to a different pixel while the button is held down.

- -
PictureCanvas.prototype.mouse = function(downEvent, onDown) {
-  if (downEvent.button != 0) return;
-  let pos = pointerPosition(downEvent, this.dom);
-  let onMove = onDown(pos);
-  if (!onMove) return;
-  let move = moveEvent => {
-    if (moveEvent.buttons == 0) {
-      this.dom.removeEventListener("mousemove", move);
-    } else {
-      let newPos = pointerPosition(moveEvent, this.dom);
-      if (newPos.x == pos.x && newPos.y == pos.y) return;
-      pos = newPos;
-      onMove(newPos);
-    }
-  };
-  this.dom.addEventListener("mousemove", move);
-};
-
-function pointerPosition(pos, domNode) {
-  let rect = domNode.getBoundingClientRect();
-  return {x: Math.floor((pos.clientX - rect.left) / scale),
-          y: Math.floor((pos.clientY - rect.top) / scale)};
-}
- -

Since we know the size of the pixels and we can use getBoundingClientRect to find the position of the canvas on the screen, it is possible to go from mouse event coordinates (clientX and clientY) to picture coordinates. These are always rounded down so that they refer to a specific pixel.

- -

With touch events, we have to do something similar, but using different events and making sure we call preventDefault on the "touchstart" event to prevent panning.

- -
PictureCanvas.prototype.touch = function(startEvent,
-                                         onDown) {
-  let pos = pointerPosition(startEvent.touches[0], this.dom);
-  let onMove = onDown(pos);
-  startEvent.preventDefault();
-  if (!onMove) return;
-  let move = moveEvent => {
-    let newPos = pointerPosition(moveEvent.touches[0],
-                                 this.dom);
-    if (newPos.x == pos.x && newPos.y == pos.y) return;
-    pos = newPos;
-    onMove(newPos);
-  };
-  let end = () => {
-    this.dom.removeEventListener("touchmove", move);
-    this.dom.removeEventListener("touchend", end);
-  };
-  this.dom.addEventListener("touchmove", move);
-  this.dom.addEventListener("touchend", end);
-};
- -

For touch events, clientX and clientY aren’t available directly on the event object, but we can use the coordinates of the first touch object in the touches property.

- -

The application

- -

To make it possible to build the application piece by piece, we’ll implement the main component as a shell around a picture canvas and a dynamic set of tools and controls that we pass to its constructor.

- -

The controls are the interface elements that appear below the picture. They’ll be provided as an array of component constructors.

- -

The tools do things like drawing pixels or filling in an area. The application shows the set of available tools as a <select> field. The currently selected tool determines what happens when the user interacts with the picture with a pointer device. The set of available tools is provided as an object that maps the names that appear in the drop-down field to functions that implement the tools. Such functions get a picture position, a current application state, and a dispatch function as arguments. They may return a move handler function that gets called with a new position and a current state when the pointer moves to a different pixel.

- -
class PixelEditor {
-  constructor(state, config) {
-    let {tools, controls, dispatch} = config;
-    this.state = state;
-
-    this.canvas = new PictureCanvas(state.picture, pos => {
-      let tool = tools[this.state.tool];
-      let onMove = tool(pos, this.state, dispatch);
-      if (onMove) return pos => onMove(pos, this.state);
-    });
-    this.controls = controls.map(
-      Control => new Control(state, config));
-    this.dom = elt("div", {}, this.canvas.dom, elt("br"),
-                   ...this.controls.reduce(
-                     (a, c) => a.concat(" ", c.dom), []));
-  }
-  syncState(state) {
-    this.state = state;
-    this.canvas.syncState(state.picture);
-    for (let ctrl of this.controls) ctrl.syncState(state);
-  }
-}
- -

The pointer handler given to PictureCanvas calls the currently selected tool with the appropriate arguments and, if that returns a move handler, adapts it to also receive the state.

- -

All controls are constructed and stored in this.controls so that they can be updated when the application state changes. The call to reduce introduces spaces between the controls’ DOM elements. That way they don’t look so pressed together.

- -

The first control is the tool selection menu. It creates a <select> element with an option for each tool and sets up a "change" event handler that updates the application state when the user selects a different tool.

- -
class ToolSelect {
-  constructor(state, {tools, dispatch}) {
-    this.select = elt("select", {
-      onchange: () => dispatch({tool: this.select.value})
-    }, ...Object.keys(tools).map(name => elt("option", {
-      selected: name == state.tool
-    }, name)));
-    this.dom = elt("label", null, "🖌 Tool: ", this.select);
-  }
-  syncState(state) { this.select.value = state.tool; }
-}
- -

By wrapping the label text and the field in a <label> element, we tell the browser that the label belongs to that field so that you can, for example, click the label to focus the field.

- -

We also need to be able to change the color, so let’s add a control for that. An HTML <input> element with a type attribute of color gives us a form field that is specialized for selecting colors. Such a field’s value is always a CSS color code in "#RRGGBB" format (red, green, and blue components, two digits per color). The browser will show a color picker interface when the user interacts with it.

- -

This control creates such a field and wires it up to stay synchronized with the application state’s color property.

- -
class ColorSelect {
-  constructor(state, {dispatch}) {
-    this.input = elt("input", {
-      type: "color",
-      value: state.color,
-      onchange: () => dispatch({color: this.input.value})
-    });
-    this.dom = elt("label", null, "🎨 Color: ", this.input);
-  }
-  syncState(state) { this.input.value = state.color; }
-}
- -

Drawing tools

- -

Before we can draw anything, we need to implement the tools that will control the functionality of mouse or touch events on the canvas.

- -

The most basic tool is the draw tool, which changes any pixel you click or tap to the currently selected color. It dispatches an action that updates the picture to a version in which the pointed-at pixel is given the currently selected color.

- -
function draw(pos, state, dispatch) {
-  function drawPixel({x, y}, state) {
-    let drawn = {x, y, color: state.color};
-    dispatch({picture: state.picture.draw([drawn])});
-  }
-  drawPixel(pos, state);
-  return drawPixel;
-}
- -

The function immediately calls the drawPixel function but then also returns it so that it is called again for newly touched pixels when the user drags or swipes over the picture.

- -

To draw larger shapes, it can be useful to quickly create rectangles. The rectangle tool draws a rectangle between the point where you start dragging and the point that you drag to.

- -
function rectangle(start, state, dispatch) {
-  function drawRectangle(pos) {
-    let xStart = Math.min(start.x, pos.x);
-    let yStart = Math.min(start.y, pos.y);
-    let xEnd = Math.max(start.x, pos.x);
-    let yEnd = Math.max(start.y, pos.y);
-    let drawn = [];
-    for (let y = yStart; y <= yEnd; y++) {
-      for (let x = xStart; x <= xEnd; x++) {
-        drawn.push({x, y, color: state.color});
-      }
-    }
-    dispatch({picture: state.picture.draw(drawn)});
-  }
-  drawRectangle(start);
-  return drawRectangle;
-}
- -

An important detail in this implementation is that when dragging, the rectangle is redrawn on the picture from the original state. That way, you can make the rectangle larger and smaller again while creating it, without the intermediate rectangles sticking around in the final picture. This is one of the reasons why immutable picture objects are useful—we’ll see another reason later.

- -

Implementing flood fill is somewhat more involved. This is a tool that fills the pixel under the pointer and all adjacent pixels that have the same color. “Adjacent” means directly horizontally or vertically adjacent, not diagonally. This picture illustrates the set of pixels colored when the flood fill tool is used at the marked pixel:

A pixel grid showing the area filled by a flood fill operation
- -

Interestingly, the way we’ll do this looks a bit like the pathfinding code from Chapter 7. Whereas that code searched through a graph to find a route, this code searches through a grid to find all “connected” pixels. The problem of keeping track of a branching set of possible routes is similar.

- -
const around = [{dx: -1, dy: 0}, {dx: 1, dy: 0},
-                {dx: 0, dy: -1}, {dx: 0, dy: 1}];
-
-function fill({x, y}, state, dispatch) {
-  let targetColor = state.picture.pixel(x, y);
-  let drawn = [{x, y, color: state.color}];
-  for (let done = 0; done < drawn.length; done++) {
-    for (let {dx, dy} of around) {
-      let x = drawn[done].x + dx, y = drawn[done].y + dy;
-      if (x >= 0 && x < state.picture.width &&
-          y >= 0 && y < state.picture.height &&
-          state.picture.pixel(x, y) == targetColor &&
-          !drawn.some(p => p.x == x && p.y == y)) {
-        drawn.push({x, y, color: state.color});
-      }
-    }
-  }
-  dispatch({picture: state.picture.draw(drawn)});
-}
- -

The array of drawn pixels doubles as the function’s work list. For each pixel reached, we have to see whether any adjacent pixels have the same color and haven’t already been painted over. The loop counter lags behind the length of the drawn array as new pixels are added. Any pixels ahead of it still need to be explored. When it catches up with the length, no unexplored pixels remain, and the function is done.

- -

The final tool is a color picker, which allows you to point at a color in the picture to use it as the current drawing color.

- -
function pick(pos, state, dispatch) {
-  dispatch({color: state.picture.pixel(pos.x, pos.y)});
-}
- -

We can now test our application!

- -
<div></div>
-<script>
-  let state = {
-    tool: "draw",
-    color: "#000000",
-    picture: Picture.empty(60, 30, "#f0f0f0")
-  };
-  let app = new PixelEditor(state, {
-    tools: {draw, fill, rectangle, pick},
-    controls: [ToolSelect, ColorSelect],
-    dispatch(action) {
-      state = updateState(state, action);
-      app.syncState(state);
-    }
-  });
-  document.querySelector("div").appendChild(app.dom);
-</script>
- -

Saving and loading

- -

When we’ve drawn our masterpiece, we’ll want to save it for later. We should add a button for downloading the current picture as an image file. This control provides that button:

- -
class SaveButton {
-  constructor(state) {
-    this.picture = state.picture;
-    this.dom = elt("button", {
-      onclick: () => this.save()
-    }, "💾 Save");
-  }
-  save() {
-    let canvas = elt("canvas");
-    drawPicture(this.picture, canvas, 1);
-    let link = elt("a", {
-      href: canvas.toDataURL(),
-      download: "pixelart.png"
-    });
-    document.body.appendChild(link);
-    link.click();
-    link.remove();
-  }
-  syncState(state) { this.picture = state.picture; }
-}
- -

The component keeps track of the current picture so that it can access it when saving. To create the image file, it uses a <canvas> element that it draws the picture on (at a scale of one pixel per pixel).

- -

The toDataURL method on a canvas element creates a URL that starts with data:. Unlike http: and https: URLs, data URLs contain the whole resource in the URL. They are usually very long, but they allow us to create working links to arbitrary pictures, right here in the browser.

- -

To actually get the browser to download the picture, we then create a link element that points at this URL and has a download attribute. Such links, when clicked, make the browser show a file save dialog. We add that link to the document, simulate a click on it, and remove it again.

- -

You can do a lot with browser technology, but sometimes the way to do it is rather odd.

- -

And it gets worse. We’ll also want to be able to load existing image files into our application. To do that, we again define a button component.

- -
class LoadButton {
-  constructor(_, {dispatch}) {
-    this.dom = elt("button", {
-      onclick: () => startLoad(dispatch)
-    }, "📁 Load");
-  }
-  syncState() {}
-}
-
-function startLoad(dispatch) {
-  let input = elt("input", {
-    type: "file",
-    onchange: () => finishLoad(input.files[0], dispatch)
-  });
-  document.body.appendChild(input);
-  input.click();
-  input.remove();
-}
- -

To get access to a file on the user’s computer, we need the user to select the file through a file input field. But I don’t want the load button to look like a file input field, so we create the file input when the button is clicked and then pretend that this file input itself was clicked.

- -

When the user has selected a file, we can use FileReader to get access to its contents, again as a data URL. That URL can be used to create an <img> element, but because we can’t get direct access to the pixels in such an image, we can’t create a Picture object from that.

- -
function finishLoad(file, dispatch) {
-  if (file == null) return;
-  let reader = new FileReader();
-  reader.addEventListener("load", () => {
-    let image = elt("img", {
-      onload: () => dispatch({
-        picture: pictureFromImage(image)
-      }),
-      src: reader.result
-    });
-  });
-  reader.readAsDataURL(file);
-}
- -

To get access to the pixels, we must first draw the picture to a <canvas> element. The canvas context has a getImageData method that allows a script to read its pixels. So, once the picture is on the canvas, we can access it and construct a Picture object.

- -
function pictureFromImage(image) {
-  let width = Math.min(100, image.width);
-  let height = Math.min(100, image.height);
-  let canvas = elt("canvas", {width, height});
-  let cx = canvas.getContext("2d");
-  cx.drawImage(image, 0, 0);
-  let pixels = [];
-  let {data} = cx.getImageData(0, 0, width, height);
-
-  function hex(n) {
-    return n.toString(16).padStart(2, "0");
-  }
-  for (let i = 0; i < data.length; i += 4) {
-    let [r, g, b] = data.slice(i, i + 3);
-    pixels.push("#" + hex(r) + hex(g) + hex(b));
-  }
-  return new Picture(width, height, pixels);
-}
- -

We’ll limit the size of images to 100 by 100 pixels since anything bigger will look huge on our display and might slow down the interface.

- -

The data property of the object returned by getImageData is an array of color components. For each pixel in the rectangle specified by the arguments, it contains four values, which represent the red, green, blue, and alpha components of the pixel’s color, as numbers between 0 and 255. The alpha part represents opacity—when it is zero, the pixel is fully transparent, and when it is 255, it is fully opaque. For our purpose, we can ignore it.

- -

The two hexadecimal digits per component, as used in our color notation, correspond precisely to the 0 to 255 range—two base-16 digits can express 162 = 256 different numbers. The toString method of numbers can be given a base as argument, so n.toString(16) will produce a string representation in base 16. We have to make sure that each number takes up two digits, so the hex helper function calls padStart to add a leading zero when necessary.

- -

We can load and save now! That leaves one more feature before we’re done.

- -

Undo history

- -

Half of the process of editing is making little mistakes and correcting them. So an important feature in a drawing program is an undo history.

- -

To be able to undo changes, we need to store previous versions of the picture. Since it’s an immutable value, that is easy. But it does require an additional field in the application state.

- -

We’ll add a done array to keep previous versions of the picture. Maintaining this property requires a more complicated state update function that adds pictures to the array.

- -

But we don’t want to store every change, only changes a certain amount of time apart. To be able to do that, we’ll need a second property, doneAt, tracking the time at which we last stored a picture in the history.

- -
function historyUpdateState(state, action) {
-  if (action.undo == true) {
-    if (state.done.length == 0) return state;
-    return Object.assign({}, state, {
-      picture: state.done[0],
-      done: state.done.slice(1),
-      doneAt: 0
-    });
-  } else if (action.picture &&
-             state.doneAt < Date.now() - 1000) {
-    return Object.assign({}, state, action, {
-      done: [state.picture, ...state.done],
-      doneAt: Date.now()
-    });
-  } else {
-    return Object.assign({}, state, action);
-  }
-}
- -

When the action is an undo action, the function takes the most recent picture from the history and makes that the current picture. It sets doneAt to zero so that the next change is guaranteed to store the picture back in the history, allowing you to revert to it another time if you want.

- -

Otherwise, if the action contains a new picture and the last time we stored something is more than a second (1000 milliseconds) ago, the done and doneAt properties are updated to store the previous picture.

- -

The undo button component doesn’t do much. It dispatches undo actions when clicked and disables itself when there is nothing to undo.

- -
class UndoButton {
-  constructor(state, {dispatch}) {
-    this.dom = elt("button", {
-      onclick: () => dispatch({undo: true}),
-      disabled: state.done.length == 0
-    }, "⮪ Undo");
-  }
-  syncState(state) {
-    this.dom.disabled = state.done.length == 0;
-  }
-}
- -

Let’s draw

- -

To set up the application, we need to create a state, a set of tools, a set of controls, and a dispatch function. We can pass them to the PixelEditor constructor to create the main component. Since we’ll need to create several editors in the exercises, we first define some bindings.

- -
const startState = {
-  tool: "draw",
-  color: "#000000",
-  picture: Picture.empty(60, 30, "#f0f0f0"),
-  done: [],
-  doneAt: 0
-};
-
-const baseTools = {draw, fill, rectangle, pick};
-
-const baseControls = [
-  ToolSelect, ColorSelect, SaveButton, LoadButton, UndoButton
-];
-
-function startPixelEditor({state = startState,
-                           tools = baseTools,
-                           controls = baseControls}) {
-  let app = new PixelEditor(state, {
-    tools,
-    controls,
-    dispatch(action) {
-      state = historyUpdateState(state, action);
-      app.syncState(state);
-    }
-  });
-  return app.dom;
-}
- -

When destructuring an object or array, you can use = after a binding name to give the binding a default value, which is used when the property is missing or holds undefined. The startPixelEditor function makes use of this to accept an object with a number of optional properties as an argument. If you don’t provide a tools property, for example, tools will be bound to baseTools.

- -

This is how we get an actual editor on the screen:

- -
<div></div>
-<script>
-  document.querySelector("div")
-    .appendChild(startPixelEditor({}));
-</script>
- -

Go ahead and draw something. I’ll wait.

- -

Why is this so hard?

- -

Browser technology is amazing. It provides a powerful set of interface building blocks, ways to style and manipulate them, and tools to inspect and debug your applications. The software you write for the browser can be run on almost every computer and phone on the planet.

- -

At the same time, browser technology is ridiculous. You have to learn a large number of silly tricks and obscure facts to master it, and the default programming model it provides is so problematic that most programmers prefer to cover it in several layers of abstraction rather than deal with it directly.

- -

And though the situation is definitely improving, it mostly does so in the form of more elements being added to address shortcomings—creating even more complexity. A feature used by a million websites can’t really be replaced. Even if it could, it would be hard to decide what it should be replaced with.

- -

Technology never exists in a vacuum—we’re constrained by our tools and the social, economic, and historical factors that produced them. This can be annoying, but it is generally more productive to try to build a good understanding of how the existing technical reality works—and why it is the way it is—than to rage against it or hold out for another reality.

- -

New abstractions can be helpful. The component model and data -flow convention I used in this chapter is a crude form of that. As mentioned, there are libraries that try to make user interface programming more pleasant. At the time of writing, React and Angular are popular choices, but there’s a whole cottage industry of such frameworks. If you’re interested in programming web applications, I recommend investigating a few of them to understand how they work and what benefits they provide.

- -

Exercises

- -

There is still room for improvement in our program. Let’s add a few more features as exercises.

- -

Keyboard bindings

- -

Add keyboard shortcuts to the application. The first letter of a tool’s name selects the tool, and control-Z or command-Z activates undo.

- -

Do this by modifying the PixelEditor component. Add a tabIndex property of 0 to the wrapping <div> element so that it can receive keyboard focus. Note that the property corresponding to the tabindex attribute is called tabIndex, with a capital I, and our elt function expects property names. Register the key event handlers directly on that element. This means you have to click, touch, or tab to the application before you can interact with it with the keyboard.

- -

Remember that keyboard events have ctrlKey and metaKey (for the command key on Mac) properties that you can use to see whether those keys are held down.

- -
<div></div>
-<script>
-  // The original PixelEditor class. Extend the constructor.
-  class PixelEditor {
-    constructor(state, config) {
-      let {tools, controls, dispatch} = config;
-      this.state = state;
-
-      this.canvas = new PictureCanvas(state.picture, pos => {
-        let tool = tools[this.state.tool];
-        let onMove = tool(pos, this.state, dispatch);
-        if (onMove) {
-          return pos => onMove(pos, this.state, dispatch);
-        }
-      });
-      this.controls = controls.map(
-        Control => new Control(state, config));
-      this.dom = elt("div", {}, this.canvas.dom, elt("br"),
-                     ...this.controls.reduce(
-                       (a, c) => a.concat(" ", c.dom), []));
-    }
-    syncState(state) {
-      this.state = state;
-      this.canvas.syncState(state.picture);
-      for (let ctrl of this.controls) ctrl.syncState(state);
-    }
-  }
-
-  document.querySelector("div")
-    .appendChild(startPixelEditor({}));
-</script>
- -
- -

The key property of events for letter keys will be the lowercase letter itself, if shift isn’t being held. We’re not interested in key events with shift here.

- -

A "keydown" handler can inspect its event object to see whether it matches any of the shortcuts. You can automatically get the list of first letters from the tools object so that you don’t have to write them out.

- -

When the key event matches a shortcut, call preventDefault on it and dispatch the appropriate action.

- -
- -

Efficient drawing

- -

During drawing, the majority of work that our application does happens in drawPicture. Creating a new state and updating the rest of the DOM isn’t very expensive, but repainting all the pixels on the canvas is quite a bit of work.

- -

Find a way to make the syncState method of PictureCanvas faster by redrawing only the pixels that actually changed.

- -

Remember that drawPicture is also used by the save button, so if you change it, either make sure the changes don’t break the old use or create a new version with a different name.

- -

Also note that changing the size of a <canvas> element, by setting its width or height properties, clears it, making it entirely transparent again.

- -
<div></div>
-<script>
-  // Change this method
-  PictureCanvas.prototype.syncState = function(picture) {
-    if (this.picture == picture) return;
-    this.picture = picture;
-    drawPicture(this.picture, this.dom, scale);
-  };
-
-  // You may want to use or change this as well
-  function drawPicture(picture, canvas, scale) {
-    canvas.width = picture.width * scale;
-    canvas.height = picture.height * scale;
-    let cx = canvas.getContext("2d");
-
-    for (let y = 0; y < picture.height; y++) {
-      for (let x = 0; x < picture.width; x++) {
-        cx.fillStyle = picture.pixel(x, y);
-        cx.fillRect(x * scale, y * scale, scale, scale);
-      }
-    }
-  }
-
-  document.querySelector("div")
-    .appendChild(startPixelEditor({}));
-</script>
- -
- -

This exercise is a good example of how immutable data structures can make code faster. Because we have both the old and the new picture, we can compare them and redraw only the pixels that changed color, saving more than 99 percent of the drawing work in most cases.

- -

You can either write a new function updatePicture or have drawPicture take an extra argument, which may be undefined or the previous picture. For each pixel, the function checks whether a previous picture was passed with the same color at this position and skips the pixel when that is the case.

- -

Because the canvas gets cleared when we change its size, you should also avoid touching its width and height properties when the old picture and the new picture have the same size. If they are different, which will happen when a new picture has been loaded, you can set the binding holding the old picture to null after changing the canvas size because you shouldn’t skip any pixels after you’ve changed the canvas size.

- -
- -

Circles

- -

Define a tool called circle that draws a filled circle when you drag. The center of the circle lies at the point where the drag or touch gesture starts, and its radius is determined by the distance dragged.

- -
<div></div>
-<script>
-  function circle(pos, state, dispatch) {
-    // Your code here
-  }
-
-  let dom = startPixelEditor({
-    tools: Object.assign({}, baseTools, {circle})
-  });
-  document.querySelector("div").appendChild(dom);
-</script>
- -
- -

You can take some inspiration from the rectangle tool. Like that tool, you’ll want to keep drawing on the starting picture, rather than the current picture, when the pointer moves.

- -

To figure out which pixels to color, you can use the Pythagorean -theorem. First figure out the distance between the current pointer position and the start position by taking the square root (Math.sqrt) of the sum of the square (Math.pow(x, 2)) of the difference in x-coordinates and the square of the difference in y-coordinates. Then loop over a square of pixels around the start position, whose sides are at least twice the radius, and color those that are within the circle’s radius, again using the Pythagorean formula to figure out their distance from the center.

- -

Make sure you don’t try to color pixels that are outside of the picture’s boundaries.

- -
- -

Proper lines

- -

This is a more advanced exercise than the preceding two, and it will require you to design a solution to a nontrivial problem. Make sure you have plenty of time and patience before starting to work on this exercise, and do not get discouraged by initial failures.

- -

On most browsers, when you select the draw tool and quickly drag across the picture, you don’t get a closed line. Rather, you get dots with gaps between them because the "mousemove" or "touchmove" events did not fire quickly enough to hit every pixel.

- -

Improve the draw tool to make it draw a full line. This means you have to make the motion handler function remember the previous position and connect that to the current one.

- -

To do this, since the pixels can be an arbitrary distance apart, you’ll have to write a general line drawing function.

- -

A line between two pixels is a connected chain of pixels, as straight as possible, going from the start to the end. Diagonally adjacent pixels count as a connected. So a slanted line should look like the picture on the left, not the picture on the right.

Two pixelated lines, one light, skipping across pixels diagonally, and one heavy, with all pixels connected horizontally or vertically
- -

Finally, if we have code that draws a line between two arbitrary points, we might as well use it to also define a line tool, which draws a straight line between the start and end of a drag.

- -
<div></div>
-<script>
-  // The old draw tool. Rewrite this.
-  function draw(pos, state, dispatch) {
-    function drawPixel({x, y}, state) {
-      let drawn = {x, y, color: state.color};
-      dispatch({picture: state.picture.draw([drawn])});
-    }
-    drawPixel(pos, state);
-    return drawPixel;
-  }
-
-  function line(pos, state, dispatch) {
-    // Your code here
-  }
-
-  let dom = startPixelEditor({
-    tools: {draw, line, fill, rectangle, pick}
-  });
-  document.querySelector("div").appendChild(dom);
-</script>
- -
- -

The thing about the problem of drawing a pixelated line is that it is really four similar but slightly different problems. Drawing a horizontal line from the left to the right is easy—you loop over the x-coordinates and color a pixel at every step. If the line has a slight slope (less than 45 degrees or ¼π radians), you can interpolate the y-coordinate along the slope. You still need one pixel per x position, with the y position of those pixels determined by the slope.

- -

But as soon as your slope goes across 45 degrees, you need to switch the way you treat the coordinates. You now need one pixel per y position since the line goes up more than it goes left. And then, when you cross 135 degrees, you have to go back to looping over the x-coordinates, but from right to left.

- -

You don’t actually have to write four loops. Since drawing a line from A to B is the same as drawing a line from B to A, you can swap the start and end positions for lines going from right to left and treat them as going left to right.

- -

So you need two different loops. The first thing your line drawing function should do is check whether the difference between the x-coordinates is larger than the difference between the y-coordinates. If it is, this is a horizontal-ish line, and if not, a vertical-ish one.

- -

Make sure you compare the absolute values of the x and y difference, which you can get with Math.abs.

- -

Once you know along which axis you will be looping, you can check whether the start point has a higher coordinate along that axis than the endpoint and swap them if necessary. A succinct way to swap the values of two bindings in JavaScript uses destructuring assignment like this:

- -
[start, end] = [end, start];
- -

Then you can compute the slope of the line, which determines the amount the coordinate on the other axis changes for each step you take along your main axis. With that, you can run a loop along the main axis while also tracking the corresponding position on the other axis, and you can draw pixels on every iteration. Make sure you round the non-main axis coordinates since they are likely to be fractional and the draw method doesn’t respond well to fractional coordinates.

- -
-
diff --git a/docs/20_node.html b/docs/20_node.html deleted file mode 100644 index 3e748eda1..000000000 --- a/docs/20_node.html +++ /dev/null @@ -1,548 +0,0 @@ - - - - - Node.js :: Eloquent JavaScript - - - - - - -
- - -

Chapter 20Node.js

- -
- -

A student asked, ‘The programmers of old used only simple machines and no programming languages, yet they made beautiful programs. Why do we use complicated machines and programming languages?’. Fu-Tzu replied, ‘The builders of old used only sticks and clay, yet they made beautiful huts.’

- -
Master Yuan-Ma, The Book of Programming
- -
Picture of a telephone pole
- -

So far, we have used the JavaScript language in a single environment: the browser. This chapter and the next one will briefly introduce Node.js, a program that allows you to apply your JavaScript skills outside of the browser. With it, you can build anything from small command line tools to HTTP servers that power dynamic websites.

- -

These chapters aim to teach you the main concepts that Node.js uses and to give you enough information to write useful programs for it. They do not try to be a complete, or even a thorough, treatment of the platform.

- -

Whereas you could run the code in previous chapters directly on these pages, because it was either raw JavaScript or written for the browser, the code samples in this chapter are written for Node and often won’t run in the browser.

- -

If you want to follow along and run the code in this chapter, you’ll need to install Node.js version 10.1 or higher. To do so, go to https://nodejs.org and follow the installation instructions for your operating system. You can also find further documentation for Node.js there.

- -

Background

- -

One of the more difficult problems with writing systems that communicate over the network is managing input and output—that is, the reading and writing of data to and from the network and hard -drive. Moving data around takes time, and scheduling it cleverly can make a big difference in how quickly a system responds to the user or to network requests.

- -

In such programs, asynchronous programming is often helpful. It allows the program to send and receive data from and to multiple devices at the same time without complicated thread management and synchronization.

- -

Node was initially conceived for the purpose of making asynchronous programming easy and convenient. JavaScript lends itself well to a system like Node. It is one of the few programming languages that does not have a built-in way to do in- and output. Thus, JavaScript could be fit onto Node’s rather eccentric approach to in- and output without ending up with two inconsistent interfaces. In 2009, when Node was being designed, people were already doing callback-based programming in the browser, so the community around the language was used to an asynchronous programming style.

- -

The node command

- -

When Node.js is installed on a system, it provides a program called node, which is used to run JavaScript files. Say you have a file hello.js, containing this code:

- -
let message = "Hello world";
-console.log(message);
- -

You can then run node from the command line like this to execute the program:

- -
$ node hello.js
-Hello world
- -

The console.log method in Node does something similar to what it does in the browser. It prints out a piece of text. But in Node, the text will go to the process’s standard output stream, rather than to a browser’s JavaScript console. When running node from the command line, that means you see the logged values in your terminal.

- -

If you run node without giving it a file, it provides you with a prompt at which you can type JavaScript code and immediately see the result.

- -
$ node
-> 1 + 1
-2
-> [-1, -2, -3].map(Math.abs)
-[1, 2, 3]
-> process.exit(0)
-$
- -

The process binding, just like the console binding, is available globally in Node. It provides various ways to inspect and manipulate the current program. The exit method ends the process and can be given an exit status code, which tells the program that started node (in this case, the command line shell) whether the program completed successfully (code zero) or encountered an error (any other code).

- -

To find the command line arguments given to your script, you can read process.argv, which is an array of strings. Note that it also includes the name of the node command and your script name, so the actual arguments start at index 2. If showargv.js contains the statement console.log(process.argv), you could run it like this:

- -
$ node showargv.js one --and two
-["node", "/tmp/showargv.js", "one", "--and", "two"]
- -

All the standard JavaScript global bindings, such as Array, Math, and JSON, are also present in Node’s environment. Browser-related functionality, such as document or prompt, is not.

- -

Modules

- -

Beyond the bindings I mentioned, such as console and process, Node puts few additional bindings in the global scope. If you want to access built-in functionality, you have to ask the module system for it.

- -

The CommonJS module system, based on the require function, was described in Chapter 10. This system is built into Node and is used to load anything from built-in modules to downloaded packages to files that are part of your own program.

- -

When require is called, Node has to resolve the given string to an actual file that it can load. Pathnames that start with /, ./, or ../ are resolved relative to the current module’s path, where . stands for the current directory, ../ for one directory up, and / for the root of the file system. So if you ask for "./graph" from the file /tmp/robot/robot.js, Node will try to load the file /tmp/robot/graph.js.

- -

The .js extension may be omitted, and Node will add it if such a file exists. If the required path refers to a directory, Node will try to load the file named index.js in that directory.

- -

When a string that does not look like a relative or absolute path is given to require, it is assumed to refer to either a built-in module or a module installed in a node_modules directory. For example, require("fs") will give you Node’s built-in file system module. And require("robot") might try to load the library found in node_modules/robot/. A common way to install such libraries is by using NPM, which we’ll come back to in a moment.

- -

Let’s set up a small project consisting of two files. The first one, called main.js, defines a script that can be called from the command line to reverse a string.

- -
const {reverse} = require("./reverse");
-
-// Index 2 holds the first actual command line argument
-let argument = process.argv[2];
-
-console.log(reverse(argument));
- -

The file reverse.js defines a library for reversing strings, which can be used both by this command line tool and by other scripts that need direct access to a string-reversing function.

- -
exports.reverse = function(string) {
-  return Array.from(string).reverse().join("");
-};
- -

Remember that adding properties to exports adds them to the interface of the module. Since Node.js treats files as CommonJS modules, main.js can take the exported reverse function from reverse.js.

- -

We can now call our tool like this:

- -
$ node main.js JavaScript
-tpircSavaJ
- -

Installing with NPM

- -

NPM, which was introduced in Chapter 10, is an online repository of JavaScript modules, many of which are specifically written for Node. When you install Node on your computer, you also get the npm command, which you can use to interact with this repository.

- -

NPM’s main use is downloading packages. We saw the ini package in Chapter 10. We can use NPM to fetch and install that package on our computer.

- -
$ npm install ini
-npm WARN enoent ENOENT: no such file or directory,
-         open '/tmp/package.json'
-+ ini@1.3.5
-added 1 package in 0.552s
-
-$ node
-> const {parse} = require("ini");
-> parse("x = 1\ny = 2");
-{ x: '1', y: '2' }
- -

After running npm install, NPM will have created a directory called node_modules. Inside that directory will be an ini directory that contains the library. You can open it and look at the code. When we call require("ini"), this library is loaded, and we can call its parse property to parse a configuration file.

- -

By default NPM installs packages under the current directory, rather than in a central place. If you are used to other package managers, this may seem unusual, but it has advantages—it puts each application in full control of the packages it installs and makes it easier to manage versions and clean up when removing an application.

- -

Package files

- -

In the npm install example, you could see a warning about the fact that the package.json file did not exist. It is recommended to create such a file for each project, either manually or by running npm init. It contains some information about the project, such as its name and version, and lists its dependencies.

- -

The robot simulation from Chapter 7, as modularized in the exercise in Chapter 10, might have a package.json file like this:

- -
{
-  "author": "Marijn Haverbeke",
-  "name": "eloquent-javascript-robot",
-  "description": "Simulation of a package-delivery robot",
-  "version": "1.0.0",
-  "main": "run.js",
-  "dependencies": {
-    "dijkstrajs": "^1.0.1",
-    "random-item": "^1.0.0"
-  },
-  "license": "ISC"
-}
- -

When you run npm install without naming a package to install, NPM will install the dependencies listed in package.json. When you install a specific package that is not already listed as a dependency, NPM will add it to package.json.

- -

Versions

- -

A package.json file lists both the program’s own version and versions for its dependencies. Versions are a way to deal with the fact that packages evolve separately, and code written to work with a package as it existed at one point may not work with a later, modified version of the package.

- -

NPM demands that its packages follow a schema called semantic -versioning, which encodes some information about which versions are compatible (don’t break the old interface) in the version number. A semantic version consists of three numbers, separated by periods, such as 2.3.0. Every time new functionality is added, the middle number has to be incremented. Every time compatibility is broken, so that existing code that uses the package might not work with the new version, the first number has to be incremented.

- -

A caret character (^) in front of the version number for a dependency in package.json indicates that any version compatible with the given number may be installed. So, for example, "^2.3.0" would mean that any version greater than or equal to 2.3.0 and less than 3.0.0 is allowed.

- -

The npm command is also used to publish new packages or new versions of packages. If you run npm publish in a directory that has a package.json file, it will publish a package with the name and version listed in the JSON file to the registry. Anyone can publish packages to NPM—though only under a package name that isn’t in use yet since it would be somewhat scary if random people could update existing packages.

- -

Since the npm program is a piece of software that talks to an open system—the package registry—there is nothing unique about what it does. Another program, yarn, which can be installed from the NPM registry, fills the same role as npm using a somewhat different interface and installation strategy.

- -

This book won’t delve further into the details of NPM usage. Refer to https://npmjs.org for further documentation and a way to search for packages.

- -

The file system module

- -

One of the most commonly used built-in modules in Node is the fs module, which stands for file system. It exports functions for working with files and directories.

- -

For example, the function called readFile reads a file and then calls a callback with the file’s contents.

- -
let {readFile} = require("fs");
-readFile("file.txt", "utf8", (error, text) => {
-  if (error) throw error;
-  console.log("The file contains:", text);
-});
- -

The second argument to readFile indicates the character -encoding used to decode the file into a string. There are several ways in which text can be encoded to binary data, but most modern systems use UTF-8. So unless you have reasons to believe another encoding is used, pass "utf8" when reading a text file. If you do not pass an encoding, Node will assume you are interested in the binary data and will give you a Buffer object instead of a string. This is an array-like object that contains numbers representing the bytes (8-bit chunks of data) in the files.

- -
const {readFile} = require("fs");
-readFile("file.txt", (error, buffer) => {
-  if (error) throw error;
-  console.log("The file contained", buffer.length, "bytes.",
-              "The first byte is:", buffer[0]);
-});
- -

A similar function, writeFile, is used to write a file to disk.

- -
const {writeFile} = require("fs");
-writeFile("graffiti.txt", "Node was here", err => {
-  if (err) console.log(`Failed to write file: ${err}`);
-  else console.log("File written.");
-});
- -

Here it was not necessary to specify the encoding—writeFile will assume that when it is given a string to write, rather than a Buffer object, it should write it out as text using its default character encoding, which is UTF-8.

- -

The fs module contains many other useful functions: readdir will return the files in a directory as an array of strings, stat will retrieve information about a file, rename will rename a file, unlink will remove one, and so on. See the documentation at https://nodejs.org for specifics.

- -

Most of these take a callback function as the last parameter, which they call either with an error (the first argument) or with a successful result (the second). As we saw in Chapter 11, there are downsides to this style of programming—the biggest one being that error handling becomes verbose and error-prone.

- -

Though promises have been part of JavaScript for a while, at the time of writing their integration into Node.js is still a work in progress. There is an object promises exported from the fs package since version 10.1 that contains most of the same functions as fs but uses promises rather than callback functions.

- -
const {readFile} = require("fs").promises;
-readFile("file.txt", "utf8")
-  .then(text => console.log("The file contains:", text));
- -

Sometimes you don’t need asynchronicity, and it just gets in the way. Many of the functions in fs also have a synchronous variant, which has the same name with Sync added to the end. For example, the synchronous version of readFile is called readFileSync.

- -
const {readFileSync} = require("fs");
-console.log("The file contains:",
-            readFileSync("file.txt", "utf8"));
- -

Do note that while such a synchronous operation is being performed, your program is stopped entirely. If it should be responding to the user or to other machines on the network, being stuck on a synchronous action might produce annoying delays.

- -

The HTTP module

- -

Another central module is called http. It provides functionality for running HTTP servers and making HTTP requests.

- -

This is all it takes to start an HTTP server:

- -
const {createServer} = require("http");
-let server = createServer((request, response) => {
-  response.writeHead(200, {"Content-Type": "text/html"});
-  response.write(`
-    <h1>Hello!</h1>
-    <p>You asked for <code>${request.url}</code></p>`);
-  response.end();
-});
-server.listen(8000);
-console.log("Listening! (port 8000)");
- -

If you run this script on your own machine, you can point your web browser at http://localhost:8000/hello to make a request to your server. It will respond with a small HTML page.

- -

The function passed as argument to createServer is called every time a client connects to the server. The request and response bindings are objects representing the incoming and outgoing data. The first contains information about the request, such as its url property, which tells us to what URL the request was made.

- -

So, when you open that page in your browser, it sends a request to your own computer. This causes the server function to run and send back a response, which you can then see in the browser.

- -

To send something back, you call methods on the response object. The first, writeHead, will write out the response headers (see Chapter 18). You give it the status code (200 for “OK” in this case) and an object that contains header values. The example sets the Content-Type header to inform the client that we’ll be sending back an HTML document.

- -

Next, the actual response body (the document itself) is sent with response.write. You are allowed to call this method multiple times if you want to send the response piece by piece, for example to stream data to the client as it becomes available. Finally, response.end signals the end of the response.

- -

The call to server.listen causes the server to start waiting for connections on port 8000. This is why you have to connect to localhost:8000 to speak to this server, rather than just localhost, which would use the default port 80.

- -

When you run this script, the process just sits there and waits. When a script is listening for events—in this case, network connections—node will not automatically exit when it reaches the end of the script. To close it, press control-C.

- -

A real web server usually does more than the one in the example—it looks at the request’s method (the method property) to see what action the client is trying to perform and looks at the request’s URL to find out which resource this action is being performed on. We’ll see a more advanced server later in this chapter.

- -

To act as an HTTP client, we can use the request function in the http module.

- -
const {request} = require("http");
-let requestStream = request({
-  hostname: "eloquentjavascript.net",
-  path: "/20_node.html",
-  method: "GET",
-  headers: {Accept: "text/html"}
-}, response => {
-  console.log("Server responded with status code",
-              response.statusCode);
-});
-requestStream.end();
- -

The first argument to request configures the request, telling Node what server to talk to, what path to request from that server, which method to use, and so on. The second argument is the function that should be called when a response comes in. It is given an object that allows us to inspect the response, for example to find out its status code.

- -

Just like the response object we saw in the server, the object returned by request allows us to stream data into the request with the write method and finish the request with the end method. The example does not use write because GET requests should not contain data in their request body.

- -

There’s a similar request function in the https module that can be used to make requests to https: URLs.

- -

Making requests with Node’s raw functionality is rather verbose. There are much more convenient wrapper packages available on NPM. For example, node-fetch provides the promise-based fetch interface that we know from the browser.

- -

Streams

- -

We have seen two instances of writable streams in the HTTP examples—namely, the response object that the server could write to and the request object that was returned from request.

- -

Writable streams are a widely used concept in Node. Such objects have a write method that can be passed a string or a Buffer object to write something to the stream. Their end method closes the stream and optionally takes a value to write to the stream before closing. Both of these methods can also be given a callback as an additional argument, which they will call when the writing or closing has finished.

- -

It is possible to create a writable stream that points at a file with the createWriteStream function from the fs module. Then you can use the write method on the resulting object to write the file one piece at a time, rather than in one shot as with writeFile.

- -

Readable streams are a little more involved. Both the request binding that was passed to the HTTP server’s callback and the response binding passed to the HTTP client’s callback are readable streams—a server reads requests and then writes responses, whereas a client first writes a request and then reads a response. Reading from a stream is done using event handlers, rather than methods.

- -

Objects that emit events in Node have a method called on that is similar to the addEventListener method in the browser. You give it an event name and then a function, and it will register that function to be called whenever the given event occurs.

- -

Readable streams have "data" and "end" events. The first is fired every time data comes in, and the second is called whenever the stream is at its end. This model is most suited for streaming data that can be immediately processed, even when the whole document isn’t available yet. A file can be read as a readable stream by using the createReadStream function from fs.

- -

This code creates a server that reads request bodies and streams them back to the client as all-uppercase text:

- -
const {createServer} = require("http");
-createServer((request, response) => {
-  response.writeHead(200, {"Content-Type": "text/plain"});
-  request.on("data", chunk =>
-    response.write(chunk.toString().toUpperCase()));
-  request.on("end", () => response.end());
-}).listen(8000);
- -

The chunk value passed to the data handler will be a binary Buffer. We can convert this to a string by decoding it as UTF-8 encoded characters with its toString method.

- -

The following piece of code, when run with the uppercasing server active, will send a request to that server and write out the response it gets:

- -
const {request} = require("http");
-request({
-  hostname: "localhost",
-  port: 8000,
-  method: "POST"
-}, response => {
-  response.on("data", chunk =>
-    process.stdout.write(chunk.toString()));
-}).end("Hello server");
-// → HELLO SERVER
- -

The example writes to process.stdout (the process’s standard output, which is a writable stream) instead of using console.log. We can’t use console.log because it adds an extra newline character after each piece of text that it writes, which isn’t appropriate here since the response may come in as multiple chunks.

- -

A file server

- -

Let’s combine our newfound knowledge about HTTP servers and working with the file system to create a bridge between the two: an HTTP server that allows remote access to a file system. Such a server has all kinds of uses—it allows web applications to store and share data, or it can give a group of people shared access to a bunch of files.

- -

When we treat files as HTTP resources, the HTTP methods GET, PUT, and DELETE can be used to read, write, and delete the files, respectively. We will interpret the path in the request as the path of the file that the request refers to.

- -

We probably don’t want to share our whole file system, so we’ll interpret these paths as starting in the server’s working directory, which is the directory in which it was started. If I ran the server from /tmp/public/ (or C:\tmp\public\ on Windows), then a request for /file.txt should refer to /tmp/public/file.txt (or C:\tmp\public\file.txt).

- -

We’ll build the program piece by piece, using an object called methods to store the functions that handle the various HTTP methods. Method handlers are async functions that get the request object as argument and return a promise that resolves to an object that describes the response.

- -
const {createServer} = require("http");
-
-const methods = Object.create(null);
-
-createServer((request, response) => {
-  let handler = methods[request.method] || notAllowed;
-  handler(request)
-    .catch(error => {
-      if (error.status != null) return error;
-      return {body: String(error), status: 500};
-    })
-    .then(({body, status = 200, type = "text/plain"}) => {
-       response.writeHead(status, {"Content-Type": type});
-       if (body && body.pipe) body.pipe(response);
-       else response.end(body);
-    });
-}).listen(8000);
-
-async function notAllowed(request) {
-  return {
-    status: 405,
-    body: `Method ${request.method} not allowed.`
-  };
-}
- -

This starts a server that just returns 405 error responses, which is the code used to indicate that the server refuses to handle a given method.

- -

When a request handler’s promise is rejected, the catch call translates the error into a response object, if it isn’t one already, so that the server can send back an error response to inform the client that it failed to handle the request.

- -

The status field of the response description may be omitted, in which case it defaults to 200 (OK). The content type, in the type property, can also be left off, in which case the response is assumed to be plain text.

- -

When the value of body is a readable stream, it will have a pipe method that is used to forward all content from a readable stream to a writable stream. If not, it is assumed to be either null (no body), a string, or a buffer, and it is passed directly to the response’s end method.

- -

To figure out which file path corresponds to a request URL, the urlPath function uses Node’s built-in url module to parse the URL. It takes its pathname, which will be something like "/file.txt", decodes that to get rid of the %20-style escape codes, and resolves it relative to the program’s working directory.

- -
const {parse} = require("url");
-const {resolve, sep} = require("path");
-
-const baseDirectory = process.cwd();
-
-function urlPath(url) {
-  let {pathname} = parse(url);
-  let path = resolve(decodeURIComponent(pathname).slice(1));
-  if (path != baseDirectory &&
-      !path.startsWith(baseDirectory + sep)) {
-    throw {status: 403, body: "Forbidden"};
-  }
-  return path;
-}
- -

As soon as you set up a program to accept network requests, you have to start worrying about security. In this case, if we aren’t careful, it is likely that we’ll accidentally expose our whole file -system to the network.

- -

File paths are strings in Node. To map such a string to an actual file, there is a nontrivial amount of interpretation going on. Paths may, for example, include ../ to refer to a parent directory. So one obvious source of problems would be requests for paths like /../secret_file.

- -

To avoid such problems, urlPath uses the resolve function from the path module, which resolves relative paths. It then verifies that the result is below the working directory. The process.cwd function (where cwd stands for “current working directory”) can be used to find this working directory. The sep binding from the path package is the system’s path separator—a backslash on Windows and a forward slash on most other systems. When the path doesn’t start with the base directory, the function throws an error response object, using the HTTP status code indicating that access to the resource is forbidden.

- -

We’ll set up the GET method to return a list of files when reading a directory and to return the file’s content when reading a regular file.

- -

One tricky question is what kind of Content-Type header we should set when returning a file’s content. Since these files could be anything, our server can’t simply return the same content type for all of them. NPM can help us again here. The mime package (content type indicators like text/plain are also called MIME types) knows the correct type for a large number of file extensions.

- -

The following npm command, in the directory where the server script lives, installs a specific version of mime:

- -
$ npm install mime@2.2.0
- -

When a requested file does not exist, the correct HTTP status code to return is 404. We’ll use the stat function, which looks up information about a file, to find out both whether the file exists and whether it is a directory.

- -
const {createReadStream} = require("fs");
-const {stat, readdir} = require("fs").promises;
-const mime = require("mime");
-
-methods.GET = async function(request) {
-  let path = urlPath(request.url);
-  let stats;
-  try {
-    stats = await stat(path);
-  } catch (error) {
-    if (error.code != "ENOENT") throw error;
-    else return {status: 404, body: "File not found"};
-  }
-  if (stats.isDirectory()) {
-    return {body: (await readdir(path)).join("\n")};
-  } else {
-    return {body: createReadStream(path),
-            type: mime.getType(path)};
-  }
-};
- -

Because it has to touch the disk and thus might take a while, stat is asynchronous. Since we’re using promises rather than callback style, it has to be imported from promises instead of directly from fs.

- -

When the file does not exist, stat will throw an error object with a code property of "ENOENT". These somewhat obscure, Unix-inspired codes are how you recognize error types in Node.

- -

The stats object returned by stat tells us a number of things about a file, such as its size (size property) and its modification date (mtime property). Here we are interested in the question of whether it is a directory or a regular file, which the isDirectory method tells us.

- -

We use readdir to read the array of files in a directory and return it to the client. For normal files, we create a readable stream with createReadStream and return that as the body, along with the content type that the mime package gives us for the file’s name.

- -

The code to handle DELETE requests is slightly simpler.

- -
const {rmdir, unlink} = require("fs").promises;
-
-methods.DELETE = async function(request) {
-  let path = urlPath(request.url);
-  let stats;
-  try {
-    stats = await stat(path);
-  } catch (error) {
-    if (error.code != "ENOENT") throw error;
-    else return {status: 204};
-  }
-  if (stats.isDirectory()) await rmdir(path);
-  else await unlink(path);
-  return {status: 204};
-};
- -

When an HTTP response does not contain any data, the status code 204 (“no content”) can be used to indicate this. Since the response to deletion doesn’t need to transmit any information beyond whether the operation succeeded, that is a sensible thing to return here.

- -

You may be wondering why trying to delete a nonexistent file returns a success status code, rather than an error. When the file that is being deleted is not there, you could say that the request’s objective is already fulfilled. The HTTP standard encourages us to make requests idempotent, which means that making the same request multiple times produces the same result as making it once. In a way, if you try to delete something that’s already gone, the effect you were trying to do has been achieved—the thing is no longer there.

- -

This is the handler for PUT requests:

- -
const {createWriteStream} = require("fs");
-
-function pipeStream(from, to) {
-  return new Promise((resolve, reject) => {
-    from.on("error", reject);
-    to.on("error", reject);
-    to.on("finish", resolve);
-    from.pipe(to);
-  });
-}
-
-methods.PUT = async function(request) {
-  let path = urlPath(request.url);
-  await pipeStream(request, createWriteStream(path));
-  return {status: 204};
-};
- -

We don’t need to check whether the file exists this time—if it does, we’ll just overwrite it. We again use pipe to move data from a readable stream to a writable one, in this case from the request to the file. But since pipe isn’t written to return a promise, we have to write a wrapper, pipeStream, that creates a promise around the outcome of calling pipe.

- -

When something goes wrong when opening the file, createWriteStream will still return a stream, but that stream will fire an "error" event. The output stream to the request may also fail, for example if the network goes down. So we wire up both streams’ "error" events to reject the promise. When pipe is done, it will close the output stream, which causes it to fire a "finish" event. That’s the point where we can successfully resolve the promise (returning nothing).

- -

The full script for the server is available at https://eloquentjavascript.net/code/file_server.js. You can download that and, after installing its dependencies, run it with Node to start your own file server. And, of course, you can modify and extend it to solve this chapter’s exercises or to experiment.

- -

The command line tool curl, widely available on Unix-like systems (such as macOS and Linux), can be used to make HTTP requests. The following session briefly tests our server. The -X option is used to set the request’s method, and -d is used to include a request body.

- -
$ curl http://localhost:8000/file.txt
-File not found
-$ curl -X PUT -d hello http://localhost:8000/file.txt
-$ curl http://localhost:8000/file.txt
-hello
-$ curl -X DELETE http://localhost:8000/file.txt
-$ curl http://localhost:8000/file.txt
-File not found
- -

The first request for file.txt fails since the file does not exist yet. The PUT request creates the file, and behold, the next request successfully retrieves it. After deleting it with a DELETE request, the file is again missing.

- -

Summary

- -

Node is a nice, small system that lets us run JavaScript in a nonbrowser context. It was originally designed for network tasks to play the role of a node in a network. But it lends itself to all kinds of scripting tasks, and if writing JavaScript is something you enjoy, automating tasks with Node works well.

- -

NPM provides packages for everything you can think of (and quite a few things you’d probably never think of), and it allows you to fetch and install those packages with the npm program. Node comes with a number of built-in modules, including the fs module for working with the file system and the http module for running HTTP servers and making HTTP requests.

- -

All input and output in Node is done asynchronously, unless you explicitly use a synchronous variant of a function, such as readFileSync. When calling such asynchronous functions, you provide callback functions, and Node will call them with an error value and (if available) a result when it is ready.

- -

Exercises

- -

Search tool

- -

On Unix systems, there is a command line tool called grep that can be used to quickly search files for a regular expression.

- -

Write a Node script that can be run from the command line and acts somewhat like grep. It treats its first command line argument as a regular expression and treats any further arguments as files to search. It should output the names of any file whose content matches the regular expression.

- -

When that works, extend it so that when one of the arguments is a directory, it searches through all files in that directory and its subdirectories.

- -

Use asynchronous or synchronous file system functions as you see fit. Setting things up so that multiple asynchronous actions are requested at the same time might speed things up a little, but not a huge amount, since most file systems can read only one thing at a time.

- -
- -

Your first command line argument, the regular expression, can be found in process.argv[2]. The input files come after that. You can use the RegExp constructor to go from a string to a regular expression object.

- -

Doing this synchronously, with readFileSync, is more straightforward, but if you use fs.promises again to get promise-returning functions and write an async function, the code looks similar.

- -

To figure out whether something is a directory, you can again use stat (or statSync) and the stats object’s isDirectory method.

- -

Exploring a directory is a branching process. You can do it either by using a recursive function or by keeping an array of work (files that still need to be explored). To find the files in a directory, you can call readdir or readdirSync. The strange capitalization—Node’s file system function naming is loosely based on standard Unix functions, such as readdir, that are all lowercase, but then it adds Sync with a capital letter.

- -

To go from a filename read with readdir to a full path name, you have to combine it with the name of the directory, putting a slash -character (/) between them.

- -
- -

Directory creation

- -

Though the DELETE method in our file server is able to delete directories (using rmdir), the server currently does not provide any way to create a directory.

- -

Add support for the MKCOL method (“make collection”), which should create a directory by calling mkdir from the fs module. MKCOL is not a widely used HTTP method, but it does exist for this same purpose in the WebDAV standard, which specifies a set of conventions on top of HTTP that make it suitable for creating documents.

- -
- -

You can use the function that implements the DELETE method as a blueprint for the MKCOL method. When no file is found, try to create a directory with mkdir. When a directory exists at that path, you can return a 204 response so that directory creation requests are idempotent. If a nondirectory file exists here, return an error code. Code 400 (“bad request”) would be appropriate.

- -
- -

A public space on the web

- -

Since the file server serves up any kind of file and even includes the right Content-Type header, you can use it to serve a website. Since it allows everybody to delete and replace files, it would be an interesting kind of website: one that can be modified, improved, and vandalized by everybody who takes the time to create the right HTTP request.

- -

Write a basic HTML page that includes a simple JavaScript file. Put the files in a directory served by the file server and open them in your browser.

- -

Next, as an advanced exercise or even a weekend project, combine all the knowledge you gained from this book to build a more user-friendly interface for modifying the website—from inside the website.

- -

Use an HTML form to edit the content of the files that make up the website, allowing the user to update them on the server by using HTTP requests, as described in Chapter 18.

- -

Start by making only a single file editable. Then make it so that the user can select which file to edit. Use the fact that our file server returns lists of files when reading a directory.

- -

Don’t work directly in the code exposed by the file server since if you make a mistake, you are likely to damage the files there. Instead, keep your work outside of the publicly accessible directory and copy it there when testing.

- -
- -

You can create a <textarea> element to hold the content of the file that is being edited. A GET request, using fetch, can retrieve the current content of the file. You can use relative URLs like index.html, instead of http://localhost:8000/index.html, to refer to files on the same server as the running script.

- -

Then, when the user clicks a button (you can use a <form> element and "submit" event), make a PUT request to the same URL, with the content of the <textarea> as request body, to save the file.

- -

You can then add a <select> element that contains all the files in the server’s top directory by adding <option> elements containing the lines returned by a GET request to the URL /. When the user selects another file (a "change" event on the field), the script must fetch and display that file. When saving a file, use the currently selected filename.

- -
-
diff --git a/docs/21_skillsharing.html b/docs/21_skillsharing.html deleted file mode 100644 index 08f987908..000000000 --- a/docs/21_skillsharing.html +++ /dev/null @@ -1,636 +0,0 @@ - - - - - Project: Skill-Sharing Website :: Eloquent JavaScript - - - - - - -
- - -

Chapter 21Project: Skill-Sharing Website

- -
- -

If you have knowledge, let others light their candles at it.

- -
Margaret Fuller
- -
Picture of two unicycles
- -

A skill-sharing meeting is an event where people with a shared interest come together and give small, informal presentations about things they know. At a gardening skill-sharing meeting, someone might explain how to cultivate celery. Or in a programming skill-sharing group, you could drop by and tell people about Node.js.

- -

Such meetups—also often called users’ groups when they are about computers—are a great way to broaden your horizon, learn about new developments, or simply meet people with similar interests. Many larger cities have JavaScript meetups. They are typically free to attend, and I’ve found the ones I’ve visited to be friendly and welcoming.

- -

In this final project chapter, our goal is to set up a website for managing talks given at a skill-sharing meeting. Imagine a small group of people meeting up regularly in the office of one of the members to talk about unicycling. The previous organizer of the meetings moved to another town, and nobody stepped forward to take over this task. We want a system that will let the participants propose and discuss talks among themselves, without a central organizer.

- -

Just like in the previous chapter, some of the code in this chapter is written for Node.js, and running it directly in the HTML page that you are looking at is unlikely to work. The full code for the project can be downloaded from https://eloquentjavascript.net/code/skillsharing.zip.

- -

Design

- -

There is a server part to this project, written for Node.js, and a client part, written for the browser. The server stores the system’s data and provides it to the client. It also serves the files that implement the client-side system.

- -

The server keeps the list of talks proposed for the next meeting, and the client shows this list. Each talk has a presenter name, a title, a summary, and an array of comments associated with it. The client allows users to propose new talks (adding them to the list), delete talks, and comment on existing talks. Whenever the user makes such a change, the client makes an HTTP request to tell the server about it.

Screenshot of the skill-sharing website
- -

The application will be set up to show a live view of the current proposed talks and their comments. Whenever someone, somewhere, submits a new talk or adds a comment, all people who have the page open in their browsers should immediately see the change. This poses a bit of a challenge—there is no way for a web server to open a connection to a client, nor is there a good way to know which clients are currently looking at a given website.

- -

A common solution to this problem is called long polling, which happens to be one of the motivations for Node’s design.

- -

Long polling

- -

To be able to immediately notify a client that something changed, we need a connection to that client. Since web browsers do not traditionally accept connections and clients are often behind routers that would block such connections anyway, having the server initiate this connection is not practical.

- -

We can arrange for the client to open the connection and keep it around so that the server can use it to send information when it needs to do so.

- -

But an HTTP request allows only a simple flow of information: the client sends a request, the server comes back with a single response, and that is it. There is a technology called WebSockets, supported by modern browsers, that makes it possible to open connections for arbitrary data exchange. But using them properly is somewhat tricky.

- -

In this chapter, we use a simpler technique—long polling—where clients continuously ask the server for new information using regular HTTP requests, and the server stalls its answer when it has nothing new to report.

- -

As long as the client makes sure it constantly has a polling request open, it will receive information from the server quickly after it becomes available. For example, if Fatma has our skill-sharing application open in her browser, that browser will have made a request for updates and will be waiting for a response to that request. When Iman submits a talk on Extreme Downhill Unicycling, the server will notice that Fatma is waiting for updates and send a response containing the new talk to her pending request. Fatma’s browser will receive the data and update the screen to show the talk.

- -

To prevent connections from timing out (being aborted because of a lack of activity), long polling techniques usually set a maximum time for each request, after which the server will respond anyway, even though it has nothing to report, after which the client will start a new request. Periodically restarting the request also makes the technique more robust, allowing clients to recover from temporary connection failures or server problems.

- -

A busy server that is using long polling may have thousands of waiting requests, and thus TCP connections, open. Node, which makes it easy to manage many connections without creating a separate thread of control for each one, is a good fit for such a system.

- -

HTTP interface

- -

Before we start designing either the server or the client, let’s think about the point where they touch: the HTTP interface over which they communicate.

- -

We will use JSON as the format of our request and response body. Like in the file server from Chapter 20, we’ll try to make good use of HTTP methods and headers. The interface is centered around the /talks path. Paths that do not start with /talks will be used for serving static files—the HTML and JavaScript code for the client-side system.

- -

A GET request to /talks returns a JSON document like this:

- -
[{"title": "Unituning",
-  "presenter": "Jamal",
-  "summary": "Modifying your cycle for extra style",
-  "comments": []}]
- -

Creating a new talk is done by making a PUT request to a URL like /talks/Unituning, where the part after the second slash is the title of the talk. The PUT request’s body should contain a JSON object that has presenter and summary properties.

- -

Since talk titles may contain spaces and other characters that may not appear normally in a URL, title strings must be encoded with the encodeURIComponent function when building up such a URL.

- -
console.log("/talks/" + encodeURIComponent("How to Idle"));
-// → /talks/How%20to%20Idle
- -

A request to create a talk about idling might look something like this:

- -
PUT /talks/How%20to%20Idle HTTP/1.1
-Content-Type: application/json
-Content-Length: 92
-
-{"presenter": "Maureen",
- "summary": "Standing still on a unicycle"}
- -

Such URLs also support GET requests to retrieve the JSON representation of a talk and DELETE requests to delete a talk.

- -

Adding a comment to a talk is done with a POST request to a URL like /talks/Unituning/comments, with a JSON body that has author and message properties.

- -
POST /talks/Unituning/comments HTTP/1.1
-Content-Type: application/json
-Content-Length: 72
-
-{"author": "Iman",
- "message": "Will you talk about raising a cycle?"}
- -

To support long polling, GET requests to /talks may include extra headers that inform the server to delay the response if no new information is available. We’ll use a pair of headers normally intended to manage caching: ETag and If-None-Match.

- -

Servers may include an ETag (“entity tag”) header in a response. Its value is a string that identifies the current version of the resource. Clients, when they later request that resource again, may make a conditional request by including an If-None-Match header whose value holds that same string. If the resource hasn’t changed, the server will respond with status code 304, which means “not modified”, telling the client that its cached version is still current. When the tag does not match, the server responds as normal.

- -

We need something like this, where the client can tell the server which version of the list of talks it has, and the server responds only when that list has changed. But instead of immediately returning a 304 response, the server should stall the response and return only when something new is available or a given amount of time has elapsed. To distinguish long polling requests from normal conditional requests, we give them another header, Prefer: wait=90, which tells the server that the client is willing to wait up to 90 seconds for the response.

- -

The server will keep a version number that it updates every time the talks change and will use that as the ETag value. Clients can make requests like this to be notified when the talks change:

- -
GET /talks HTTP/1.1
-If-None-Match: "4"
-Prefer: wait=90
-
-(time passes)
-
-HTTP/1.1 200 OK
-Content-Type: application/json
-ETag: "5"
-Content-Length: 295
-
-[....]
- -

The protocol described here does not do any access control. Everybody can comment, modify talks, and even delete them. (Since the Internet is full of hooligans, putting such a system online without further protection probably wouldn’t end well.)

- -

The server

- -

Let’s start by building the server-side part of the program. The code in this section runs on Node.js.

- -

Routing

- -

Our server will use createServer to start an HTTP server. In the function that handles a new request, we must distinguish between the various kinds of requests (as determined by the method and the path) that we support. This can be done with a long chain of if statements, but there is a nicer way.

- -

A router is a component that helps dispatch a request to the function that can handle it. You can tell the router, for example, that PUT requests with a path that matches the regular expression /^\/talks\/([^\/]+)$/ (/talks/ followed by a talk title) can be handled by a given function. In addition, it can help extract the meaningful parts of the path (in this case the talk title), wrapped in parentheses in the regular expression, and pass them to the handler function.

- -

There are a number of good router packages on NPM, but here we’ll write one ourselves to illustrate the principle.

- -

This is router.js, which we will later require from our server module:

- -
const {parse} = require("url");
-
-module.exports = class Router {
-  constructor() {
-    this.routes = [];
-  }
-  add(method, url, handler) {
-    this.routes.push({method, url, handler});
-  }
-  resolve(context, request) {
-    let path = parse(request.url).pathname;
-
-    for (let {method, url, handler} of this.routes) {
-      let match = url.exec(path);
-      if (!match || request.method != method) continue;
-      let urlParts = match.slice(1).map(decodeURIComponent);
-      return handler(context, ...urlParts, request);
-    }
-    return null;
-  }
-};
- -

The module exports the Router class. A router object allows new handlers to be registered with the add method and can resolve requests with its resolve method.

- -

The latter will return a response when a handler was found, and null otherwise. It tries the routes one at a time (in the order in which they were defined) until a matching one is found.

- -

The handler functions are called with the context value (which will be the server instance in our case), match strings for any groups they defined in their regular expression, and the request object. The strings have to be URL-decoded since the raw URL may contain %20-style codes.

- -

Serving files

- -

When a request matches none of the request types defined in our router, the server must interpret it as a request for a file in the public directory. It would be possible to use the file server defined in Chapter 20 to serve such files, but we neither need nor want to support PUT and DELETE requests on files, and we would like to have advanced features such as support for caching. So let’s use a solid, well-tested static file server from NPM instead.

- -

I opted for ecstatic. This isn’t the only such server on NPM, but it works well and fits our purposes. The ecstatic package exports a function that can be called with a configuration object to produce a request handler function. We use the root option to tell the server where it should look for files. The handler function accepts request and response parameters and can be passed directly to createServer to create a server that serves only files. We want to first check for requests that we should handle specially, though, so we wrap it in another function.

- -
const {createServer} = require("http");
-const Router = require("./router");
-const ecstatic = require("ecstatic");
-
-const router = new Router();
-const defaultHeaders = {"Content-Type": "text/plain"};
-
-class SkillShareServer {
-  constructor(talks) {
-    this.talks = talks;
-    this.version = 0;
-    this.waiting = [];
-
-    let fileServer = ecstatic({root: "./public"});
-    this.server = createServer((request, response) => {
-      let resolved = router.resolve(this, request);
-      if (resolved) {
-        resolved.catch(error => {
-          if (error.status != null) return error;
-          return {body: String(error), status: 500};
-        }).then(({body,
-                  status = 200,
-                  headers = defaultHeaders}) => {
-          response.writeHead(status, headers);
-          response.end(body);
-        });
-      } else {
-        fileServer(request, response);
-      }
-    });
-  }
-  start(port) {
-    this.server.listen(port);
-  }
-  stop() {
-    this.server.close();
-  }
-}
- -

This uses a similar convention as the file server from the previous chapter for responses—handlers return promises that resolve to objects describing the response. It wraps the server in an object that also holds its state.

- -

Talks as resources

- -

The talks that have been proposed are stored in the talks property of the server, an object whose property names are the talk titles. These will be exposed as HTTP resources under /talks/[title], so we need to add handlers to our router that implement the various methods that clients can use to work with them.

- -

The handler for requests that GET a single talk must look up the talk and respond either with the talk’s JSON data or with a 404 error response.

- -
const talkPath = /^\/talks\/([^\/]+)$/;
-
-router.add("GET", talkPath, async (server, title) => {
-  if (title in server.talks) {
-    return {body: JSON.stringify(server.talks[title]),
-            headers: {"Content-Type": "application/json"}};
-  } else {
-    return {status: 404, body: `No talk '${title}' found`};
-  }
-});
- -

Deleting a talk is done by removing it from the talks object.

- -
router.add("DELETE", talkPath, async (server, title) => {
-  if (title in server.talks) {
-    delete server.talks[title];
-    server.updated();
-  }
-  return {status: 204};
-});
- -

The updated method, which we will define later, notifies waiting long polling requests about the change.

- -

To retrieve the content of a request body, we define a function called readStream, which reads all content from a readable stream and returns a promise that resolves to a string.

- -
function readStream(stream) {
-  return new Promise((resolve, reject) => {
-    let data = "";
-    stream.on("error", reject);
-    stream.on("data", chunk => data += chunk.toString());
-    stream.on("end", () => resolve(data));
-  });
-}
- -

One handler that needs to read request bodies is the PUT handler, which is used to create new talks. It has to check whether the data it was given has presenter and summary properties, which are strings. Any data coming from outside the system might be nonsense, and we don’t want to corrupt our internal data model or crash when bad requests come in.

- -

If the data looks valid, the handler stores an object that represents the new talk in the talks object, possibly overwriting an existing talk with this title, and again calls updated.

- -
router.add("PUT", talkPath,
-           async (server, title, request) => {
-  let requestBody = await readStream(request);
-  let talk;
-  try { talk = JSON.parse(requestBody); }
-  catch (_) { return {status: 400, body: "Invalid JSON"}; }
-
-  if (!talk ||
-      typeof talk.presenter != "string" ||
-      typeof talk.summary != "string") {
-    return {status: 400, body: "Bad talk data"};
-  }
-  server.talks[title] = {title,
-                         presenter: talk.presenter,
-                         summary: talk.summary,
-                         comments: []};
-  server.updated();
-  return {status: 204};
-});
- -

Adding a comment to a talk works similarly. We use readStream to get the content of the request, validate the resulting data, and store it as a comment when it looks valid.

- -
router.add("POST", /^\/talks\/([^\/]+)\/comments$/,
-           async (server, title, request) => {
-  let requestBody = await readStream(request);
-  let comment;
-  try { comment = JSON.parse(requestBody); }
-  catch (_) { return {status: 400, body: "Invalid JSON"}; }
-
-  if (!comment ||
-      typeof comment.author != "string" ||
-      typeof comment.message != "string") {
-    return {status: 400, body: "Bad comment data"};
-  } else if (title in server.talks) {
-    server.talks[title].comments.push(comment);
-    server.updated();
-    return {status: 204};
-  } else {
-    return {status: 404, body: `No talk '${title}' found`};
-  }
-});
- -

Trying to add a comment to a nonexistent talk returns a 404 error.

- -

Long polling support

- -

The most interesting aspect of the server is the part that handles long polling. When a GET request comes in for /talks, it may be either a regular request or a long polling request.

- -

There will be multiple places in which we have to send an array of talks to the client, so we first define a helper method that builds up such an array and includes an ETag header in the response.

- -
SkillShareServer.prototype.talkResponse = function() {
-  let talks = [];
-  for (let title of Object.keys(this.talks)) {
-    talks.push(this.talks[title]);
-  }
-  return {
-    body: JSON.stringify(talks),
-    headers: {"Content-Type": "application/json",
-              "ETag": `"${this.version}"`}
-  };
-};
- -

The handler itself needs to look at the request headers to see whether If-None-Match and Prefer headers are present. Node stores headers, whose names are specified to be case insensitive, under their lowercase names.

- -
router.add("GET", /^\/talks$/, async (server, request) => {
-  let tag = /"(.*)"/.exec(request.headers["if-none-match"]);
-  let wait = /\bwait=(\d+)/.exec(request.headers["prefer"]);
-  if (!tag || tag[1] != server.version) {
-    return server.talkResponse();
-  } else if (!wait) {
-    return {status: 304};
-  } else {
-    return server.waitForChanges(Number(wait[1]));
-  }
-});
- -

If no tag was given or a tag was given that doesn’t match the server’s current version, the handler responds with the list of talks. If the request is conditional and the talks did not change, we consult the Prefer header to see whether we should delay the response or respond right away.

- -

Callback functions for delayed requests are stored in the server’s waiting array so that they can be notified when something happens. The waitForChanges method also immediately sets a timer to respond with a 304 status when the request has waited long enough.

- -
SkillShareServer.prototype.waitForChanges = function(time) {
-  return new Promise(resolve => {
-    this.waiting.push(resolve);
-    setTimeout(() => {
-      if (!this.waiting.includes(resolve)) return;
-      this.waiting = this.waiting.filter(r => r != resolve);
-      resolve({status: 304});
-    }, time * 1000);
-  });
-};
- -

Registering a change with updated increases the version property and wakes up all waiting requests.

- -
SkillShareServer.prototype.updated = function() {
-  this.version++;
-  let response = this.talkResponse();
-  this.waiting.forEach(resolve => resolve(response));
-  this.waiting = [];
-};
- -

That concludes the server code. If we create an instance of SkillShareServer and start it on port 8000, the resulting HTTP server serves files from the public subdirectory alongside a talk-managing interface under the /talks URL.

- -
new SkillShareServer(Object.create(null)).start(8000);
- -

The client

- -

The client-side part of the skill-sharing website consists of three files: a tiny HTML page, a style sheet, and a JavaScript file.

- -

HTML

- -

It is a widely used convention for web servers to try to serve a file named index.html when a request is made directly to a path that corresponds to a directory. The file server module we use, ecstatic, supports this convention. When a request is made to the path /, the server looks for the file ./public/index.html (./public being the root we gave it) and returns that file if found.

- -

Thus, if we want a page to show up when a browser is pointed at our server, we should put it in public/index.html. This is our index file:

- -
<!doctype html>
-<meta charset="utf-8">
-<title>Skill Sharing</title>
-<link rel="stylesheet" href="skillsharing.css">
-
-<h1>Skill Sharing</h1>
-
-<script src="skillsharing_client.js"></script>
- -

It defines the document title and includes a style sheet, which defines a few styles to, among other things, make sure there is some space between talks.

- -

At the bottom, it adds a heading at the top of the page and loads the script that contains the client-side application.

- -

Actions

- -

The application state consists of the list of talks and the name of the user, and we’ll store it in a {talks, user} object. We don’t allow the user interface to directly manipulate the state or send off HTTP requests. Rather, it may emit actions that describe what the user is trying to do.

- -

The handleAction function takes such an action and makes it happen. Because our state updates are so simple, state changes are handled in the same function.

- -
function handleAction(state, action) {
-  if (action.type == "setUser") {
-    localStorage.setItem("userName", action.user);
-    return Object.assign({}, state, {user: action.user});
-  } else if (action.type == "setTalks") {
-    return Object.assign({}, state, {talks: action.talks});
-  } else if (action.type == "newTalk") {
-    fetchOK(talkURL(action.title), {
-      method: "PUT",
-      headers: {"Content-Type": "application/json"},
-      body: JSON.stringify({
-        presenter: state.user,
-        summary: action.summary
-      })
-    }).catch(reportError);
-  } else if (action.type == "deleteTalk") {
-    fetchOK(talkURL(action.talk), {method: "DELETE"})
-      .catch(reportError);
-  } else if (action.type == "newComment") {
-    fetchOK(talkURL(action.talk) + "/comments", {
-      method: "POST",
-      headers: {"Content-Type": "application/json"},
-      body: JSON.stringify({
-        author: state.user,
-        message: action.message
-      })
-    }).catch(reportError);
-  }
-  return state;
-}
- -

We’ll store the user’s name in localStorage so that it can be restored when the page is loaded.

- -

The actions that need to involve the server make network requests, using fetch, to the HTTP interface described earlier. We use a wrapper function, fetchOK, which makes sure the returned promise is rejected when the server returns an error code.

- -
function fetchOK(url, options) {
-  return fetch(url, options).then(response => {
-    if (response.status < 400) return response;
-    else throw new Error(response.statusText);
-  });
-}
- -

This helper function is used to build up a URL for a talk with a given title.

- -
function talkURL(title) {
-  return "talks/" + encodeURIComponent(title);
-}
- -

When the request fails, we don’t want to have our page just sit there, doing nothing without explanation. So we define a function called reportError, which at least shows the user a dialog that tells them something went wrong.

- -
function reportError(error) {
-  alert(String(error));
-}
- -

Rendering components

- -

We’ll use an approach similar to the one we saw in Chapter 19, splitting the application into components. But since some of the components either never need to update or are always fully redrawn when updated, we’ll define those not as classes but as functions that directly return a DOM node. For example, here is a component that shows the field where the user can enter their name:

- -
function renderUserField(name, dispatch) {
-  return elt("label", {}, "Your name: ", elt("input", {
-    type: "text",
-    value: name,
-    onchange(event) {
-      dispatch({type: "setUser", user: event.target.value});
-    }
-  }));
-}
- -

The elt function used to construct DOM elements is the one we used in Chapter 19.

- -

A similar function is used to render talks, which include a list of comments and a form for adding a new comment.

- -
function renderTalk(talk, dispatch) {
-  return elt(
-    "section", {className: "talk"},
-    elt("h2", null, talk.title, " ", elt("button", {
-      type: "button",
-      onclick() {
-        dispatch({type: "deleteTalk", talk: talk.title});
-      }
-    }, "Delete")),
-    elt("div", null, "by ",
-        elt("strong", null, talk.presenter)),
-    elt("p", null, talk.summary),
-    ...talk.comments.map(renderComment),
-    elt("form", {
-      onsubmit(event) {
-        event.preventDefault();
-        let form = event.target;
-        dispatch({type: "newComment",
-                  talk: talk.title,
-                  message: form.elements.comment.value});
-        form.reset();
-      }
-    }, elt("input", {type: "text", name: "comment"}), " ",
-       elt("button", {type: "submit"}, "Add comment")));
-}
- -

The "submit" event handler calls form.reset to clear the form’s content after creating a "newComment" action.

- -

When creating moderately complex pieces of DOM, this style of programming starts to look rather messy. There’s a widely used (non-standard) JavaScript extension called JSX that lets you write HTML directly in your scripts, which can make such code prettier (depending on what you consider pretty). Before you can actually run such code, you have to run a program on your script to convert the pseudo-HTML into JavaScript function calls much like the ones we use here.

- -

Comments are simpler to render.

- -
function renderComment(comment) {
-  return elt("p", {className: "comment"},
-             elt("strong", null, comment.author),
-             ": ", comment.message);
-}
- -

Finally, the form that the user can use to create a new talk is rendered like this:

- -
function renderTalkForm(dispatch) {
-  let title = elt("input", {type: "text"});
-  let summary = elt("input", {type: "text"});
-  return elt("form", {
-    onsubmit(event) {
-      event.preventDefault();
-      dispatch({type: "newTalk",
-                title: title.value,
-                summary: summary.value});
-      event.target.reset();
-    }
-  }, elt("h3", null, "Submit a Talk"),
-     elt("label", null, "Title: ", title),
-     elt("label", null, "Summary: ", summary),
-     elt("button", {type: "submit"}, "Submit"));
-}
- -

Polling

- -

To start the app we need the current list of talks. Since the initial load is closely related to the long polling process—the ETag from the load must be used when polling—we’ll write a function that keeps polling the server for /talks and calls a callback function when a new set of talks is available.

- -
async function pollTalks(update) {
-  let tag = undefined;
-  for (;;) {
-    let response;
-    try {
-      response = await fetchOK("/talks", {
-        headers: tag && {"If-None-Match": tag,
-                         "Prefer": "wait=90"}
-      });
-    } catch (e) {
-      console.log("Request failed: " + e);
-      await new Promise(resolve => setTimeout(resolve, 500));
-      continue;
-    }
-    if (response.status == 304) continue;
-    tag = response.headers.get("ETag");
-    update(await response.json());
-  }
-}
- -

This is an async function so that looping and waiting for the request is easier. It runs an infinite loop that, on each iteration, retrieves the list of talks—either normally or, if this isn’t the first request, with the headers included that make it a long polling request.

- -

When a request fails, the function waits a moment and then tries again. This way, if your network connection goes away for a while and then comes back, the application can recover and continue updating. The promise resolved via setTimeout is a way to force the async function to wait.

- -

When the server gives back a 304 response, that means a long polling request timed out, so the function should just immediately start the next request. If the response is a normal 200 response, its body is read as JSON and passed to the callback, and its ETag header value is stored for the next iteration.

- -

The application

- -

The following component ties the whole user interface together:

- -
class SkillShareApp {
-  constructor(state, dispatch) {
-    this.dispatch = dispatch;
-    this.talkDOM = elt("div", {className: "talks"});
-    this.dom = elt("div", null,
-                   renderUserField(state.user, dispatch),
-                   this.talkDOM,
-                   renderTalkForm(dispatch));
-    this.syncState(state);
-  }
-
-  syncState(state) {
-    if (state.talks != this.talks) {
-      this.talkDOM.textContent = "";
-      for (let talk of state.talks) {
-        this.talkDOM.appendChild(
-          renderTalk(talk, this.dispatch));
-      }
-      this.talks = state.talks;
-    }
-  }
-}
- -

When the talks change, this component redraws all of them. This is simple but also wasteful. We’ll get back to that in the exercises.

- -

We can start the application like this:

- -
function runApp() {
-  let user = localStorage.getItem("userName") || "Anon";
-  let state, app;
-  function dispatch(action) {
-    state = handleAction(state, action);
-    app.syncState(state);
-  }
-
-  pollTalks(talks => {
-    if (!app) {
-      state = {user, talks};
-      app = new SkillShareApp(state, dispatch);
-      document.body.appendChild(app.dom);
-    } else {
-      dispatch({type: "setTalks", talks});
-    }
-  }).catch(reportError);
-}
-
-runApp();
- -

If you run the server and open two browser windows for http://localhost:8000 next to each other, you can see that the actions you perform in one window are immediately visible in the other.

- -

Exercises

- -

The following exercises will involve modifying the system defined in this chapter. To work on them, make sure you download the code first (https://eloquentjavascript.net/code/skillsharing.zip), have Node installed https://nodejs.org, and have installed the project’s dependency with npm install.

- -

Disk persistence

- -

The skill-sharing server keeps its data purely in memory. This means that when it crashes or is restarted for any reason, all talks and comments are lost.

- -

Extend the server so that it stores the talk data to disk and automatically reloads the data when it is restarted. Do not worry about efficiency—do the simplest thing that works.

- -
- -

The simplest solution I can come up with is to encode the whole talks object as JSON and dump it to a file with writeFile. There is already a method (updated) that is called every time the server’s data changes. It can be extended to write the new data to disk.

- -

Pick a filename, for example ./talks.json. When the server starts, it can try to read that file with readFile, and if that succeeds, the server can use the file’s contents as its starting data.

- -

Beware, though. The talks object started as a prototype-less object so that the in operator could reliably be used. JSON.parse will return regular objects with Object.prototype as their prototype. If you use JSON as your file format, you’ll have to copy the properties of the object returned by JSON.parse into a new, prototype-less object.

- -
- -

Comment field resets

- -

The wholesale redrawing of talks works pretty well because you usually can’t tell the difference between a DOM node and its identical replacement. But there are exceptions. If you start typing something in the comment field for a talk in one browser window and then, in another, add a comment to that talk, the field in the first window will be redrawn, removing both its content and its focus.

- -

In a heated discussion, where multiple people are adding comments at the same time, this would be annoying. Can you come up with a way to solve it?

- -
- -

The best way to do this is probably to make talks component objects, with a syncState method, so that they can be updated to show a modified version of the talk. During normal operation, the only way a talk can be changed is by adding more comments, so the syncState method can be relatively simple.

- -

The difficult part is that, when a changed list of talks comes in, we have to reconcile the existing list of DOM components with the talks on the new list—deleting components whose talk was deleted and updating components whose talk changed.

- -

To do this, it might be helpful to keep a data structure that stores the talk components under the talk titles so that you can easily figure out whether a component exists for a given talk. You can then loop over the new array of talks, and for each of them, either synchronize an existing component or create a new one. To delete components for deleted talks, you’ll have to also loop over the components and check whether the corresponding talks still exist.

- -
-
diff --git a/docs/author.html b/docs/author.html deleted file mode 100644 index 3e287ea46..000000000 --- a/docs/author.html +++ /dev/null @@ -1,10 +0,0 @@ - - -
-

Marijn Haverbeke, Programmer

- -

You can reach me at marijn@haverbeke.nl, or visit my web page, marijnhaverbeke.nl.

-
diff --git a/docs/author.json b/docs/author.json deleted file mode 100644 index 24be3c131..000000000 --- a/docs/author.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Marijn Haverbeke", - "email": "marijn@haverbeke.nl", - "website": "https://marijnhaverbeke.nl/" -} diff --git a/docs/author.txt b/docs/author.txt deleted file mode 100644 index 6cf92433c..000000000 --- a/docs/author.txt +++ /dev/null @@ -1 +0,0 @@ -My name is Marijn Haverbeke. You can email me at marijn@haverbeke.nl, or visit my website, https://marijnhaverbeke.nl/ . diff --git a/docs/backers.html b/docs/backers.html deleted file mode 100644 index 5fbc2bf40..000000000 --- a/docs/backers.html +++ /dev/null @@ -1,3687 +0,0 @@ - - - - - Eloquent JavaScript :: 2nd Edition Backers - - - - - -
-

List of Backers
Eloquent JavaScript, 2nd Edition

- -

These are the kind souls who have contributed towards making the -second edition of Eloquent JavaScript -possible.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - 10,000 $
- - 7000 €
- Magnus Skog - 1000 €
- - 1000 $Donation from dev team at Ghostery! Thanks for the awesome work!
- Anonymous - 1000 $
- erin lee - 200 $
- Alexander Shendi - 100 €
- Anonymous - 100 €
- Anonymous - 100 €I love your book. Thank you very much!
- Morgan Roderick - 100 €We need a second edition of the best, free JavaScript book
- Stelian Ionescu - 100 €
- Anonymous - 100 $
- Anton Kovalyov - 100 $
- david karapetyan - 100 $I fully support this effort. We need more decentralized sources of education for technology related matters.
- Joshua Waheed - 100 $
- Kevin Layer, Franz Inc. - 100 $
- Nathan Youngman - 100 $
- Raynos - 100 $
- Tim Caswell - 100 $I especially liked the intro chapter in which you describe programming as a creative art more than a cold technical task.
- Stan Yamane - 75 $
- tiffon - 75 $
- Anonymous - 56 €
- [o]_O - 50 €I am a T-SQL monkey with the occasional C# app or python script. Much to my dismay, I was assigned the task of fixing some Javascript bugs in an internal deployment tool. All I knew at the time about Javascript was that it sucked, so I tried reading the 1st edition of Eloquent Javascript. Unfortunately, it worked too well: not only did I fix the problems, I now think Javascript is cool :-(
- David Owens - 50 €
- Dominique Poulain - 50 €
- Jans Aasman - 50 €looking forward to see the new edition...
- Kai Elvin - 50 €Not too interested in the book itself, donating to promote the funding model. I would advise to keep the bitcoins as Bitpay provides a very bad exchange rate.
- Koen Steenbrink - 50 €First edition helped me a lot. Looking forward for the next one.
- Mihai Bazon - 50 €
- Siltaar - 50 €
- tmk - 50 €
- Tom Weber - 50 €
- Vaidas Gaurilcikas - 50 €Thanks for the great work! I found the 1st edition of Eloquent Javascript to be one of the most pleasant to read and useful programming books I've ever come across. Eagerly awaiting the 2nd edition!
- Vincent - 50 €
- Iain Bryson - 40 €
- Adam Hyland - 50 $
- Amanda Brown - 50 $
- Anonymous - 50 $
- Anonymous - 50 $
- Anonymous - 50 $
- Anonymous - 50 $
- Anonymous - 50 $
- Mato Ilic - 50 $This is one of the best JavaScript books around. Would love to see an updated version-
- Peter deHaan - 50 $
- Pierce Lopez - 50 $
- Trevor Blackwell - 50 $
- Zach W - 50 $I loved the first edition. Looking forward to another :)
- Anonymous - 40 $
- Henry Allen-Tilford - 40 $
- Kory Mathis - 40 $
- Tom Kelley - 40 $Keep up the good work. I brag about your current book, and I can't wait to see the new one.
- Anonymous - 30 €
- Anonymous - 30 €
- Anonymous - 30 €
- Daniel Harrington - 30 €
- Onno Schwanen - 30 €
- Anonymous - 25 €
- Chris Smith - 25 €Marijn's CodeMirror project is the foundation upon which I've been realizing my own vision of a better way to learn programming, and Eloquent JavaScript was truly a pioneering work. I feel it is extremely important to provide Marijn with the encouragement and financial support he needs.
- Eric Allam - 25 €
- fronx - 25 €
- Octavian Nita - 25 €One of the best books on programming ever, if you ask me; even if you know your way around programming, it is still worth it to read even the beginner chapters. - -Can hardly wait to buy the new edition! Best of luck, man!
- Orde Saunders - 25 €
- Rob Crowther - 25 €
- Tom - 25 €More of this sort of thing.
- Alan Fothergill - 30 $
- Ben Handzo - 30 $Eloquent JS was my intro to programming as a way of thinking. I've gone back to it a bunch of times and learned new things every time. A beautiful updated version is worth every penyn.
- Carlos Iriarte - 30 $Your first book is amazing. This is my humble way of saying thanks for creating amazing things such as CodeMirror and the first edition of this book.
- Dav Clark - 30 $
- Isak Dalström - 30 $
- Richard Yeh - 30 $
- @fwg - 20 €Eloquent Javascript has been an invaluable resource both to my learning and teaching of JS over the years.
- Anonymous - 20 €The only useful JavaScript Tutorial ;-)
- Anonymous - 20 €
- Anonymous - 20 €
- Anonymous - 20 €The first edition is a wonderful book, something I recommend to my employees as a must-read. Thanks for all the hard work!
- Anonymous - 20 €
- Anonymous - 20 €
- Anonymous - 20 €
- Anonymous - 20 €
- Anonymous - 20 €
- Bundyo - 20 €
- d4kris - 20 €
- Florian Heinisch - 20 €
- Frank Taillandier - 20 €The Web need this. Every good JS developer I have met said he had read your book. An updated edition will continue help everyone writing good Javascript.
- Jason Smith - 20 €My most preferred task "author's discretion"; but a Node.js section will be great. Remember, the API documentation indicates the permanence ("stability") of the API.
- Jonas Bredenfeldt - 20 €
- Karl Westin - 20 €It's the #1 i recommend to people wanting to learn JS
- larz - 20 €
- Martin S. - 20 €
- Massimiliano Filacchioni - 20 €
- Michał Laskus - 20 €
- nerdess - 20 €
- Oliver Anan - 20 €
- Owen Densmore - 20 €My preferred task really is "all"!
- Panos Astithas - 20 €
- Sonny Piers - 20 €Eloquent JavaScript is one of the best resource to learn programming and JavaScript. I'd love to read a new edition.
- Tiago Rodrigues - 20 €I recommend Eloquent JavaScript to everyone wanting to learn JS as the first book they look at and people usually love it. Hopefully a new edition will get even more people to learn JS!
- Tom Alterman - 20 €
- Victor Hooi - 20 €Loved the first book. - -Go hard mate! =)
- Vit Brunner - 20 €Whenever someone tells me they'd like to learn programming, I point them to Eloquent Javascript!
- Adrian Schaedle - 25 $Eloquent JS is not only the greatest introduction to Javascript that exists, it's one of the most illuminating books about how to reason about your programs and how to start thinking functionally. I think it's the book responsible for most developers making the jump from curious HTML prodders to full-stop intelligent programmers.
- Andrew de Andrade - 25 $Awesome work. I recommend your book to lots of people who are new to programming. I think it is easily one of the best books out there. Thank you and thank you on behalf of everyone I've recommended it to. - -Also, thank you for Tern.js, CodeMirror and the Haskell code I have perused. I love it when people like you Brian Lonsdorf, Substack, Fogus and others help build bridges between JavaScript and functional programming programming languages like Haskell and Clojure, and grow the body of work in JavaScript code that use functional paradigms and idioms. - -Best, -Andrew -engineer @ famo.us
- Anonymous - 25 $
- Anonymous - 25 $
- Anonymous - 25 $
- Anonymous - 25 $
- Anonymous - 25 $
- Brian Kung - 25 $
- Dina Lamdany - 25 $
- G. Jason Head - 25 $
- Greg Lind - 25 $The original is such a wonderful resource. Thank you very much. Please take $20 for the project and spend the other $5 on a pint of something refreshing. You've earned it again and again.
- Hartley - 25 $Despite its age, this book provides a wonderful introduction into Javascript. Would love to see it updated for a new generation of developers.
- John Goodleaf - 25 $
- Kashif Jabbar - 25 $EJS is by far the best JavaScript book I have come across. Really looking forward to the 2nd Edition.
- Les Dougherty - 25 $I'm just past being a beginner to JavaScript, but do have older experience in Perl. I think this is a great project. Best wishes for success. I'm retired now, but will try to donate again later.
- Matt Sahr - 25 $
- Paul C - 25 $I've been exposed to Javascript for 10 years and thought I had a reasonable grasp of things but today your book confused me. This is a good thing because I realised there is an awful lot I don't know and am working through your book to start again with a better foundation.
- Sean - 25 $
- Thorin Messer - 25 $
- Tom Gamble - 25 $
- Trae - 25 $
- Udi Wertheimer - 25 $
- Yojan Shrestha - 25 $This is how education should happen to begin with. Also, love this book. Keep doing what you do.
- Yusuf Abdi - 25 $
- Anonymous - 18 €
- Anonymous - 20 $When I use eloquent javascript people hardly notice my disfigurement.
- Aaron Ackerman - 20 $
- Aaron Moodie - 20 $
- Alex Gill - 20 $
- Alvin Ashcraft - 20 $Thanks! Love the first edition.
- Andrey Fedorov - 20 $
- Anonymous - 20 $
- Anonymous - 20 $
- Anonymous - 20 $
- Anonymous - 20 $I'm going through the first edition right now. It's excellent. Can't wait for the second edition!
- Anonymous - 20 $
- Anonymous - 20 $
- Anonymous - 20 $
- Anonymous - 20 $
- Anonymous - 20 $
- Anonymous - 20 $
- Anonymous - 20 $
- Anonymous - 20 $
- Anonymous - 20 $
- Anonymous - 20 $Thanks for your great works! -The practice-based introduction is what distinguishes this book from other JS books, so I think more practical chapters would be great.
- Ariel Kirkwood - 20 $
- Chris Kottom - 20 $
- Cody Lindley - 20 $
- David Sprague - 20 $
- Dethe Elza - 20 $Hi Marijn, I can't tell you how many people I have steered to your book and online version. I still think it's the best introduction to JavaScript around, and am enthusiastic about it becoming even better.
- Drew Bell - 20 $
- Eric Baer - 20 $Reading this book helped me understand functional programming for the first time!
- Erik Swan - 20 $
- Gary Lucas - 20 $
- Griffin Alberti - 20 $
- Jeremy Schlatter - 20 $
- Jerzy Batalinski - 20 $I love the work you have done, I continue to try to improve my knowledge of objects, loops as I rushed through the book to grasp the concepts as fast as possible. Now working with backbone.js I often refer to the Javascript fundamentals in your book. Plus the Aunt with Cats email is epic. Great work, continue doing awesome stuff :)
- Jesus Alvarez - 20 $The first edition was amazing. Can't wait for the second edition!
- John DeHope - 20 $I appreciate your work, Marijn, and the way you're funding and licensing it.
- John M - 20 $Javascript is the world's most popular language -- I'd love to write it as gracefully and minimally as possible -- thanks!
- Jorge L Garcia - 20 $Awesome book, awesome legacy, let's keep it alive.
- Joseph Clay - 20 $
- Justin Lowery - 20 $
- Klemen Slavic - 20 $This guy.
- Kyle Alexander Thompson - 20 $
- LadyMartel - 20 $
- Mauricio Mercado - 20 $
- Millard Ellingsworth - 20 $I paid about $20 for the paper version and I'm happy to kick in another $20 to see an updated, generally available version. It's excellent work that I have referred others to multiple times.
- Patrick Taylor - 20 $
- Patrick Teglia - 20 $Loved the first book, really hope you make your goal!
- Pierre-Francois Laquerre - 20 $
- pmThompson - 20 $I initially wanted to mark task:None, but I was worried that un-targeted funds could become un-directed work. Besides, *EVERYONE* needs to hire an artist ;-) -
- RicheTheBuddha - 20 $Thank you for working to update this excellent work and putting it out there in a free way. Thank you for the first edition!
- Sanket Patel - 20 $I am always in a hurry and first time when I read this book very quick I felt kind of satisfaction and enlightenment with this book more than with any other book...I knew that I had to read this book again and I did...The title Eloquent is not an overstatement...Really the modern introduction on javascript...The definite way to go for writing a book...not only best javascript beginner book but best programming book for any programmer wanna be...Thanks for this book!
- Scott Carpenter - 20 $Thank you!
- Scott Lesser - 20 $
- Sean Diamond - 20 $
- Sergii - 20 $
- TehShrike - 20 $Chapter 6 helped me wrap my head around functional programming. I am in your debt!
- Timur - 20 $
- Tom Keeler - 20 $
- Tyler Cipriani - 20 $
- Vish Jiawon - 20 $
- Zev Averbach - 20 $
- Alex Gyoshev - 15 €Rock on!
- Alun Davey - 15 €
- Anonymous - 15 €
- Anonymous - 15 €
- Anonymous - 15 €
- Anonymous - 15 €
- Anonymous - 15 €
- Anonymous - 15 €It would be incredibly great to be able to not only read the book, but use the book sandbox on iPhone. -With v1 AFAIK it was not practical and/or not working. -An offline manifest would also be great for smartphones, in particular the (upcoming) Firefox OS based ones.
- Anonymous - 15 €
- Jérémy Ozog - 15 €
- Karl Inglis - 15 €
- manichord - 15 €
- Pedro Figueiredo - 15 €
- Peter Zuidhoek - 15 €
- Tchesko - 15 €Great job. It's better than all the other books i have bought far away... Signed : A french reader.
- Victor Cazacov - 15 €
- Stefan Bauckmeier - 13 €
- Ben Frank Lodge - 12 €Have fun writing the new book. -I look forward to the results!
- @partyfists - 15 $The book that taught me how Javascript can be beautiful and well written needs this update and I am proud to support it.
- Anonymous - 15 $I read a large part of your book online. It was awesome, I intended to buy a hardcopy but never did. Hope this compensates you adequately and get you running on the second edition. Looking forward to it. -
- Anonymous - 15 $
- Anonymous - 15 $
- Anonymous - 15 $Like the first book, so would use the second book. Good luck on the rewrite!
- Anthony Yu - 15 $Thanks bro. You're teaching this baby bird how to take flight. I guess, "Thanks Daddy!" would be more appropriate!
- Jason Laster - 15 $thanks
- Jim Hart - 15 $
- Karl J. Smith - 15 $
- Miroki - 15 $
- Patrick - 15 $
- Ramkumar - 15 $
- Shawn Searcy - 15 $
- Sheldon - 15 $
- Alexander Dobbert - 10 €
- Amr Malik - 10 €
- Andrew Ducker - 10 €
- Andrew Price - 10 €
- Andrew Whitehouse - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €For beginners chapter 6-8 are where you are losing them, when it comes to functions, recursion, method chaining, oop and so on within a few pages.
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €yay i am so excited
- Anonymous - 10 €
- Anonymous - 10 €
- Anonymous - 10 €
- bastian - 10 €
- Bema - 10 €Thanks for doing this excellent work, I read the first edition online and leaned a lot. -I would like to help translating it to Portuguese, I am Brazilian, so if by any chance you are contacted by other Brazilians willing to do the same job please put us in contact so we can build a working group for this task. - -Best wishes, -Bema - -
- blake johnson - 10 €this is one of the best programming books ever written regardless of language, thanks!
- Bundyo - 10 €
- Colm Heaney - 10 €I love this book! Still working my way through the first edition but it has been good enough to convince me to contribute to the 2nd. Great job man!
- Daniel Beck - 10 €Eloquent Javascript is a really good book. Thanks for the hard work.
- Dinis Correia - 10 €
- Fabian Straubinger - 10 €
- Ferdinand Salis-Samaden - 10 €
- Garren Smith - 10 €Keep up the great work. I loved the first book.
- gasper - 10 €yay for giving money to people to do cool stuff!
- Greg McCarvell - 10 €
- GZiolo - 10 €Good luck! Waiting for updated version.
- Jag - 10 €Great work!
- Jan Tiedemann - 10 €
- Jiří Prokop - 10 €I hope this book will help to change world! :-)
- jjjmmmhhh - 10 €
- Kevin Dangoor - 10 €
- Lech Rzedzicki - 10 €Hi. - -Thank you for all the work so far, looking forward to 2nd edition.
- Loucas Papantoniou - 10 €
- Lucas - 10 €Thank you!
- Manuel Kiessling - 10 €Eloquent JavaScript is one of the very few books I did not sell before me and my family moved into another city last year. I really love having it.
- Mark Robson - 10 €
- Matthew Lancey - 10 €
- Maurizio Mangione - 10 €
- Paddy O'Hanlon - 10 €I learned so much from the first book. It broke down many JavaScript walls for me. Really looking forward to the new edition!
- Patrick Te Tau - 10 €I'd prefer as much of a functional programming lean as possible in a rewrite. But, you know, it's your book ;)
- Pedro Teixeira - 10 €Eloquent JavaScript has long been my go-to recommendation for people that want to have a good coverage of modern JavaScript programming. I'm really happy that the author is planning a second revision.
- Piotr Migdał - 10 €A great starting point to learn JavaScript!
- pixelkritzel - 10 €
- PurplePilot - 10 €
- qgi - 10 €
- Richard - 10 €Great book! I'm a beginner but it helped me understand so much about Javascript and the fundamentals of the language.
- Rob Campbell - 10 €It's been 6 whole years. JavaScript and the DOM have changed quite a bit since then. This new version should address that.
- Roland Tanglao - 10 €
- Rémi Gérard-Marchant - 10 €
- Stefan Lodders - 10 €
- Stuart Cuthbertson - 10 €
- Thomas L. - 10 €Loved the first one. Keep up the good work!
- Volodymyr Prokopyuk - 10 €
- whostolemyhat - 10 €Great book, incredibly useful!
- Juan Carlos Lopez B - 12 $I used your guide to learn JavaScript, and it was great! Thanks alot!
- Anonymous - 11 $
- Adam Khorshid - 10 $
- Al Billings - 10 $
- Alejandro Garcia - 10 $
- Andriy G - 10 $
- Anonymous - 10 $
- Anonymous - 10 $You have written an excellent book on my favourite language... You havema made a difference to my life... I can afford only 10 $.. good luck
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $I used the original Eloquent Javascript to learn JS and I am grateful to it. It's a classic that was made available to everyone. I can't wait to see the next version and am happy to support its continued existence as a goto for learning JS especially as JS becomes more and more ubiquitous.
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $still going through the (very well-done) first version, excited to see the updates!
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $Thank you so much for your work. I've been working with javascript daily for about a year now, and your tutelage, especially in the way of data structures and programming paradigms, has been a huge boon to my abilities! Keep up the terrific work.
- Anonymous - 10 $Thanks a lot for writing this.
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $Eloquent Javascript served as my intro to JS and it's been with me the whole way. Thanks a bunch.
- Anonymous - 10 $
- Anonymous - 10 $
- Anonymous - 10 $
- Audrey - 10 $Great work with 1st edition. Please keep up with the good work.
- Brian FitzGerald - 10 $I think the current intro is beautiful, don't change it too much :) Thanks for your efforts!
- C J Silverio - 10 $
- Chris Bobek - 10 $
- Collin Garvey - 10 $
- Cory Gagliardi - 10 $Eloquent JavaScript is my favorite JS book. I'm looking forward to seeing an update.
- Cris Noble - 10 $
- CyberAP - 10 $First edition helped me a lot. Looking forward for a new one.
- Dan Lourenco - 10 $
- davewhat - 10 $
- Don Burks - 10 $The first edition is how I get people excited about JS.
- Eduardo Nieto - 10 $Your book has been invaluable to me. I recommend it anytime I can, especially to people learning how to program. Thanks a lot!
- Eric Schiller - 10 $This book taught me Javascript.
- erutan - 10 $
- Ivan Shornikov - 10 $Great idea!
- James - 10 $
- James Herdman - 10 $
- Jan - 10 $
- Jason - 10 $
- Jeb Schiefer - 10 $
- John Caruso - 10 $
- Leandro D'Onofrio - 10 $JavaScript FTW!
- max borghino - 10 $
- Mayuresh Kathe - 10 $
- Megan Taylor - 10 $Eloquent JavaScript got me over a lot of my early hurdles, and is still a resource I return to. I've also recommended it to friends who were looking to learn programming.
- Michael Allan - 10 $Heck, I'd have paid just to have one of the bugs speak my bubble! *So* much cooler than any Kickstarter bonus. Also, can't wait to see an updated version of this classic :-)
- Michael Paulukonis - 10 $I loved the original web-version, and have recommended it to colleagues learning JS. - -For the record, I was always impressed with the in-page editor/executor environment. Being able to test ideas in the same location I was reading about kept me from jarring context-shifts. Making the sample code vanilla-js is a good idea, though.
- mike maxwell - 10 $
- Mohammed Ameen - 10 $
- Nick Ketter - 10 $Have been wanting to learn Javascript for a while and something about this seems like a better use of any $ I'd spend on a finished book.
- Rick Yentzer - 10 $
- Rob Bartholme - 10 $
- Robert Brimhall - 10 $
- Rolf - 10 $I bought your first book and it was great :-)
- Sachin Palewar - 10 $I took Derek Sivers's advice and started learning Javascript recently with your online book. I am onto 3rd chapter now and already feel that your book is useful not only for novice programmers but for experienced ones as well. - -I am mighty impressed with current version and can only imagine what can you do with a re-written version. All the best. Also your background animation rocks.
- Sandy - 10 $I love the first edition. Your writing made me rediscover the joy of learning to code.
- Scott - 10 $
- Shaun Santa Cruz - 10 $
- Steve Kinney - 10 $The first edition of the book really helped me wrap my head around JavaScript. I'm looking forward to the second version.
- Tessa Thornton - 10 $
- Tony Ching - 10 $Thank you for teaching programming via JavaScript. I look forward to buying the book again.
- Varun Raj - 10 $Thank Tou Marijn... I would like to express my gratitude for the work you are doing. Great Job.
- Zachary Freeman - 10 $Can't wait for the new version!
- 谢彪 - 10 $Love your book and open source works <3 :-)
- Anonymous - 8 $
- Anonymous - 8 $
- Anonymous - 8 $Congratulations for the initiative and the book. I home you get all the money you need for write this second version and make an ePub version too :)
- Farid Neshat - 8 $
- Patrick Stapleton - 8 $@gdi2290
- vobi - 6 €
- Robbie Edwards - 8 $
- Anonymous - 5 €
- Anonymous - 5 €
- Anonymous - 5 €
- Anonymous - 5 €
- Anonymous - 5 €
- Anonymous - 5 €
- Anonymous - 5 €
- Anonymous - 5 €
- Anonymous - 5 €
- Anonymous - 5 €
- Chris Mear - 5 €
- Dima Samodurov - 5 €
- Emilian Losneanu - 5 €
- Francisco Fernández Castaño - 5 €
- Harry Moreno - 5 €Loved the original. Consider including jquery, it's a pretty standard requirement these days for a JS programmer.
- Ian Rose - 5 €Thanks and looking forward to the release!
- Jacob - 5 €
- Jan Aagaard - 5 €
- Lasse - 5 €
- Mario Estrada - 5 €
- Peter Janotta - 5 €
- Ralf Puchert - 5 €
- Roberto Ferro - 5 €
- Sergi - 5 €Awesome stuff, make it awesomer!
- Slavo Ingilizov - 5 €Rock on dude. You're doing an awesome thing.
- Staale Nataas - 5 €
- Thomas Herzog - 5 €
- Wojtek - 5 €
- Wolfgang - 5 €
- Anonymous - 4 €
- Anonymous - 5 $
- Ali Ukani - 5 $
- Anonymous - 5 $
- Anonymous - 5 $
- Anonymous - 5 $
- Anonymous - 5 $
- Anonymous - 5 $
- Anonymous - 5 $
- Anonymous - 5 $
- Anonymous - 5 $
- Anonymous - 5 $
- Anonymous - 5 $
- Ben Boarder - 5 $The first edition of 'Eloquent JavaScript' helped me at the beginning of my development career so much, that I hope for the next generation of JavaScripters to gain the same solid skills.
- Christopher Lamm - 5 $
- Colin Gourlay - 5 $The original is a classic. Excited to share the new edition with new JS devs!
- curtis gagliardi - 5 $
- CYRIL DAVID - 5 $
- Dhruv Chandna - 5 $
- Eugene Bulkin - 5 $
- Garth Johnson - 5 $
- Jack Crish - 5 $Thank you for the first book. Glad to see you're working on an update. Good luck with your progress.
- Loren Saele - 5 $Enjoyed the first edition and looking forward to an updated second edition.
- Lucas D - 5 $Amazing contribution to the javascript community.
- Luiz Americo Pereira Camara - 5 $
- Paul Jaworski - 5 $Probably the best introduction to Javascript I have encountered!
- Peter M - 5 $
- Ron Hamenahem - 5 $
- Stanislav - 5 $EloquentJS rocks !
- Ted Young - 5 $
- Thomas - 5 $Can't wait!
- tucaz - 5 $
- wannianchuan - 5 $Like your book, look forward to the second edition.
- Wyatt - 5 $Yours is the best JS resource available. Keep up the good work.
- Yorgos Kopanias - 5 $I am only half-way the 1st edition and I think you have done a great job! Thank you!
- Anonymous - 4 $
- Anonymous - 3 €
- Guido Corradi - 3 €Good job! : )
- Radek P - 3 €
- Anthony Mastrean - 3 $
- Anonymous - 2 €
- Anonymous - 2 €
- Anonymous - 2 $
- Toby - 2 $
- Tori Hamblin - 2 $
- Anonymous - 1 €
- Anonymous - 1 €
- bzlm - 1 €hi guys
- Krzysztof B. Wicher - 1 €
- robalarcon - 1 €I have never read the first edition, but I have used a lot of your software and I'll looking forward for this edition
- Anonymous - 1 $
- Farid Neshat - 1 $
- s5s5 - 1 $good job
- Tapan Shah - 1 $Good luck & Thank you
-
- diff --git a/docs/backers3.html b/docs/backers3.html deleted file mode 100644 index 2fd7a2a52..000000000 --- a/docs/backers3.html +++ /dev/null @@ -1,802 +0,0 @@ - - - - - Eloquent JavaScript :: 3rd Edition Backers - - - - - -
-

List of Backers
Eloquent JavaScript, 3rd Edition

- -

These are the wonderful people and organizations who have -contributed towards making the third edition -of Eloquent JavaScript happen.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1000 €
1000 €
Belma Gaukrodger200 €
Christopher Bejnar150 €
Dennis van Leeuwen150 €
Tim Caswell111 €
Anonymous100 €
John W. Bruce100 €
Liam Newman100 €
Morgan Roderick100 €
Peter-Paul van Gemerden100 €
Christopher Astfalk75 €
Lord Omlette75 €
Edward Cheng66 €
t.m.k.65 €
Charles McMackin60 €
Alexander Bartolo50 €
Allie Jones50 €
Anonymous50 €×5
Anthony Lupinetti50 €
Ash Kumar50 €
Blaine Cook50 €
Brandon Yanofsky50 €
Brian Jones50 €
Chandler50 €
Chris Wright50 €
Chrissy Gonzalez50 €
Connie Kephart50 €
Daisuke Horie50 €
David Sommers50 €
Dwayne Gardner50 €
Eleanor Weigert50 €
Florencia Herra Vega50 €
G C Glaser50 €
Geoffrey Kidd50 €
jake.ro50 €
JS Cheerleader50 €
Jaime Silvela50 €
Jarren Bird50 €
Jeremy Whitbred50 €
Jiaxing Wang50 €
Joe Paris50 €
Johan Gunawan50 €
José de Leon50 €
Juri Leino50 €
Kei Ito50 €
Lewis Liu50 €
Louis Cheung50 €
Mathieu Jouhet50 €
Max Zhuravsky50 €
Mirco Strässle50 €
Nikita Skrebets50 €
Peter Persson50 €
Phil Aylesworth50 €
Pitch Arunsuwannakorn50 €
Raphael LANG50 €
Richard Curzon50 €
Scott Sauyet50 €
Stephen Band50 €
Techit U.50 €
Tim Scheer50 €
Tom MacWright50 €
Tommy Williams50 €
Vincent Sisk50 €
aravind mohan50 €
jsm50 €
Martin Bohgard45 €
Anonymous40 €
Don McCurdy40 €
Steve Albers33 €
Anonymous30 €
Charlie Orford30 €
Paul Jacobson30 €
Steven Mitts26 €
Aaron Williams25 €
Adi Dahiya25 €
Alex Melville25 €
Alex Moldovan25 €
Alexis La Porte25 €
Alfonso Higuera Gamboa25 €
Ali Smith25 €
Alper25 €
Amberley Romo25 €
Anonymous25 €×23
August G Dombrow25 €
Bas Hintemann25 €
Bastian Albers25 €
Biniam Bekele25 €
Bradley Ayers25 €
Bálint Kléri25 €
C.H.A.D.25 €
Camilo25 €
Charles Stanhope25 €
Chris Aves25 €
Christian Simonsen25 €
Christophe Pouliot25 €
Colby25 €
Constantinescu Nicolaie25 €
Coy Sanders25 €
Craig Maloney25 €
Cristina Suarez Corzo25 €
Cyril Pierron25 €
D C Jackson25 €
Daniel Kocoj25 €
Darren Torpey25 €
Dave King25 €
David Bremner25 €
David Lukeš25 €
David Owens25 €
David Shirey25 €
Dennis Martinez25 €
Donald Craig25 €
Dor Tzur25 €
Drew Bollinger25 €
Dumoulin Pierre25 €
ECAD Labs Inc.25 €
Eamonn Bell25 €
Elena Santana25 €
Elijah Dorman25 €
Emil Redzic25 €
Eric Haseltine25 €
Ernest D Weems25 €
Ethan Sherbondy25 €
Ferdinand Salis-Samaden25 €
Flaki25 €
Fran Iglesias25 €
Francesco Agosti25 €
Frank Siebenlist25 €
Frederik Eichler25 €
Henrik Saksela25 €
Ignacy25 €
James Taylor25 €
Jeff Whitfield25 €
Jerry Mao25 €
Jesse Printz25 €
Johan25 €
John Meredith25 €
Jonathan So25 €
Kashyap25 €
Kenji Rikitake25 €
Kenyasoweta Bowman25 €
Kovács László25 €
LIONEL E RAMOS25 €
Lev Izraelit25 €
Lev Izraelit25 €
Luciano Mammino25 €
Maarten25 €
Marc Farra25 €
Marcus Weiner25 €
Marko Bilal25 €
Marshall25 €
Mattie Kenny25 €
Michael Saxton25 €
Michael Terry25 €
Mikko Tolmunen25 €
Muhammad Abdusamad25 €
Niels Gregersen25 €
Niles Turner25 €
Nikolaus Klumpp25 €
Octavian Dobrescu25 €
Olivier Forget25 €
Omari Rose25 €
Paul Haddad25 €
Pedro Tavares25 €
Pelle Lundgren25 €
Peter Weber25 €
Phillip Bruk25 €
Pietro Menna25 €
Rocio Chongtay25 €
Ron Male25 €
Rosario Fusca25 €
Roy Grubb25 €
Ryan Paul25 €
SHI Wenhao25 €
Safa Yasin Yıldırım25 €
Samuel Durkin25 €
Sathya Gunasekaran25 €
Stephan Seidt25 €
Steve Ingram25 €
Steven Landman25 €
Stuart Kennedy25 €
SunnyByte25 €
Tim Richards25 €
Tyler Cipriani25 €
Will Schmidt25 €
William Harris25 €
YOU,ZONGYAN25 €
Yuya Saito25 €
Zhivko Siderov25 €
jenn schiffer25 €
theo bousquet25 €
www.actioncy.co.uk25 €
Anonymous< 25×107
-
- diff --git a/docs/code b/docs/code deleted file mode 120000 index 2edff2610..000000000 --- a/docs/code +++ /dev/null @@ -1 +0,0 @@ -../code \ No newline at end of file diff --git a/docs/css/ejs.css b/docs/css/ejs.css deleted file mode 100644 index 7b3dfca8e..000000000 --- a/docs/css/ejs.css +++ /dev/null @@ -1,461 +0,0 @@ -@font-face { - font-family: 'Cinzel'; - font-style: normal; - font-weight: 700; - src: local('Cinzel-Bold'), url(../font/cinzel_bold.woff) format('woff'); -} - -@font-face { - font-family: 'PT Mono'; - font-style: normal; - font-weight: 400; - src: local('PT Mono'), local('PTMono-Regular'), url(../font/pt_mono.woff) format('woff'); -} - -html, body { - padding: 0; - margin: 0; -} - -body { - font-family: Georgia, 'Nimbus Roman No9 L', 'Century Schoolbook L', serif; - font-size: 20px; - line-height: 1.45; - color: black; - background: white; -} - -article { - margin: 0 auto; - max-width: 35em; - padding: 2em 1em 5em; - position: relative; - overflow-wrap: break-word; -} - -nav { - display: block; - height: 0; - text-align: right; -} - -nav a { - font-size: 80%; - color: #aaa !important; - text-decoration: none !important; -} - -a.subtlelink { - color: black !important; - text-decoration: none !important; -} - -pre { - padding: 5px 0 5px 15px; - line-height: 1.35; - margin: 1rem 0; - max-width: 100%; - overflow-x: auto; -} - -pre[data-language=javascript] { - cursor: pointer; -} - -p:hover a.p_ident:after, pre:hover a.c_ident:after, h2:hover a.h_ident:after, h3:hover a.i_ident:after { - content: "¶"; - font-family: 'Cinzel', Georgia, serif; - color: #888; - font-size: 17px; - position: absolute; - right: -10px; -} - -@media screen and (max-width: 800px) { - p:hover a.p_ident:after, pre:hover a.c_ident:after, h2:hover a.h_ident:after, h3:hover a.i_ident:after { - right: 5px; - } - - blockquote p:hover a.p_ident:after { - right: -15px; - } -} - - -code, pre, .CodeMirror { - font-size: 18px; - font-family: 'PT Mono', monospace; -} - -code { - padding: 0 2px; -} - -h1, h2, h3 { - font-family: 'Cinzel', Georgia, serif; - font-weight: 700; - margin: 1rem 0; - letter-spacing: 2px; -} - -h1 { - font-size: 130%; -} -h2 { - font-size: 115%; -} -h3 { - font-size: 100%; -} - -pre.cm-s-default, p, h2, h3 { - margin-right: -30px; - padding-right: 30px; -} - -@media screen and (max-width: 800px) { - pre.cm-s-default, p, h2, h3 { - margin-right: 0; - padding-right: 0; - } -} - -a, a:visited, a:active { - text-decoration: none; - color: #467; -} - -a:hover { - text-decoration: underline; -} - -ol { - margin: 1em 0; - padding: 0; - counter-reset: li; -} - -ol li { - margin: 0 0 0 40px; - padding: 0; - list-style: none; - position: relative; -} - -ol li:before { - content: counter(li) "."; - counter-increment: li; - position: absolute; - width: 2em; - text-align: right; - left: -2.5em; top: 1px; - font-size: 90%; -} - -ol li p { - margin: 0; -} - -.chap_num { - font-size: 60%; - color: #aaa; - margin-top: -.7em; - display: block; -} - -blockquote { - margin: 0 0 0 3em; - padding: 0; - position: relative; - font-size: 85%; -} - -blockquote p { - color: #333; -} - -blockquote:before { - content: '“'; - position: absolute; - left: -.5em; -} - -blockquote p:last-of-type:after { - content: '”'; -} - -blockquote footer { - position: relative; - margin-left: 1em; -} - -p + footer { - margin-top: -.5em; -} - -blockquote footer cite { - font-style: italic; -} - -blockquote footer:before { - content: '—'; - position: absolute; - left: -1em; -} - -.editor-wrap { - margin: 1rem 0; - position: relative; - -moz-transition: margin-left .5s ease-out, margin-right .5s ease-out; - -webkit-transition: margin-left .5s ease-out, margin-right .5s ease-out; - -o-transition: margin-left .5s ease-out, margin-right .5s ease-out; - transition: margin-left .5s ease-out, margin-right .5s ease-out; - border-bottom: 1px solid #4ab; -} - -.sandbox-output { - border-top: 1px solid #4ab; - padding: 4px 0 4px 10px; - white-space: pre; - max-height: 25em; - overflow: auto; -} - -.sandbox-output:empty { - display: none; -} - -.editor-wrap iframe { - display: block; - border: 1px dotted #4ab; - border-top: 1px solid #4ab; - border-bottom-width: 0; - padding: 0; margin: 0; - width: 100%; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.sandbox-output pre { - margin: 0; - padding: 0; - white-space: pre-wrap; -} - -.sandbox-output-error { color: red; } -.sandbox-output-warn { color: orange; } - -.sandbox-output-etc { - color: #1a1; - background: #dfd; - cursor: pointer; - border-radius: 5px; - padding: 0 1px; -} - -.sandbox-output-prop { - color: #444; -} - -.sandbox-output-etc-block { - display: inline-block; - vertical-align: top; -} - -.sandbox-output-etc-block table { - border-collapse: collapse; -} -.sandbox-output-etc-block table td { - vertical-align: top; - white-space: pre-wrap; - font-family: 'PT Mono', monospace; -} -.sandbox-output-etc-block table td:first-child { - text-align: right; -} - -.sandbox-menu { - position: absolute; - z-index: 19; - right: -13px; top: -1px; - cursor: pointer; - font-size: 80%; - overflow: hidden; - width: 10px; - border-top: 2px solid #4ab; - height: 2px; - border-bottom: 6px double #4ab; -} - -.sandbox-open-menu { - font-family: tahoma, arial, sans-serif; - position: absolute; - background: white; - border: 1px solid #aaa; - box-shadow: 2px 2px 10px rgba(0, 0, 0, .2); - padding: 0; - font-size: 75%; - color: black; - line-height: 1.3; - right: -9px; top: 5px; - z-index: 20; -} - -.sandbox-open-menu div { - cursor: pointer; - padding: 5px 10px; -} - -.sandbox-open-menu div:hover { - background: #bdd; -} - -/* Toned-down CodeMirror style */ - -.cm-s-default .cm-keyword, .sandbox-output-null, .sandbox-output-fun {color: #506;} -.cm-s-default .cm-atom, .sandbox-output-bool {color: #106;} -.cm-s-default .cm-number, .sandbox-output-number {color: #042;} -.cm-s-default .cm-def {color: #009;} -.cm-s-default .cm-variable-2, .cm-s-default .cm-attribute {color: #027;} -.cm-s-default .cm-variable-3 {color: #072;} -.cm-s-default .cm-comment {color: #740;} -.cm-s-default .cm-string, .sandbox-output-string {color: #700;} -.cm-s-default .cm-string-2 {color: #740;} -.cm-s-default .cm-tag, .sandbox-output-symbol {color: #170;} - -.CodeMirror { - height: auto; - line-height: 1.35; - border-top: 1px solid #4ab; - overflow-wrap: normal; -} -.CodeMirror-scroll { - max-height: 700px; -} -.CodeMirror pre { - padding: 0 4px 0 10px; -} -.CodeMirror-gutters { - border: none; - background: white; -} -.CodeMirror-linenumber { - padding: .5em 3px 0 0; - min-width: 12px; - color: #4ab; - font-size: 60%; -} - -.sandboxhint { - position: absolute; - right: -15px; - font-family: tahoma, arial, sans-serif; - font-size: 70%; - padding: 4px 8px; - background: rgb(220, 220, 220); - color: white; - border-radius: 5px; -} - -@media screen and (max-width: 800px) { - .sandboxhint { - right: 5px; - } -} - -.sandboxhint:before { - position: absolute; - width: 0; height: 0; - border-top: 6px solid transparent; - border-bottom: 6px solid transparent; - border-right: 12px solid rgb(220, 220, 220); - top: 6px; - left: -11px; - content: ''; -} - - -figure { - max-width: 640px; - margin: 0 30px; -} - -figure.chapter { - text-align: center; - margin: 3em 0 2em; -} - -figure.chapter img { - max-width: 75%; -} - -figure.framed img { - border-radius: 50%; - border: 4px double #666; -} - -figure.square-framed img { - border-radius: 30px; - border: 4px double #666; -} - -span.keyname { font-variant: small-caps } - -@media screen and (max-width: 500px) { - figure { - margin: 0; - } -} - -figure img { - max-width: 100%; -} - -div.solution:before { - content: "» Display hints..."; -} - -div.solution { - color: #156; - cursor: pointer; -} - -div.solution-text { - display: none; -} - -div.solution.open:before { - content: ""; -} - -div.solution.open { - cursor: default; -} - -div.solution.open div.solution-text { - display: block; -} - -td { - vertical-align: top; -} - -td + td { - padding-left: 1em; -} - -table { - margin-left: 15px; -} - -sub, sup { - line-height: 1; -} - -sub { - font-size: 60%; -} - -sup { - font-size: 70%; -} diff --git a/docs/css/game.css b/docs/css/game.css deleted file mode 100644 index bb777788a..000000000 --- a/docs/css/game.css +++ /dev/null @@ -1,24 +0,0 @@ -.background { background: rgb(52, 166, 251); - table-layout: fixed; - border-spacing: 0; } -.background td { padding: 0; } -.lava { background: rgb(255, 100, 100); } -.wall { background: white; } - -.actor { position: absolute; } -.coin { background: rgb(241, 229, 89); } -.player { background: rgb(64, 64, 64); } - -.game { - overflow: hidden; - max-width: 600px; - max-height: 450px; - position: relative; -} - -.lost .player { - background: rgb(160, 64, 64); -} -.won .player { - box-shadow: -4px -7px 8px white, 4px -7px 8px white; -} diff --git a/docs/css/paint.css b/docs/css/paint.css deleted file mode 100644 index 159faddf4..000000000 --- a/docs/css/paint.css +++ /dev/null @@ -1,13 +0,0 @@ -.picturepanel { - width: -webkit-fit-content; - width: -moz-fit-content; - width: -ms-fit-content; - width: fit-content; - max-width: 500px; - max-height: 300px; - border: 2px solid silver; - overflow: auto; - position: relative; -} -.picturepanel canvas { display: block; } -.toolbar > * { margin-right: 5px; } diff --git a/docs/empty.html b/docs/empty.html deleted file mode 100644 index c2a57ea02..000000000 --- a/docs/empty.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/errata.html b/docs/errata.html deleted file mode 100644 index 6bb107bef..000000000 --- a/docs/errata.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Eloquent JavaScript :: Errata - - - - - -
- -

Errata
Eloquent JavaScript, 3rd Edition

- -

These are the known mistakes in the third edition -of the book. For errata in the first edition, -see this -page. For the second edition, -see this -page. To report a problem that is not listed -here, send me an email.

- -

Issues whose page number is followed by an ordinal number are only -present up to the print denoted by that number. I.e. those followed by -“1st” were fixed in the second print.

- -

Chapter 2

- -

Page 34 (1st) Updating Bindings Succintly: Where it -says counter- it should be counter--.

- -

Chapter 5

- -

Page 91 Composability: Due to an initial -mistake in the script data set, the results of the computations on -this page differ from the ones with the current, corrected data. The -average year for living scripts should be 1165, the average for -non-living scripts should be 204.

- -

Chapter 6

- -

Page 111 (2nd) Inheritance: In the second -paragraph below the example code, instead of “content -method”, the text should say “element function”. - -

Chapter 8

- -

Page 134 (2nd) Error Propagation: In the third -paragraph of the section, a function promptInteger is -referred to. The function is actually -called promptNumber, and the word “whole” should be -dropped from the sentence (it accepts non-whole numbers, too).

- -

Chapter 10

- -

Page 168 (1st) Modules as Building Blocks: In “each -needs it own private scope“, it should say “its own private -scope“.

- -

Chapter 14

- -

Page 234 (2nd) Creating Nodes: In the -code, “edition” is misspelled as “editon”.

- -

Chapter 15

- -

Page 258 Load Event: The description of -the beforeunload claims that you just need to return a -string from your event handler. For handlers registered -with addEventListener you, in fact, need to -call preventDefault and set a returnValue -property to get the warn-on-leave behavior.

- -

Chapter 16

- -

Page 285 (2nd) Pausing the Game: The text -refers to the arrow binding, where it should -say arrowKeys.

- -

Chapter 20

- -

Page 369 (1st) Directory -Creation: MKCOL stands for “make collection”, not “make -column” as the book claims.

- -

Chapter 21

- -

Page 373 HTTP Interface: There is a -superfluous closing brace at the end of the example JSON snippet.

- -

Exercise Hints

- -

Page 414 A Modular Robot: -The dijkstrajs package name is misspelled -as dijkstajs. - -

diff --git a/docs/example/bert.json b/docs/example/bert.json deleted file mode 100644 index c943c0942..000000000 --- a/docs/example/bert.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Bert", - "spouse": "example/suzie.json" -} diff --git a/docs/example/data.txt b/docs/example/data.txt deleted file mode 100644 index 2a5a69639..000000000 --- a/docs/example/data.txt +++ /dev/null @@ -1 +0,0 @@ -This is the content of data.txt diff --git a/docs/example/fruit.json b/docs/example/fruit.json deleted file mode 100644 index b54ea36b7..000000000 --- a/docs/example/fruit.json +++ /dev/null @@ -1,3 +0,0 @@ -{"banana": "yellow", - "lemon": "yellow", - "cherry": "red"} diff --git a/docs/example/fruit.xml b/docs/example/fruit.xml deleted file mode 100644 index d932c647a..000000000 --- a/docs/example/fruit.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/docs/example/message.html b/docs/example/message.html deleted file mode 100644 index cf92826f7..000000000 --- a/docs/example/message.html +++ /dev/null @@ -1,35 +0,0 @@ - -Example form target - - - - - - -

(Note that this page is just an illustration. No actual message was delivered anywhere.)

- - - - diff --git a/docs/example/muriel.json b/docs/example/muriel.json deleted file mode 100644 index 921f40d45..000000000 --- a/docs/example/muriel.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "Muriel" -} diff --git a/docs/example/submit.html b/docs/example/submit.html deleted file mode 100644 index 6607f2275..000000000 --- a/docs/example/submit.html +++ /dev/null @@ -1,22 +0,0 @@ - -Example form target - - - -

You submitted...

- -

-
-
-
-
diff --git a/docs/example/suzie.json b/docs/example/suzie.json
deleted file mode 100644
index 1e47fac94..000000000
--- a/docs/example/suzie.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "name": "Suzie",
-  "spouse": "example/bert.json",
-  "mother": "example/muriel.json"
-}
diff --git a/docs/favicon.ico b/docs/favicon.ico
deleted file mode 100644
index 382b706926693fa007bfef3425deec7a3e2f265f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1406
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
ioKjy@tG?8}^JEySv6`q>eW(rXdrN%-I
-
-  
-  
-  Eloquent JavaScript
-  
-  
-
-
-
-
- -

- - Cover image - -

- -

Eloquent JavaScript
3rd edition

- -

This is a book about JavaScript, programming, and the wonders of - the digital. You can read it online here, or get your own - paperback - copy.

- -

Written by Marijn Haverbeke.

- -
-

Licensed under - a Creative - Commons attribution-noncommercial license. All code in this book - may also be considered licensed under - an MIT license.

- -

Illustrations by various artists: Cover and chapter illustrations - by Madalina Tantareanu. - Pixel art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular - expression diagrams in Chapter 9 generated - with regexper.com by Jeff - Avallone. Village photograph in Chapter 11 by Fabrice Creuzot. Game - concept for Chapter 15 by Thomas - Palef.

- -

The third edition was made possible - by 325 financial backers, most - notably and . The second edition - was supported by 454 backers, with - significant contributions - from , , - and .

-
- -
- - - - -
diff --git a/docs/js/.tern-project b/docs/js/.tern-project deleted file mode 100644 index 3a218cbc1..000000000 --- a/docs/js/.tern-project +++ /dev/null @@ -1,3 +0,0 @@ -{ - "libs": ["browser"] -} \ No newline at end of file diff --git a/docs/js/acorn_codemirror.js b/docs/js/acorn_codemirror.js deleted file mode 100644 index 729457568..000000000 --- a/docs/js/acorn_codemirror.js +++ /dev/null @@ -1,11 +0,0 @@ -(function(e,t){typeof exports==="object"&&typeof module!=="undefined"?module.exports=t():typeof define==="function"&&define.amd?define(t):e.CodeMirror=t()})(this,function(){"use strict";var e=navigator.userAgent;var t=navigator.platform;var r=/gecko\/\d/i.test(e);var i=/MSIE \d/.test(e);var n=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e);var a=/Edge\/(\d+)/.exec(e);var s=i||n||a;var o=s&&(i?document.documentMode||6:+(a||n)[1]);var l=!a&&/WebKit\//.test(e);var u=l&&/Qt\/\d+\.\d+/.test(e);var c=!a&&/Chrome\//.test(e);var f=/Opera\//.test(e);var h=/Apple Computer/.test(navigator.vendor);var p=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e);var d=/PhantomJS/.test(e);var m=!a&&/AppleWebKit/.test(e)&&/Mobile\/\w+/.test(e);var v=/Android/.test(e);var g=m||v||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(e);var y=m||/Mac/.test(t);var x=/\bCrOS\b/.test(e);var b=/win/i.test(t);var w=f&&e.match(/Version\/(\d*\.\d*)/);if(w){w=Number(w[1])}if(w&&w>=15){f=false;l=true}var k=y&&(u||f&&(w==null||w<12.11));var C=r||s&&o>=9;function S(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}var L=function(e,t){var r=e.className;var i=S(t).exec(r);if(i){var n=r.slice(i.index+i[0].length);e.className=r.slice(0,i.index)+(n?i[1]+n:"")}};function T(e){for(var t=e.childNodes.length;t>0;--t){e.removeChild(e.firstChild)}return e}function A(e,t){return T(e).appendChild(t)}function E(e,t,r,i){var n=document.createElement(e);if(r){n.className=r}if(i){n.style.cssText=i}if(typeof t=="string"){n.appendChild(document.createTextNode(t))}else if(t){for(var a=0;a=t){return s+(t-a)}s+=o-a;s+=r-s%r;a=o+1}}var W=function(){this.id=null;this.f=null;this.time=0;this.handler=R(this.onTimeout,this)};W.prototype.onTimeout=function(e){e.id=0;if(e.time<=+new Date){e.f()}else{setTimeout(e.handler,e.time-+new Date)}};W.prototype.set=function(e,t){this.f=t;var r=+new Date+e;if(!this.id||r=t){return i+Math.min(s,t-n)}n+=a-i;n+=r-n%r;i=a+1;if(n>=t){return i}}}var K=[""];function $(e){while(K.length<=e){K.push(X(K)+" ")}return K[e]}function X(e){return e[e.length-1]}function Y(e,t){var r=[];for(var i=0;i"€"&&(e.toUpperCase()!=e.toLowerCase()||ee.test(e))}function re(e,t){if(!t){return te(e)}if(t.source.indexOf("\\w")>-1&&te(e)){return true}return t.test(e)}function ie(e){for(var t in e){if(e.hasOwnProperty(t)&&e[t]){return false}}return true}var ne=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;function ae(e){return e.charCodeAt(0)>=768&&ne.test(e)}function se(e,t,r){while((r<0?t>0:tr?-1:1;for(;;){if(t==r){return t}var n=(t+r)/2,a=i<0?Math.ceil(n):Math.floor(n);if(a==t){return e(a)?t:r}if(e(a)){r=a}else{t=a+i}}}function le(e,t,r,i){if(!e){return i(t,r,"ltr",0)}var n=false;for(var a=0;at||t==r&&s.to==t){i(Math.max(s.from,t),Math.min(s.to,r),s.level==1?"rtl":"ltr",a);n=true}}if(!n){i(t,r,"ltr")}}var ue=null;function ce(e,t,r){var i;ue=null;for(var n=0;nt){return n}if(a.to==t){if(a.from!=a.to&&r=="before"){i=n}else{ue=n}}if(a.from==t){if(a.from!=a.to&&r!="before"){i=n}else{ue=n}}}return i!=null?i:ue}var fe=function(){var e="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";var t="nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111";function r(r){if(r<=247){return e.charAt(r)}else if(1424<=r&&r<=1524){return"R"}else if(1536<=r&&r<=1785){return t.charAt(r-1536)}else if(1774<=r&&r<=2220){return"r"}else if(8192<=r&&r<=8203){return"w"}else if(r==8204){return"b"}else{return"L"}}var i=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;var n=/[stwN]/,a=/[LRr]/,s=/[Lb1n]/,o=/[1n]/;function l(e,t,r){this.level=e;this.from=t;this.to=r}return function(e,t){var u=t=="ltr"?"L":"R";if(e.length==0||t=="ltr"&&!i.test(e)){return false}var c=e.length,f=[];for(var h=0;h-1){i[t]=n.slice(0,a).concat(n.slice(a+1))}}}}function ge(e,t){var r=me(e,t);if(!r.length){return}var i=Array.prototype.slice.call(arguments,2);for(var n=0;n0}function we(e){e.prototype.on=function(e,t){de(this,e,t)};e.prototype.off=function(e,t){ve(this,e,t)}}function ke(e){if(e.preventDefault){e.preventDefault()}else{e.returnValue=false}}function Ce(e){if(e.stopPropagation){e.stopPropagation()}else{e.cancelBubble=true}}function Se(e){return e.defaultPrevented!=null?e.defaultPrevented:e.returnValue==false}function Le(e){ke(e);Ce(e)}function Te(e){return e.target||e.srcElement}function Ae(e){var t=e.which;if(t==null){if(e.button&1){t=1}else if(e.button&2){t=3}else if(e.button&4){t=2}}if(y&&e.ctrlKey&&t==1){t=3}return t}var Ee=function(){if(s&&o<9){return false}var e=E("div");return"draggable"in e||"dragDrop"in e}();var Me;function Ne(e){if(Me==null){var t=E("span","​");A(e,E("span",[t,document.createTextNode("x")]));if(e.firstChild.offsetHeight!=0){Me=t.offsetWidth<=1&&t.offsetHeight>2&&!(s&&o<8)}}var r=Me?E("span","​"):E("span"," ",null,"display: inline-block; width: 1px; margin-right: -1px");r.setAttribute("cm-text","");return r}var _e;function Pe(e){if(_e!=null){return _e}var t=A(e,document.createTextNode("AخA"));var r=N(t,0,1).getBoundingClientRect();var i=N(t,1,2).getBoundingClientRect();T(e);if(!r||r.left==r.right){return false}return _e=i.right-r.right<3}var Ie="\n\nb".split(/\n/).length!=3?function(e){var t=0,r=[],i=e.length;while(t<=i){var n=e.indexOf("\n",t);if(n==-1){n=e.length}var a=e.slice(t,e.charAt(n-1)=="\r"?n-1:n);var s=a.indexOf("\r");if(s!=-1){r.push(a.slice(0,s));t+=s+1}else{r.push(a);t=n+1}}return r}:function(e){return e.split(/\r\n?|\n/)};var Oe=window.getSelection?function(e){try{return e.selectionStart!=e.selectionEnd}catch(e){return false}}:function(e){var t;try{t=e.ownerDocument.selection.createRange()}catch(e){}if(!t||t.parentElement()!=e){return false}return t.compareEndPoints("StartToEnd",t)!=0};var De=function(){var e=E("div");if("oncopy"in e){return true}e.setAttribute("oncopy","return;");return typeof e.oncopy=="function"}();var Re=null;function Ve(e){if(Re!=null){return Re}var t=A(e,E("span","x"));var r=t.getBoundingClientRect();var i=N(t,0,1).getBoundingClientRect();return Re=Math.abs(r.left-i.left)>1}var Fe={},We={};function ze(e,t){if(arguments.length>2){t.dependencies=Array.prototype.slice.call(arguments,2)}Fe[e]=t}function Be(e,t){We[e]=t}function He(e){if(typeof e=="string"&&We.hasOwnProperty(e)){e=We[e]}else if(e&&typeof e.name=="string"&&We.hasOwnProperty(e.name)){var t=We[e.name];if(typeof t=="string"){t={name:t}}e=J(t,e);e.name=t.name}else if(typeof e=="string"&&/^[\w\-]+\/[\w\-]+\+xml$/.test(e)){return He("application/xml")}else if(typeof e=="string"&&/^[\w\-]+\/[\w\-]+\+json$/.test(e)){return He("application/json")}if(typeof e=="string"){return{name:e}}else{return e||{name:"null"}}}function je(e,t){t=He(t);var r=Fe[t.name];if(!r){return je(e,"text/plain")}var i=r(e,t);if(Ue.hasOwnProperty(t.name)){var n=Ue[t.name];for(var a in n){if(!n.hasOwnProperty(a)){continue}if(i.hasOwnProperty(a)){i["_"+a]=i[a]}i[a]=n[a]}}i.name=t.name;if(t.helperType){i.helperType=t.helperType}if(t.modeProps){for(var s in t.modeProps){i[s]=t.modeProps[s]}}return i}var Ue={};function Ge(e,t){var r=Ue.hasOwnProperty(e)?Ue[e]:Ue[e]={};V(t,r)}function qe(e,t){if(t===true){return t}if(e.copyState){return e.copyState(t)}var r={};for(var i in t){var n=t[i];if(n instanceof Array){n=n.concat([])}r[i]=n}return r}function Ke(e,t){var r;while(e.innerMode){r=e.innerMode(t);if(!r||r.mode==e){break}t=r.state;e=r.mode}return r||{mode:e,state:t}}function $e(e,t,r){return e.startState?e.startState(t,r):true}var Xe=function(e,t,r){this.pos=this.start=0;this.string=e;this.tabSize=t||8;this.lastColumnPos=this.lastColumnValue=0;this.lineStart=0;this.lineOracle=r};Xe.prototype.eol=function(){return this.pos>=this.string.length};Xe.prototype.sol=function(){return this.pos==this.lineStart};Xe.prototype.peek=function(){return this.string.charAt(this.pos)||undefined};Xe.prototype.next=function(){if(this.post};Xe.prototype.eatSpace=function(){var e=this;var t=this.pos;while(/[\s\u00a0]/.test(this.string.charAt(this.pos))){++e.pos}return this.pos>t};Xe.prototype.skipToEnd=function(){this.pos=this.string.length};Xe.prototype.skipTo=function(e){var t=this.string.indexOf(e,this.pos);if(t>-1){this.pos=t;return true}};Xe.prototype.backUp=function(e){this.pos-=e};Xe.prototype.column=function(){if(this.lastColumnPos0){return null}if(a&&t!==false){this.pos+=a[0].length}return a}};Xe.prototype.current=function(){return this.string.slice(this.start,this.pos)};Xe.prototype.hideFirstChars=function(e,t){this.lineStart+=e;try{return t()}finally{this.lineStart-=e}};Xe.prototype.lookAhead=function(e){var t=this.lineOracle;return t&&t.lookAhead(e)};Xe.prototype.baseToken=function(){var e=this.lineOracle;return e&&e.baseToken(this.pos)};function Ye(e,t){t-=e.first;if(t<0||t>=e.size){throw new Error("There is no line "+(t+e.first)+" in the document.")}var r=e;while(!r.lines){for(var i=0;;++i){var n=r.children[i],a=n.chunkSize();if(t=e.first&&tr){return nt(r,Ye(e,r).text.length)}return ht(t,Ye(e,t.line).text.length)}function ht(e,t){var r=e.ch;if(r==null||r>t){return nt(e.line,t)}else if(r<0){return nt(e.line,0)}else{return e}}function pt(e,t){var r=[];for(var i=0;ithis.maxLookAhead){this.maxLookAhead=e}return t};mt.prototype.baseToken=function(e){var t=this;if(!this.baseTokens){return null}while(this.baseTokens[this.baseTokenPos]<=e){t.baseTokenPos+=2}var r=this.baseTokens[this.baseTokenPos+1];return{type:r&&r.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}};mt.prototype.nextLine=function(){this.line++;if(this.maxLookAhead>0){this.maxLookAhead--}};mt.fromSaved=function(e,t,r){if(t instanceof dt){return new mt(e,qe(e.mode,t.state),r,t.lookAhead)}else{return new mt(e,qe(e.mode,t),r)}};mt.prototype.save=function(e){var t=e!==false?qe(this.doc.mode,this.state):this.state;return this.maxLookAhead>0?new dt(t,this.maxLookAhead):t};function vt(e,t,r,i){var n=[e.state.modeGen],a={};Lt(e,t.text,e.doc.mode,r,function(e,t){return n.push(e,t)},a,i);var s=r.state;var o=function(i){r.baseTokens=n;var o=e.state.overlays[i],l=1,u=0;r.state=true;Lt(e,t.text,o.mode,r,function(e,t){var r=l;while(ue){n.splice(l,1,e,n[l+1],i)}l+=2;u=Math.min(e,i)}if(!t){return}if(o.opaque){n.splice(r,l-r,e,"overlay "+t);l=r+2}else{for(;re.options.maxHighlightLength&&qe(e.doc.mode,i.state);var a=vt(e,t,i);if(n){i.state=n}t.stateAfter=i.save(!n);t.styles=a.styles;if(a.classes){t.styleClasses=a.classes}else if(t.styleClasses){t.styleClasses=null}if(r===e.doc.highlightFrontier){e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier)}}return t.styles}function yt(e,t,r){var i=e.doc,n=e.display;if(!i.mode.startState){return new mt(i,true,t)}var a=Tt(e,t,r);var s=a>i.first&&Ye(i,a-1).stateAfter;var o=s?mt.fromSaved(i,s,a):new mt(i,$e(i.mode),a);i.iter(a,t,function(r){xt(e,r.text,o);var i=o.line;r.stateAfter=i==t-1||i%5==0||i>=n.viewFrom&&it.start){return a}}throw new Error("Mode "+e.name+" failed to advance stream.")}var kt=function(e,t,r){this.start=e.start;this.end=e.pos;this.string=e.current();this.type=t||null;this.state=r};function Ct(e,t,r,i){var n=e.doc,a=n.mode,s;t=ft(n,t);var o=Ye(n,t.line),l=yt(e,t.line,r);var u=new Xe(o.text,e.options.tabSize,l),c;if(i){c=[]}while((i||u.pose.options.maxHighlightLength){o=false;if(s){xt(e,t,i,c.pos)}c.pos=t.length;f=null}else{f=St(wt(r,c,i.state,h),a)}if(h){var p=h[0].name;if(p){f="m-"+(f?p+" "+f:p)}}if(!o||u!=f){while(ls;--o){if(o<=a.first){return a.first}var l=Ye(a,o-1),u=l.stateAfter;if(u&&(!r||o+(u instanceof dt?u.lookAhead:0)<=a.modeFrontier)){return o}var c=F(l.text,null,e.options.tabSize);if(n==null||i>c){n=o-1;i=c}}return n}function At(e,t){e.modeFrontier=Math.min(e.modeFrontier,t);if(e.highlightFrontierr;i--){var n=Ye(e,i).stateAfter;if(n&&(!(n instanceof dt)||i+n.lookAhead=t:a.to>t);(i||(i=[])).push(new Pt(s,a.from,l?null:a.to))}}}return i}function Vt(e,t,r){var i;if(e){for(var n=0;n=t:a.to>t);if(o||a.from==t&&s.type=="bookmark"&&(!r||a.marker.insertLeft)){var l=a.from==null||(s.inclusiveLeft?a.from<=t:a.from0&&o){for(var b=0;b0){continue}var c=[l,1],f=at(u.from,o.from),h=at(u.to,o.to);if(f<0||!s.inclusiveLeft&&!f){c.push({from:u.from,to:o.from})}if(h>0||!s.inclusiveRight&&!h){c.push({from:o.to,to:u.to})}n.splice.apply(n,c);l+=c.length-3}}return n}function Bt(e){var t=e.markedSpans;if(!t){return}for(var r=0;rt)&&(!i||Gt(i,a.marker)<0)){i=a.marker}}}return i}function Yt(e,t,r,i,n){var a=Ye(e,t);var s=Mt&&a.markedSpans;if(s){for(var o=0;o=0&&f<=0||c<=0&&f>=0){continue}if(c<=0&&(l.marker.inclusiveRight&&n.inclusiveLeft?at(u.to,r)>=0:at(u.to,r)>0)||c>=0&&(l.marker.inclusiveRight&&n.inclusiveLeft?at(u.from,i)<=0:at(u.from,i)<0)){return true}}}}function Qt(e){var t;while(t=Kt(e)){e=t.find(-1,true).line}return e}function Zt(e){var t;while(t=$t(e)){e=t.find(1,true).line}return e}function Jt(e){var t,r;while(t=$t(e)){e=t.find(1,true).line;(r||(r=[])).push(e)}return r}function er(e,t){var r=Ye(e,t),i=Qt(r);if(r==i){return t}return et(i)}function tr(e,t){if(t>e.lastLine()){return t}var r=Ye(e,t),i;if(!rr(e,r)){return t}while(i=$t(r)){r=i.find(1,true).line}return et(r)+1}function rr(e,t){var r=Mt&&t.markedSpans;if(r){for(var i=void 0,n=0;nt.maxLineLength){t.maxLineLength=r;t.maxLine=e}})}var or=function(e,t,r){this.text=e;Ht(this,t);this.height=r?r(this):1};or.prototype.lineNo=function(){return et(this)};we(or);function lr(e,t,r,i){e.text=t;if(e.stateAfter){e.stateAfter=null}if(e.styles){e.styles=null}if(e.order!=null){e.order=null}Bt(e);Ht(e,r);var n=i?i(e):1;if(n!=e.height){Je(e,n)}}function ur(e){e.parent=null;Bt(e)}var cr={},fr={};function hr(e,t){if(!e||/^\s*$/.test(e)){return null}var r=t.addModeClass?fr:cr;return r[e]||(r[e]=e.replace(/\S+/g,"cm-$&"))}function pr(e,t){var r=M("span",null,null,l?"padding-right: .1px":null);var i={pre:M("pre",[r],"CodeMirror-line"),content:r,col:0,pos:0,cm:e,trailingSpace:false,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var n=0;n<=(t.rest?t.rest.length:0);n++){var a=n?t.rest[n-1]:t.line,s=void 0;i.pos=0;i.addToken=mr;if(Pe(e.display.measure)&&(s=he(a,e.doc.direction))){i.addToken=gr(i.addToken,s)}i.map=[];var o=t!=e.display.externalMeasured&&et(a);xr(a,i,gt(e,a,o));if(a.styleClasses){if(a.styleClasses.bgClass){i.bgClass=O(a.styleClasses.bgClass,i.bgClass||"")}if(a.styleClasses.textClass){i.textClass=O(a.styleClasses.textClass,i.textClass||"")}}if(i.map.length==0){i.map.push(0,0,i.content.appendChild(Ne(e.display.measure)))}if(n==0){t.measure.map=i.map;t.measure.cache={}}else{(t.measure.maps||(t.measure.maps=[])).push(i.map);(t.measure.caches||(t.measure.caches=[])).push({})}}if(l){var u=i.content.lastChild;if(/\bcm-tab\b/.test(u.className)||u.querySelector&&u.querySelector(".cm-tab")){i.content.className="cm-tab-wrap-hack"}}ge(e,"renderLine",e,t.line,i.pre);if(i.pre.className){i.textClass=O(i.pre.className,i.textClass||"")}return i}function dr(e){var t=E("span","•","cm-invalidchar");t.title="\\u"+e.charCodeAt(0).toString(16);t.setAttribute("aria-label",t.title);return t}function mr(e,t,r,i,n,a,l){if(!t){return}var u=e.splitSpaces?vr(t,e.trailingSpace):t;var c=e.cm.state.specialChars,f=false;var h;if(!c.test(t)){e.col+=t.length;h=document.createTextNode(u);e.map.push(e.pos,e.pos+t.length,h);if(s&&o<9){f=true}e.pos+=t.length}else{h=document.createDocumentFragment();var p=0;while(true){c.lastIndex=p;var d=c.exec(t);var m=d?d.index-p:t.length-p;if(m){var v=document.createTextNode(u.slice(p,p+m));if(s&&o<9){h.appendChild(E("span",[v]))}else{h.appendChild(v)}e.map.push(e.pos,e.pos+m,v);e.col+=m;e.pos+=m}if(!d){break}p+=m+1;var g=void 0;if(d[0]=="\t"){var y=e.cm.options.tabSize,x=y-e.col%y;g=h.appendChild(E("span",$(x),"cm-tab"));g.setAttribute("role","presentation");g.setAttribute("cm-text","\t");e.col+=x}else if(d[0]=="\r"||d[0]=="\n"){g=h.appendChild(E("span",d[0]=="\r"?"␍":"␤","cm-invalidchar"));g.setAttribute("cm-text",d[0]);e.col+=1}else{g=e.cm.options.specialCharPlaceholder(d[0]);g.setAttribute("cm-text",d[0]);if(s&&o<9){h.appendChild(E("span",[g]))}else{h.appendChild(g)}e.col+=1}e.map.push(e.pos,e.pos+1,g);e.pos++}}e.trailingSpace=u.charCodeAt(t.length-1)==32;if(r||i||n||f||a){var b=r||"";if(i){ -b+=i}if(n){b+=n}var w=E("span",[h],b,a);if(l){for(var k in l){if(l.hasOwnProperty(k)&&k!="style"&&k!="class"){w.setAttribute(k,l[k])}}}return e.content.appendChild(w)}e.content.appendChild(h)}function vr(e,t){if(e.length>1&&!/ /.test(e)){return e}var r=t,i="";for(var n=0;nu&&f.from<=u){break}}if(f.to>=c){return e(r,i,n,a,s,o,l)}e(r,i.slice(0,f.to-u),n,a,null,o,l);a=null;i=i.slice(f.to-u);u=f.to}}}function yr(e,t,r,i){var n=!i&&r.widgetNode;if(n){e.map.push(e.pos,e.pos+t,n)}if(!i&&e.cm.display.input.needsContentAttribute){if(!n){n=e.content.appendChild(document.createElement("span"))}n.setAttribute("cm-marker",r.id)}if(n){e.cm.display.input.setUneditable(n);e.content.appendChild(n)}e.pos+=t;e.trailingSpace=false}function xr(e,t,r){var i=e.markedSpans,n=e.text,a=0;if(!i){for(var s=1;sl||C.collapsed&&k.to==l&&k.from==l)){if(k.to!=null&&k.to!=l&&p>k.to){p=k.to;m=""}if(C.className){d+=" "+C.className}if(C.css){h=(h?h+";":"")+C.css}if(C.startStyle&&k.from==l){v+=" "+C.startStyle}if(C.endStyle&&k.to==p){(b||(b=[])).push(C.endStyle,k.to)}if(C.title){(y||(y={})).title=C.title}if(C.attributes){for(var S in C.attributes){(y||(y={}))[S]=C.attributes[S]}}if(C.collapsed&&(!g||Gt(g.marker,C)<0)){g=k}}else if(k.from>l&&p>k.from){p=k.from}}if(b){for(var L=0;L=o){break}var A=Math.min(o,p);while(true){if(c){var E=l+c.length;if(!g){var M=E>A?c.slice(0,A-l):c;t.addToken(t,M,f?f+d:d,v,l+M.length==p?m:"",h,y)}if(E>=A){c=c.slice(A-l);l=A;break}l=E;v=""}c=n.slice(a,a=r[u++]);f=hr(r[u++],t.cm.options)}}}function br(e,t,r){this.line=t;this.rest=Jt(t);this.size=this.rest?et(X(this.rest))-r+1:1;this.node=this.text=null;this.hidden=rr(e,t)}function wr(e,t,r){var i=[],n;for(var a=t;a2){a.push((l.bottom+u.top)/2-r.top)}}}a.push(r.bottom-r.top)}}function Yr(e,t,r){if(e.line==t){return{map:e.measure.map,cache:e.measure.cache}}for(var i=0;ir){return{map:e.measure.maps[n],cache:e.measure.caches[n],before:true}}}}function Qr(e,t){t=Qt(t);var r=et(t);var i=e.display.externalMeasured=new br(e.doc,t,r);i.lineN=r;var n=i.built=pr(e,i);i.text=n.pre;A(e.display.lineMeasure,n.pre);return i}function Zr(e,t,r,i){return ti(e,ei(e,t),r,i)}function Jr(e,t){if(t>=e.display.viewFrom&&t=r.lineN&&tt){a=l-o;n=a-1;if(t>=l){s="right"}}if(n!=null){i=e[u+2];if(o==l&&r==(i.insertLeft?"left":"right")){s=r}if(r=="left"&&n==0){while(u&&e[u-2]==e[u-3]&&e[u-1].insertLeft){i=e[(u-=3)+2];s="left"}}if(r=="right"&&n==l-o){while(u=0;n--){if((r=e[n]).left!=r.right){break}}}return r}function ai(e,t,r,i){var n=ii(t.map,r,i);var a=n.node,l=n.start,u=n.end,c=n.collapse;var f;if(a.nodeType==3){for(var h=0;h<4;h++){while(l&&ae(t.line.text.charAt(n.coverStart+l))){--l}while(n.coverStart+u0){c=i="right"}var p;if(e.options.lineWrapping&&(p=a.getClientRects()).length>1){f=p[i=="right"?p.length-1:0]}else{f=a.getBoundingClientRect()}}if(s&&o<9&&!l&&(!f||!f.left&&!f.right)){var d=a.parentNode.getClientRects()[0];if(d){f={left:d.left,right:d.left+Ei(e.display),top:d.top,bottom:d.bottom}}else{f=ri}}var m=f.top-t.rect.top,v=f.bottom-t.rect.top;var g=(m+v)/2;var y=t.view.measure.heights;var x=0;for(;x=i.text.length){l=i.text.length;u="before"}else if(l<=0){l=0;u="after"}if(!o){return s(u=="before"?l-1:l,u=="before")}function c(e,t,r){var i=o[t],n=i.level==1;return s(r?e-1:e,n!=r)}var f=ce(o,l,u);var h=ue;var p=c(l,f,u=="before");if(h!=null){p.other=c(l,h,u!="before")}return p}function gi(e,t){var r=0;t=ft(e.doc,t);if(!e.options.lineWrapping){r=Ei(e.display)*t.ch}var i=Ye(e.doc,t.line);var n=nr(i)+jr(e.display);return{left:r,right:r,top:n,bottom:n+i.height}}function yi(e,t,r,i,n){var a=nt(e,t,r);a.xRel=n;if(i){a.outside=i}return a}function xi(e,t,r){var i=e.doc;r+=e.display.viewOffset;if(r<0){return yi(i.first,0,null,-1,-1)}var n=tt(i,r),a=i.first+i.size-1;if(n>a){return yi(i.first+i.size-1,Ye(i,a).text.length,null,1,1)}if(t<0){t=0}var s=Ye(i,n);for(;;){var o=Ci(e,s,n,t,r);var l=Xt(s,o.ch+(o.xRel>0||o.outside>0?1:0));if(!l){return o}var u=l.find(1);if(u.line==n){return u}s=Ye(i,n=u.line)}}function bi(e,t,r,i){i-=hi(t);var n=t.text.length;var a=oe(function(t){return ti(e,r,t-1).bottom<=i},n,0);n=oe(function(t){return ti(e,r,t).top>i},a,n);return{begin:a,end:n}}function wi(e,t,r,i){if(!r){r=ei(e,t)}var n=pi(e,t,ti(e,r,i),"line").top;return bi(e,t,r,n)}function ki(e,t,r,i){return e.bottom<=r?false:e.top>r?true:(i?e.left:e.right)>t}function Ci(e,t,r,i,n){n-=nr(t);var a=ei(e,t);var s=hi(t);var o=0,l=t.text.length,u=true;var c=he(t,e.doc.direction);if(c){var f=(e.options.lineWrapping?Li:Si)(e,t,r,a,c,i,n);u=f.level!=1;o=u?f.from:f.to-1;l=u?f.to:f.from-1}var h=null,p=null;var d=oe(function(t){var r=ti(e,a,t);r.top+=s;r.bottom+=s;if(!ki(r,i,n,false)){return false}if(r.top<=n&&r.left<=i){h=t;p=r}return true},o,l);var m,v,g=false;if(p){var y=i-p.left=b.bottom?1:0}d=se(t.text,d,1);return yi(r,d,v,g,i-m)}function Si(e,t,r,i,n,a,s){var o=oe(function(o){var l=n[o],u=l.level!=1;return ki(vi(e,nt(r,u?l.to:l.from,u?"before":"after"),"line",t,i),a,s,true)},0,n.length-1);var l=n[o];if(o>0){var u=l.level!=1;var c=vi(e,nt(r,u?l.from:l.to,u?"after":"before"),"line",t,i);if(ki(c,a,s,true)&&c.top>s){l=n[o-1]}}return l}function Li(e,t,r,i,n,a,s){var o=bi(e,t,i,s);var l=o.begin;var u=o.end;if(/\s/.test(t.text.charAt(u-1))){u--}var c=null,f=null;for(var h=0;h=u||p.to<=l){continue}var d=p.level!=1;var m=ti(e,i,d?Math.min(u,p.to)-1:Math.max(l,p.from)).right;var v=mv){c=p;f=v}}if(!c){c=n[n.length-1]}if(c.fromu){c={from:c.from,to:u,level:c.level}}return c}var Ti;function Ai(e){if(e.cachedTextHeight!=null){return e.cachedTextHeight}if(Ti==null){Ti=E("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t){Ti.appendChild(document.createTextNode("x"));Ti.appendChild(E("br"))}Ti.appendChild(document.createTextNode("x"))}A(e.measure,Ti);var r=Ti.offsetHeight/50;if(r>3){e.cachedTextHeight=r}T(e.measure);return r||1}function Ei(e){if(e.cachedCharWidth!=null){return e.cachedCharWidth}var t=E("span","xxxxxxxxxx");var r=E("pre",[t],"CodeMirror-line-like");A(e.measure,r);var i=t.getBoundingClientRect(),n=(i.right-i.left)/10;if(n>2){e.cachedCharWidth=n}return n||10}function Mi(e){var t=e.display,r={},i={};var n=t.gutters.clientLeft;for(var a=t.gutters.firstChild,s=0;a;a=a.nextSibling,++s){var o=e.display.gutterSpecs[s].className;r[o]=a.offsetLeft+a.clientLeft+n;i[o]=a.clientWidth}return{fixedPos:Ni(t),gutterTotalWidth:t.gutters.offsetWidth,gutterLeft:r,gutterWidth:i,wrapperWidth:t.wrapper.clientWidth}}function Ni(e){return e.scroller.getBoundingClientRect().left-e.sizer.getBoundingClientRect().left}function _i(e){var t=Ai(e.display),r=e.options.lineWrapping;var i=r&&Math.max(5,e.display.scroller.clientWidth/Ei(e.display)-3);return function(n){if(rr(e.doc,n)){return 0}var a=0;if(n.widgets){for(var s=0;s=e.display.viewTo){return null}t-=e.display.viewFrom;if(t<0){return null}var r=e.display.view;for(var i=0;it)){n.updateLineNumbers=t}e.curOp.viewChanged=true;if(t>=n.viewTo){if(Mt&&er(e.doc,t)n.viewFrom){Vi(e)}else{n.viewFrom+=i;n.viewTo+=i}}else if(t<=n.viewFrom&&r>=n.viewTo){Vi(e)}else if(t<=n.viewFrom){var a=Fi(e,r,r+i,1);if(a){n.view=n.view.slice(a.index);n.viewFrom=a.lineN;n.viewTo+=i}else{Vi(e)}}else if(r>=n.viewTo){var s=Fi(e,t,t,-1);if(s){n.view=n.view.slice(0,s.index);n.viewTo=s.lineN}else{Vi(e)}}else{var o=Fi(e,t,t,-1);var l=Fi(e,r,r+i,1);if(o&&l){n.view=n.view.slice(0,o.index).concat(wr(e,o.lineN,l.lineN)).concat(n.view.slice(l.index));n.viewTo+=i}else{Vi(e)}}var u=n.externalMeasured;if(u){if(r=n.lineN&&t=i.viewTo){return}var a=i.view[Oi(e,t)];if(a.node==null){return}var s=a.changes||(a.changes=[]);if(z(s,r)==-1){s.push(r)}}function Vi(e){e.display.viewFrom=e.display.viewTo=e.doc.first;e.display.view=[];e.display.viewOffset=0}function Fi(e,t,r,i){var n=Oi(e,t),a,s=e.display.view;if(!Mt||r==e.doc.first+e.doc.size){return{index:n,lineN:r}}var o=e.display.viewFrom;for(var l=0;l0){if(n==s.length-1){return null}a=o+s[n].size-t;n++}else{a=o-t}t+=a;r+=a}while(er(e.doc,r)!=r){if(n==(i<0?0:s.length-1)){return null}r+=i*s[n-(i<0?1:0)].size;n+=i}return{index:n,lineN:r}}function Wi(e,t,r){var i=e.display,n=i.view;if(n.length==0||t>=i.viewTo||r<=i.viewFrom){i.view=wr(e,t,r);i.viewFrom=t}else{if(i.viewFrom>t){i.view=wr(e,t,i.viewFrom).concat(i.view)}else if(i.viewFromr){i.view=i.view.slice(0,Oi(e,r))}}i.viewTo=r}function zi(e){var t=e.display.view,r=0;for(var i=0;i=e.display.viewTo||o.to().line0){t.blinker=setInterval(function(){return t.cursorDiv.style.visibility=(r=!r)?"":"hidden"},e.options.cursorBlinkRate)}else if(e.options.cursorBlinkRate<0){t.cursorDiv.style.visibility="hidden"}}function Ki(e){if(!e.state.focused){e.display.input.focus();Xi(e)}}function $i(e){e.state.delayingBlurEvent=true;setTimeout(function(){if(e.state.delayingBlurEvent){e.state.delayingBlurEvent=false;Yi(e)}},100)}function Xi(e,t){if(e.state.delayingBlurEvent){e.state.delayingBlurEvent=false}if(e.options.readOnly=="nocursor"){return}if(!e.state.focused){ge(e,"focus",e,t);e.state.focused=true;I(e.display.wrapper,"CodeMirror-focused");if(!e.curOp&&e.display.selForContextMenu!=e.doc.sel){e.display.input.reset();if(l){setTimeout(function(){return e.display.input.reset(true)},20)}}e.display.input.receivedFocus()}qi(e)}function Yi(e,t){if(e.state.delayingBlurEvent){return}if(e.state.focused){ge(e,"blur",e,t);e.state.focused=false;L(e.display.wrapper,"CodeMirror-focused")}clearInterval(e.display.blinker);setTimeout(function(){if(!e.state.focused){e.display.shift=false}},150)}function Qi(e){var t=e.display;var r=t.lineDiv.offsetTop;for(var i=0;i.005||h<-.005){Je(n.line,l);Zi(n.line);if(n.rest){for(var p=0;pe.display.sizerWidth){var d=Math.ceil(u/Ei(e.display));if(d>e.display.maxLineLength){e.display.maxLineLength=d;e.display.maxLine=n.line;e.display.maxLineChanged=true}}}}function Zi(e){if(e.widgets){for(var t=0;t=s){a=tt(t,nr(Ye(t,l))-e.wrapper.clientHeight);s=l}}return{from:a,to:Math.max(s,a+1)}}function en(e,t){if(ye(e,"scrollCursorIntoView")){return}var r=e.display,i=r.sizer.getBoundingClientRect(),n=null;if(t.top+i.top<0){n=true}else if(t.bottom+i.top>(window.innerHeight||document.documentElement.clientHeight)){n=false}if(n!=null&&!d){var a=E("div","​",null,"position: absolute;\n top: "+(t.top-r.viewOffset-jr(e.display))+"px;\n height: "+(t.bottom-t.top+qr(e)+r.barHeight)+"px;\n left: "+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;");e.display.lineSpace.appendChild(a);a.scrollIntoView(n);e.display.lineSpace.removeChild(a)}}function tn(e,t,r,i){if(i==null){i=0}var n;if(!e.options.lineWrapping&&t==r){t=t.ch?nt(t.line,t.sticky=="before"?t.ch-1:t.ch,"after"):t;r=t.sticky=="before"?nt(t.line,t.ch+1,"before"):t}for(var a=0;a<5;a++){var s=false;var o=vi(e,t);var l=!r||r==t?o:vi(e,r);n={left:Math.min(o.left,l.left),top:Math.min(o.top,l.top)-i,right:Math.max(o.left,l.left),bottom:Math.max(o.bottom,l.bottom)+i};var u=nn(e,n);var c=e.doc.scrollTop,f=e.doc.scrollLeft;if(u.scrollTop!=null){fn(e,u.scrollTop);if(Math.abs(e.doc.scrollTop-c)>1){s=true}}if(u.scrollLeft!=null){pn(e,u.scrollLeft);if(Math.abs(e.doc.scrollLeft-f)>1){s=true}}if(!s){break}}return n}function rn(e,t){var r=nn(e,t);if(r.scrollTop!=null){fn(e,r.scrollTop)}if(r.scrollLeft!=null){pn(e,r.scrollLeft)}}function nn(e,t){var r=e.display,i=Ai(e.display);if(t.top<0){t.top=0}var n=e.curOp&&e.curOp.scrollTop!=null?e.curOp.scrollTop:r.scroller.scrollTop;var a=$r(e),s={};if(t.bottom-t.top>a){t.bottom=t.top+a}var o=e.doc.height+Ur(r);var l=t.topo-i;if(t.topn+a){var c=Math.min(t.top,(u?o:t.bottom)-a);if(c!=n){s.scrollTop=c}}var f=e.curOp&&e.curOp.scrollLeft!=null?e.curOp.scrollLeft:r.scroller.scrollLeft;var h=Kr(e)-(e.options.fixedGutter?r.gutters.offsetWidth:0);var p=t.right-t.left>h;if(p){t.right=t.left+h}if(t.left<10){s.scrollLeft=0}else if(t.lefth+f-3){s.scrollLeft=t.right+(p?0:10)-h}return s}function an(e,t){if(t==null){return}un(e);e.curOp.scrollTop=(e.curOp.scrollTop==null?e.doc.scrollTop:e.curOp.scrollTop)+t}function sn(e){un(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function on(e,t,r){if(t!=null||r!=null){un(e)}if(t!=null){e.curOp.scrollLeft=t}if(r!=null){e.curOp.scrollTop=r}}function ln(e,t){un(e);e.curOp.scrollToPos=t}function un(e){var t=e.curOp.scrollToPos;if(t){e.curOp.scrollToPos=null;var r=gi(e,t.from),i=gi(e,t.to);cn(e,r,i,t.margin)}}function cn(e,t,r,i){var n=nn(e,{left:Math.min(t.left,r.left),top:Math.min(t.top,r.top)-i,right:Math.max(t.right,r.right),bottom:Math.max(t.bottom,r.bottom)+i});on(e,n.scrollLeft,n.scrollTop)}function fn(e,t){if(Math.abs(e.doc.scrollTop-t)<2){return}if(!r){Hn(e,{top:t})}hn(e,t,true);if(r){Hn(e)}On(e,100)}function hn(e,t,r){t=Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t);if(e.display.scroller.scrollTop==t&&!r){return}e.doc.scrollTop=t;e.display.scrollbars.setScrollTop(t);if(e.display.scroller.scrollTop!=t){e.display.scroller.scrollTop=t}}function pn(e,t,r,i){t=Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth);if((r?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!i){return}e.doc.scrollLeft=t;qn(e);if(e.display.scroller.scrollLeft!=t){e.display.scroller.scrollLeft=t}e.display.scrollbars.setScrollLeft(t)}function dn(e){var t=e.display,r=t.gutters.offsetWidth;var i=Math.round(e.doc.height+Ur(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?r:0,docHeight:i,scrollHeight:i+qr(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:r}}var mn=function(e,t,r){this.cm=r;var i=this.vert=E("div",[E("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar");var n=this.horiz=E("div",[E("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");i.tabIndex=n.tabIndex=-1;e(i);e(n);de(i,"scroll",function(){if(i.clientHeight){t(i.scrollTop,"vertical")}});de(n,"scroll",function(){if(n.clientWidth){t(n.scrollLeft,"horizontal")}});this.checkedZeroWidth=false;if(s&&o<8){this.horiz.style.minHeight=this.vert.style.minWidth="18px"}};mn.prototype.update=function(e){var t=e.scrollWidth>e.clientWidth+1;var r=e.scrollHeight>e.clientHeight+1;var i=e.nativeBarWidth;if(r){this.vert.style.display="block";this.vert.style.bottom=t?i+"px":"0";var n=e.viewHeight-(t?i:0);this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+n)+"px"}else{this.vert.style.display="";this.vert.firstChild.style.height="0"}if(t){this.horiz.style.display="block";this.horiz.style.right=r?i+"px":"0";this.horiz.style.left=e.barLeft+"px";var a=e.viewWidth-e.barLeft-(r?i:0);this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+a)+"px"}else{this.horiz.style.display="";this.horiz.firstChild.style.width="0"}if(!this.checkedZeroWidth&&e.clientHeight>0){if(i==0){this.zeroWidthHack()}this.checkedZeroWidth=true}return{right:r?i:0,bottom:t?i:0}};mn.prototype.setScrollLeft=function(e){if(this.horiz.scrollLeft!=e){ -this.horiz.scrollLeft=e}if(this.disableHoriz){this.enableZeroWidthBar(this.horiz,this.disableHoriz,"horiz")}};mn.prototype.setScrollTop=function(e){if(this.vert.scrollTop!=e){this.vert.scrollTop=e}if(this.disableVert){this.enableZeroWidthBar(this.vert,this.disableVert,"vert")}};mn.prototype.zeroWidthHack=function(){var e=y&&!p?"12px":"18px";this.horiz.style.height=this.vert.style.width=e;this.horiz.style.pointerEvents=this.vert.style.pointerEvents="none";this.disableHoriz=new W;this.disableVert=new W};mn.prototype.enableZeroWidthBar=function(e,t,r){e.style.pointerEvents="auto";function i(){var n=e.getBoundingClientRect();var a=r=="vert"?document.elementFromPoint(n.right-1,(n.top+n.bottom)/2):document.elementFromPoint((n.right+n.left)/2,n.bottom-1);if(a!=e){e.style.pointerEvents="none"}else{t.set(1e3,i)}}t.set(1e3,i)};mn.prototype.clear=function(){var e=this.horiz.parentNode;e.removeChild(this.horiz);e.removeChild(this.vert)};var vn=function(){};vn.prototype.update=function(){return{bottom:0,right:0}};vn.prototype.setScrollLeft=function(){};vn.prototype.setScrollTop=function(){};vn.prototype.clear=function(){};function gn(e,t){if(!t){t=dn(e)}var r=e.display.barWidth,i=e.display.barHeight;yn(e,t);for(var n=0;n<4&&r!=e.display.barWidth||i!=e.display.barHeight;n++){if(r!=e.display.barWidth&&e.options.lineWrapping){Qi(e)}yn(e,dn(e));r=e.display.barWidth;i=e.display.barHeight}}function yn(e,t){var r=e.display;var i=r.scrollbars.update(t);r.sizer.style.paddingRight=(r.barWidth=i.right)+"px";r.sizer.style.paddingBottom=(r.barHeight=i.bottom)+"px";r.heightForcer.style.borderBottom=i.bottom+"px solid transparent";if(i.right&&i.bottom){r.scrollbarFiller.style.display="block";r.scrollbarFiller.style.height=i.bottom+"px";r.scrollbarFiller.style.width=i.right+"px"}else{r.scrollbarFiller.style.display=""}if(i.bottom&&e.options.coverGutterNextToScrollbar&&e.options.fixedGutter){r.gutterFiller.style.display="block";r.gutterFiller.style.height=i.bottom+"px";r.gutterFiller.style.width=t.gutterWidth+"px"}else{r.gutterFiller.style.display=""}}var xn={native:mn,null:vn};function bn(e){if(e.display.scrollbars){e.display.scrollbars.clear();if(e.display.scrollbars.addClass){L(e.display.wrapper,e.display.scrollbars.addClass)}}e.display.scrollbars=new xn[e.options.scrollbarStyle](function(t){e.display.wrapper.insertBefore(t,e.display.scrollbarFiller);de(t,"mousedown",function(){if(e.state.focused){setTimeout(function(){return e.display.input.focus()},0)}});t.setAttribute("cm-not-content","true")},function(t,r){if(r=="horizontal"){pn(e,t)}else{fn(e,t)}},e);if(e.display.scrollbars.addClass){I(e.display.wrapper,e.display.scrollbars.addClass)}}var wn=0;function kn(e){e.curOp={cm:e,viewChanged:false,startHeight:e.doc.height,forceUpdate:false,updateInput:0,typing:false,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:false,updateMaxLine:false,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:false,id:++wn};Cr(e.curOp)}function Cn(e){var t=e.curOp;if(t){Lr(t,function(e){for(var t=0;t=r.viewTo)||r.maxLineChanged&&t.options.lineWrapping;e.update=e.mustUpdate&&new Rn(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}function Tn(e){e.updatedDisplay=e.mustUpdate&&zn(e.cm,e.update)}function An(e){var t=e.cm,r=t.display;if(e.updatedDisplay){Qi(t)}e.barMeasure=dn(t);if(r.maxLineChanged&&!t.options.lineWrapping){e.adjustWidthTo=Zr(t,r.maxLine,r.maxLine.text.length).left+3;t.display.sizerWidth=e.adjustWidthTo;e.barMeasure.scrollWidth=Math.max(r.scroller.clientWidth,r.sizer.offsetLeft+e.adjustWidthTo+qr(t)+t.display.barWidth);e.maxScrollLeft=Math.max(0,r.sizer.offsetLeft+e.adjustWidthTo-Kr(t))}if(e.updatedDisplay||e.selectionChanged){e.preparedSelection=r.input.prepareSelection()}}function En(e){var t=e.cm;if(e.adjustWidthTo!=null){t.display.sizer.style.minWidth=e.adjustWidthTo+"px";if(e.maxScrollLeft=e.display.viewTo){return}var r=+new Date+e.options.workTime;var i=yt(e,t.highlightFrontier);var n=[];t.iter(i.line,Math.min(t.first+t.size,e.display.viewTo+500),function(a){if(i.line>=e.display.viewFrom){var s=a.styles;var o=a.text.length>e.options.maxHighlightLength?qe(t.mode,i.state):null;var l=vt(e,a,i,true);if(o){i.state=o}a.styles=l.styles;var u=a.styleClasses,c=l.classes;if(c){a.styleClasses=c}else if(u){a.styleClasses=null}var f=!s||s.length!=a.styles.length||u!=c&&(!u||!c||u.bgClass!=c.bgClass||u.textClass!=c.textClass);for(var h=0;!f&&hr){On(e,e.options.workDelay);return true}});t.highlightFrontier=i.line;t.modeFrontier=Math.max(t.modeFrontier,i.line);if(n.length){Nn(e,function(){for(var t=0;t=r.viewFrom&&t.visible.to<=r.viewTo&&(r.updateLineNumbers==null||r.updateLineNumbers>=r.viewTo)&&r.renderedView==r.view&&zi(e)==0){return false}if(Kn(e)){Vi(e);t.dims=Mi(e)}var n=i.first+i.size;var a=Math.max(t.visible.from-e.options.viewportMargin,i.first);var s=Math.min(n,t.visible.to+e.options.viewportMargin);if(r.viewFroms&&r.viewTo-s<20){s=Math.min(n,r.viewTo)}if(Mt){a=er(e.doc,a);s=tr(e.doc,s)}var o=a!=r.viewFrom||s!=r.viewTo||r.lastWrapHeight!=t.wrapperHeight||r.lastWrapWidth!=t.wrapperWidth;Wi(e,a,s);r.viewOffset=nr(Ye(e.doc,r.viewFrom));e.display.mover.style.top=r.viewOffset+"px";var l=zi(e);if(!o&&l==0&&!t.force&&r.renderedView==r.view&&(r.updateLineNumbers==null||r.updateLineNumbers>=r.viewTo)){return false}var u=Fn(e);if(l>4){r.lineDiv.style.display="none"}jn(e,r.updateLineNumbers,t.dims);if(l>4){r.lineDiv.style.display=""}r.renderedView=r.view;Wn(u);T(r.cursorDiv);T(r.selectionDiv);r.gutters.style.height=r.sizer.style.minHeight=0;if(o){r.lastWrapHeight=t.wrapperHeight;r.lastWrapWidth=t.wrapperWidth;On(e,400)}r.updateLineNumbers=null;return true}function Bn(e,t){var r=t.viewport;for(var i=true;;i=false){if(!i||!e.options.lineWrapping||t.oldDisplayWidth==Kr(e)){if(r&&r.top!=null){r={top:Math.min(e.doc.height+Ur(e.display)-$r(e),r.top)}}t.visible=Ji(e.display,e.doc,r);if(t.visible.from>=e.display.viewFrom&&t.visible.to<=e.display.viewTo){break}}if(!zn(e,t)){break}Qi(e);var n=dn(e);Bi(e);gn(e,n);Gn(e,n);t.force=false}t.signal(e,"update",e);if(e.display.viewFrom!=e.display.reportedViewFrom||e.display.viewTo!=e.display.reportedViewTo){t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo);e.display.reportedViewFrom=e.display.viewFrom;e.display.reportedViewTo=e.display.viewTo}}function Hn(e,t){var r=new Rn(e,t);if(zn(e,r)){Qi(e);Bn(e,r);var i=dn(e);Bi(e);gn(e,i);Gn(e,i);r.finish()}}function jn(e,t,r){var i=e.display,n=e.options.lineNumbers;var a=i.lineDiv,s=a.firstChild;function o(t){var r=t.nextSibling;if(l&&y&&e.display.currentWheelTarget==t){t.style.display="none"}else{t.parentNode.removeChild(t)}return r}var u=i.view,c=i.viewFrom;for(var f=0;f-1){d=false}Mr(e,h,c,r)}if(d){T(h.lineNumber);h.lineNumber.appendChild(document.createTextNode(it(e.options,c)))}s=h.node.nextSibling}c+=h.size}while(s){s=o(s)}}function Un(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px"}function Gn(e,t){e.display.sizer.style.minHeight=t.docHeight+"px";e.display.heightForcer.style.top=t.docHeight+"px";e.display.gutters.style.height=t.docHeight+e.display.barHeight+qr(e)+"px"}function qn(e){var t=e.display,r=t.view;if(!t.alignWidgets&&(!t.gutters.firstChild||!e.options.fixedGutter)){return}var i=Ni(t)-t.scroller.scrollLeft+e.doc.scrollLeft;var n=t.gutters.offsetWidth,a=i+"px";for(var s=0;so.clientWidth;var c=o.scrollHeight>o.clientHeight;if(!(n&&u||a&&c)){return}if(a&&y&&l){e:for(var h=t.target,p=s.view;h!=o;h=h.parentNode){for(var d=0;d=0&&at(e,n.to())<=0){return i}}return-1};var na=function(e,t){this.anchor=e;this.head=t};na.prototype.from=function(){return ut(this.anchor,this.head)};na.prototype.to=function(){return lt(this.anchor,this.head)};na.prototype.empty=function(){return this.head.line==this.anchor.line&&this.head.ch==this.anchor.ch};function aa(e,t,r){var i=e&&e.options.selectionsMayTouch;var n=t[r];t.sort(function(e,t){return at(e.from(),t.from())});r=z(t,n);for(var a=1;a0:l>=0){var u=ut(o.from(),s.from()),c=lt(o.to(),s.to());var f=o.empty()?s.from()==s.head:o.from()==o.head;if(a<=r){--r}t.splice(--a,2,new na(f?c:u,f?u:c))}}return new ia(t,r)}function sa(e,t){return new ia([new na(e,t||e)],0)}function oa(e){if(!e.text){return e.to}return nt(e.from.line+e.text.length-1,X(e.text).length+(e.text.length==1?e.from.ch:0))}function la(e,t){if(at(e,t.from)<0){return e}if(at(e,t.to)<=0){return oa(t)}var r=e.line+t.text.length-(t.to.line-t.from.line)-1,i=e.ch;if(e.line==t.to.line){i+=oa(t).ch-t.to.ch}return nt(r,i)}function ua(e,t){var r=[];for(var i=0;i1){e.remove(o.line+1,d-1)}e.insert(o.line+1,g)}Ar(e,"change",e,t)}function va(e,t,r){function i(e,n,a){if(e.linked){for(var s=0;s1&&!e.done[e.done.length-2].ranges){e.done.pop();return X(e.done)}}function Sa(e,t,r,i){var n=e.history;n.undone.length=0;var a=+new Date,s;var o;if((n.lastOp==i||n.lastOrigin==t.origin&&t.origin&&(t.origin.charAt(0)=="+"&&n.lastModTime>a-(e.cm?e.cm.options.historyEventDelay:500)||t.origin.charAt(0)=="*"))&&(s=Ca(n,n.lastOp==i))){o=X(s.changes);if(at(t.from,t.to)==0&&at(t.from,o.to)==0){o.to=oa(t)}else{s.changes.push(wa(e,t))}}else{var l=X(n.done);if(!l||!l.ranges){Aa(e.sel,n.done)}s={changes:[wa(e,t)],generation:n.generation};n.done.push(s);while(n.done.length>n.undoDepth){n.done.shift();if(!n.done[0].ranges){n.done.shift()}}}n.done.push(r);n.generation=++n.maxGeneration;n.lastModTime=n.lastSelTime=a;n.lastOp=n.lastSelOp=i;n.lastOrigin=n.lastSelOrigin=t.origin;if(!o){ge(e,"historyAdded")}}function La(e,t,r,i){var n=t.charAt(0);return n=="*"||n=="+"&&r.ranges.length==i.ranges.length&&r.somethingSelected()==i.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500)}function Ta(e,t,r,i){var n=e.history,a=i&&i.origin;if(r==n.lastSelOp||a&&n.lastSelOrigin==a&&(n.lastModTime==n.lastSelTime&&n.lastOrigin==a||La(e,a,X(n.done),t))){n.done[n.done.length-1]=t}else{Aa(t,n.done)}n.lastSelTime=+new Date;n.lastSelOrigin=a;n.lastSelOp=r;if(i&&i.clearRedo!==false){ka(n.undone)}}function Aa(e,t){var r=X(t);if(!(r&&r.ranges&&r.equals(e))){t.push(e)}}function Ea(e,t,r,i){var n=t["spans_"+e.id],a=0;e.iter(Math.max(e.first,r),Math.min(e.first+e.size,i),function(r){if(r.markedSpans){(n||(n=t["spans_"+e.id]={}))[a]=r.markedSpans}++a})}function Ma(e){if(!e){return null}var t;for(var r=0;r-1){X(o)[f]=u[f];delete u[f]}}}}}}return i}function Ia(e,t,r,i){if(i){var n=e.anchor;if(r){var a=at(t,n)<0;if(a!=at(r,n)<0){n=t;t=r}else if(a!=at(t,r)<0){t=r}}return new na(n,t)}else{return new na(r||t,t)}}function Oa(e,t,r,i,n){if(n==null){n=e.cm&&(e.cm.display.shift||e.extend)}za(e,new ia([Ia(e.sel.primary(),t,r,n)],0),i)}function Da(e,t,r){var i=[];var n=e.cm&&(e.cm.display.shift||e.extend);for(var a=0;a=t.ch:o.to>t.ch))){if(n){ge(l,"beforeCursorEnter");if(l.explicitlyCleared){if(!a.markedSpans){break}else{--s;continue}}}if(!l.atomic){continue}if(r){var f=l.find(i<0?1:-1),h=void 0;if(i<0?c:u){f=Ka(e,f,-i,f&&f.line==t.line?a:null)}if(f&&f.line==t.line&&(h=at(f,r))&&(i<0?h<0:h>0)){return Ga(e,f,t,i,n)}}var p=l.find(i<0?-1:1);if(i<0?u:c){p=Ka(e,p,i,p.line==t.line?a:null)}return p?Ga(e,p,t,i,n):null}}}return t}function qa(e,t,r,i,n){var a=i||1;var s=Ga(e,t,r,a,n)||!n&&Ga(e,t,r,a,true)||Ga(e,t,r,-a,n)||!n&&Ga(e,t,r,-a,true);if(!s){e.cantEdit=true;return nt(e.first,0)}return s}function Ka(e,t,r,i){if(r<0&&t.ch==0){if(t.line>e.first){return ft(e,nt(t.line-1))}else{return null}}else if(r>0&&t.ch==(i||Ye(e,t.line)).text.length){if(t.line=0;--n){Qa(e,{from:i[n].from,to:i[n].to,text:n?[""]:t.text,origin:t.origin})}}else{Qa(e,t)}}function Qa(e,t){if(t.text.length==1&&t.text[0]==""&&at(t.from,t.to)==0){return}var r=ua(e,t);Sa(e,t,r,e.cm?e.cm.curOp.id:NaN);es(e,t,r,Ft(e,t));var i=[];va(e,function(e,r){if(!r&&z(i,e.history)==-1){as(e.history,t);i.push(e.history)}es(e,t,null,Ft(e,t))})}function Za(e,t,r){var i=e.cm&&e.cm.state.suppressEdits;if(i&&!r){return}var n=e.history,a,s=e.sel;var o=t=="undo"?n.done:n.undone,l=t=="undo"?n.undone:n.done;var u=0;for(;u=0;--p){var d=h(p);if(d)return d.v}}function Ja(e,t){if(t==0){return}e.first+=t;e.sel=new ia(Y(e.sel.ranges,function(e){return new na(nt(e.anchor.line+t,e.anchor.ch),nt(e.head.line+t,e.head.ch))}),e.sel.primIndex);if(e.cm){Di(e.cm,e.first,e.first-t,t);for(var r=e.cm.display,i=r.viewFrom;ie.lastLine()){return}if(t.from.linea){t={from:t.from,to:nt(a,Ye(e,a).text.length),text:[t.text[0]],origin:t.origin}}t.removed=Qe(e,t.from,t.to);if(!r){r=ua(e,t)}if(e.cm){ts(e.cm,t,i)}else{ma(e,t,i)}Ba(e,r,j);if(e.cantEdit&&qa(e,nt(e.firstLine(),0))){e.cantEdit=false}}function ts(e,t,r){var i=e.doc,n=e.display,a=t.from,s=t.to;var o=false,l=a.line;if(!e.options.lineWrapping){l=et(Qt(Ye(i,a.line)));i.iter(l,s.line+1,function(e){if(e==n.maxLine){o=true;return true}})}if(i.sel.contains(t.from,t.to)>-1){xe(e)}ma(i,t,r,_i(e));if(!e.options.lineWrapping){i.iter(l,a.line+t.text.length,function(e){var t=ar(e);if(t>n.maxLineLength){n.maxLine=e;n.maxLineLength=t;n.maxLineChanged=true;o=false}});if(o){e.curOp.updateMaxLine=true}}At(i,a.line);On(e,400);var u=t.text.length-(s.line-a.line)-1;if(t.full){Di(e)}else if(a.line==s.line&&t.text.length==1&&!da(e.doc,t)){Ri(e,a.line,"text")}else{Di(e,a.line,s.line+1,u)}var c=be(e,"changes"),f=be(e,"change");if(f||c){var h={from:a,to:s,text:t.text,removed:t.removed,origin:t.origin};if(f){Ar(e,"change",e,h)}if(c){(e.curOp.changeObjs||(e.curOp.changeObjs=[])).push(h)}}e.display.selForContextMenu=null}function rs(e,t,r,i,n){var a;if(!i){i=r}if(at(i,r)<0){a=[i,r],r=a[0],i=a[1]}if(typeof t=="string"){t=e.splitLines(t)}Ya(e,{from:r,to:i,text:t,origin:n})}function is(e,t,r,i){if(r1||!(this.children[0]instanceof os))){var l=[];this.collapse(l);this.children=[new os(l)];this.children[0].parent=this}},collapse:function(e){var t=this;for(var r=0;r50){var o=a.lines.length%25+25;for(var l=o;l10);e.parent.maybeSpill()},iterN:function(e,t,r){var i=this;for(var n=0;nt.display.maxLineLength){t.display.maxLine=c;t.display.maxLineLength=f;t.display.maxLineChanged=true}}}if(n!=null&&t&&this.collapsed){Di(t,n,a+1)}this.lines.length=0;this.explicitlyCleared=true;if(this.atomic&&this.doc.cantEdit){this.doc.cantEdit=false;if(t){ja(t.doc)}}if(t){Ar(t,"markerCleared",t,this,n,a)}if(r){Cn(t)}if(this.parent){this.parent.clear()}};ps.prototype.find=function(e,t){var r=this;if(e==null&&this.type=="bookmark"){e=1}var i,n;for(var a=0;a0||s==0&&a.clearWhenEmpty!==false){return a}if(a.replacedWith){a.collapsed=true;a.widgetNode=M("span",[a.replacedWith],"CodeMirror-widget");if(!i.handleMouseEvents){a.widgetNode.setAttribute("cm-ignore-events","true")}if(i.insertLeft){a.widgetNode.insertLeft=true}}if(a.collapsed){if(Yt(e,t.line,t,r,a)||t.line!=r.line&&Yt(e,r.line,t,r,a)){throw new Error("Inserting collapsed marker partially overlapping an existing one")}_t()}if(a.addToHistory){Sa(e,{from:t,to:r,origin:"markText"},e.sel,NaN)}var o=t.line,l=e.cm,u;e.iter(o,r.line+1,function(e){if(l&&a.collapsed&&!l.options.lineWrapping&&Qt(e)==l.display.maxLine){u=true}if(a.collapsed&&o!=t.line){Je(e,0)}Dt(e,new Pt(a,o==t.line?t.ch:null,o==r.line?r.ch:null));++o});if(a.collapsed){e.iter(t.line,r.line+1,function(t){if(rr(e,t)){Je(t,0)}})}if(a.clearOnEnter){de(a,"beforeCursorEnter",function(){return a.clear()})}if(a.readOnly){Nt();if(e.history.done.length||e.history.undone.length){e.clearHistory()}}if(a.collapsed){a.id=++hs;a.atomic=true}if(l){if(u){l.curOp.updateMaxLine=true}if(a.collapsed){Di(l,t.line,r.line+1)}else if(a.className||a.startStyle||a.endStyle||a.css||a.attributes||a.title){for(var c=t.line;c<=r.line;c++){Ri(l,c,"text")}}if(a.atomic){ja(l.doc)}Ar(l,"markerAdded",l,a)}return a}var ms=function(e,t){var r=this;this.markers=e;this.primary=t;for(var i=0;i=0;u--){Ya(i,n[u])}if(l){Wa(this,l)}else if(this.cm){sn(this.cm)}}),undo:In(function(){Za(this,"undo")}),redo:In(function(){Za(this,"redo")}),undoSelection:In(function(){Za(this,"undo",true)}),redoSelection:In(function(){Za(this,"redo",true)}),setExtending:function(e){this.extend=e},getExtending:function(){return this.extend},historySize:function(){var e=this.history,t=0,r=0;for(var i=0;i=e.ch)){t.push(n.marker.parent||n.marker)}}}return t},findMarks:function(e,t,r){e=ft(this,e);t=ft(this,t);var i=[],n=e.line;this.iter(e.line,t.line+1,function(a){var s=a.markedSpans;if(s){for(var o=0;o=l.to||l.from==null&&n!=e.line||l.from!=null&&n==t.line&&l.from>=t.ch)&&(!r||r(l.marker))){i.push(l.marker.parent||l.marker)}}}++n});return i},getAllMarks:function(){var e=[];this.iter(function(t){var r=t.markedSpans;if(r){for(var i=0;ie){t=e;return true}e-=a;++r});return ft(this,nt(r,t))},indexFromPos:function(e){e=ft(this,e);var t=e.ch;if(e.linet){t=e.from}if(e.to!=null&&e.to-1){t.state.draggingText(e);setTimeout(function(){return t.display.input.focus()},20);return}try{var c=e.dataTransfer.getData("Text");if(c){var f;if(t.state.draggingText&&!t.state.draggingText.copy){f=t.listSelections()}Ba(t.doc,sa(r,r));if(f){for(var h=0;h=0;t--){rs(e.doc,"",i[t].from,i[t].to,"+delete")}sn(e)})}function Gs(e,t,r){var i=se(e.text,t+r,r);return i<0||i>e.text.length?null:i}function qs(e,t,r){var i=Gs(e,t.ch,r);return i==null?null:new nt(t.line,i,r<0?"after":"before")}function Ks(e,t,r,i,n){if(e){var a=he(r,t.doc.direction);if(a){var s=n<0?X(a):a[0];var o=n<0==(s.level==1);var l=o?"after":"before";var u;if(s.level>0||t.doc.direction=="rtl"){var c=ei(t,r);u=n<0?r.text.length-1:0;var f=ti(t,c,u).top;u=oe(function(e){return ti(t,c,e).top==f},n<0==(s.level==1)?s.from:s.to-1,u);if(l=="before"){u=Gs(r,u,1)}}else{u=n<0?s.to:s.from}return new nt(i,u,l)}}return new nt(i,n<0?r.text.length:0,n<0?"before":"after")}function $s(e,t,r,i){var n=he(t,e.doc.direction);if(!n){return qs(t,r,i)}if(r.ch>=t.text.length){r.ch=t.text.length;r.sticky="before"}else if(r.ch<=0){r.ch=0;r.sticky="after"}var a=ce(n,r.ch,r.sticky),s=n[a];if(e.doc.direction=="ltr"&&s.level%2==0&&(i>0?s.to>r.ch:s.from=s.from&&h>=c.begin:h<=s.to&&h<=c.end)){var p=f?"before":"after";return new nt(r.line,h,p)}}var d=function(e,t,i){var a=function(e,t){return t?new nt(r.line,o(e,1),"before"):new nt(r.line,e,"after")};for(;e>=0&&e0==(s.level!=1);var u=l?i.begin:o(i.end,-1);if(s.from<=u&&u0?c.end:o(c.begin,-1);if(v!=null&&!(i>0&&v==t.text.length)){m=d(i>0?0:n.length-1,i,u(v));if(m){return m}}return null}var Xs={selectAll:$a,singleSelection:function(e){return e.setSelection(e.getCursor("anchor"),e.getCursor("head"),j)},killLine:function(e){return Us(e,function(t){if(t.empty()){var r=Ye(e.doc,t.head.line).text.length;if(t.head.ch==r&&t.head.line0){n=new nt(n.line,n.ch+1);e.replaceRange(a.charAt(n.ch-1)+a.charAt(n.ch-2),nt(n.line,n.ch-2),n,"+transpose")}else if(n.line>e.doc.first){var s=Ye(e.doc,n.line-1).text;if(s){n=new nt(n.line,1);e.replaceRange(a.charAt(0)+e.doc.lineSeparator()+s.charAt(s.length-1),nt(n.line-1,s.length-1),n,"+transpose")}}}r.push(new na(n,n))}e.setSelections(r)})},newlineAndIndent:function(e){return Nn(e,function(){var t=e.listSelections();for(var r=t.length-1;r>=0;r--){e.replaceRange(e.doc.lineSeparator(),t[r].anchor,t[r].head,"+input")}t=e.listSelections();for(var i=0;ie&&at(t,this.pos)==0&&r==this.button};var po,mo;function vo(e,t){var r=+new Date;if(mo&&mo.compare(r,e,t)){po=mo=null;return"triple"}else if(po&&po.compare(r,e,t)){mo=new ho(r,e,t);po=null;return"double"}else{po=new ho(r,e,t);mo=null;return"single"}}function go(e){var t=this,r=t.display;if(ye(t,e)||r.activeTouch&&r.input.supportsTouch()){return}r.input.ensurePolled();r.shift=e.shiftKey;if(Hr(r,e)){if(!l){r.scroller.draggable=false;setTimeout(function(){return r.scroller.draggable=true},100)}return}if(To(t,e)){return}var i=Ii(t,e),n=Ae(e),a=i?vo(i,n):"single";window.focus();if(n==1&&t.state.selectingText){t.state.selectingText(e)}if(i&&yo(t,n,i,a,e)){return}if(n==1){if(i){bo(t,i,a,e)}else if(Te(e)==r.scroller){ke(e)}}else if(n==2){if(i){Oa(t.doc,i)}setTimeout(function(){return r.input.focus()},20)}else if(n==3){if(C){t.display.input.onContextMenu(e)}else{$i(t)}}}function yo(e,t,r,i,n){var a="Click";if(i=="double"){a="Double"+a}else if(i=="triple"){a="Triple"+a}a=(t==1?"Left":t==2?"Middle":"Right")+a;return ro(e,Bs(a,n),n,function(t){if(typeof t=="string"){t=Xs[t]}if(!t){return false}var i=false;try{if(e.isReadOnly()){e.state.suppressEdits=true}i=t(e,r)!=H}finally{e.state.suppressEdits=false}return i})}function xo(e,t,r){var i=e.getOption("configureMouse");var n=i?i(e,t,r):{};if(n.unit==null){var a=x?r.shiftKey&&r.metaKey:r.altKey;n.unit=a?"rectangle":t=="single"?"char":t=="double"?"word":"line"}if(n.extend==null||e.doc.extend){n.extend=e.doc.extend||r.shiftKey}if(n.addNew==null){n.addNew=y?r.metaKey:r.ctrlKey}if(n.moveOnDrag==null){n.moveOnDrag=!(y?r.altKey:r.ctrlKey)}return n}function bo(e,t,r,i){if(s){setTimeout(R(Ki,e),0)}else{e.curOp.focus=P()}var n=xo(e,r,i);var a=e.doc.sel,o;if(e.options.dragDrop&&Ee&&!e.isReadOnly()&&r=="single"&&(o=a.contains(t))>-1&&(at((o=a.ranges[o]).from(),t)<0||t.xRel>0)&&(at(o.to(),t)>0||t.xRel<0)){wo(e,i,t,n)}else{Co(e,i,t,n)}}function wo(e,t,r,i){var n=e.display,a=false;var u=_n(e,function(t){if(l){n.scroller.draggable=false}e.state.draggingText=false;ve(n.wrapper.ownerDocument,"mouseup",u);ve(n.wrapper.ownerDocument,"mousemove",c);ve(n.scroller,"dragstart",f);ve(n.scroller,"drop",u);if(!a){ke(t);if(!i.addNew){Oa(e.doc,r,null,null,i.extend)}if(l||s&&o==9){setTimeout(function(){n.wrapper.ownerDocument.body.focus();n.input.focus()},20)}else{n.input.focus()}}});var c=function(e){a=a||Math.abs(t.clientX-e.clientX)+Math.abs(t.clientY-e.clientY)>=10};var f=function(){return a=true};if(l){n.scroller.draggable=true}e.state.draggingText=u;u.copy=!i.moveOnDrag;if(n.scroller.dragDrop){n.scroller.dragDrop()}de(n.wrapper.ownerDocument,"mouseup",u);de(n.wrapper.ownerDocument,"mousemove",c);de(n.scroller,"dragstart",f);de(n.scroller,"drop",u);$i(e);setTimeout(function(){return n.input.focus()},20)}function ko(e,t,r){if(r=="char"){return new na(t,t)}if(r=="word"){return e.findWordAt(t)}if(r=="line"){return new na(nt(t.line,0),ft(e.doc,nt(t.line+1,0)))}var i=r(e,t);return new na(i.from,i.to)}function Co(e,t,r,i){var n=e.display,a=e.doc;ke(t);var s,o,l=a.sel,u=l.ranges;if(i.addNew&&!i.extend){o=a.sel.contains(r);if(o>-1){s=u[o]}else{s=new na(r,r)}}else{s=a.sel.primary();o=a.sel.primIndex}if(i.unit=="rectangle"){if(!i.addNew){s=new na(r,r)}r=Ii(e,t,true,true);o=-1}else{var c=ko(e,r,i.unit);if(i.extend){s=Ia(s,c.anchor,c.head,i.extend)}else{s=c}}if(!i.addNew){o=0;za(a,new ia([s],0),U);l=a.sel}else if(o==-1){o=u.length;za(a,aa(e,u.concat([s]),o),{scroll:false,origin:"*mouse"})}else if(u.length>1&&u[o].empty()&&i.unit=="char"&&!i.extend){za(a,aa(e,u.slice(0,o).concat(u.slice(o+1)),0),{scroll:false,origin:"*mouse"});l=a.sel}else{Ra(a,o,s,U)}var f=r;function h(t){if(at(f,t)==0){return}f=t;if(i.unit=="rectangle"){var n=[],u=e.options.tabSize;var c=F(Ye(a,r.line).text,r.ch,u);var h=F(Ye(a,t.line).text,t.ch,u);var p=Math.min(c,h),d=Math.max(c,h);for(var m=Math.min(r.line,t.line),v=Math.min(e.lastLine(),Math.max(r.line,t.line));m<=v;m++){var g=Ye(a,m).text,y=q(g,p,u);if(p==d){n.push(new na(nt(m,y),nt(m,y)))}else if(g.length>y){n.push(new na(nt(m,y),nt(m,q(g,d,u))))}}if(!n.length){n.push(new na(r,r))}za(a,aa(e,l.ranges.slice(0,o).concat(n),o),{origin:"*mouse",scroll:false});e.scrollIntoView(t)}else{var x=s;var b=ko(e,t,i.unit);var w=x.anchor,k;if(at(b.anchor,w)>0){k=b.head;w=ut(x.from(),b.anchor)}else{k=b.anchor;w=lt(x.to(),b.head)}var C=l.ranges.slice(0);C[o]=So(e,new na(ft(a,w),k));za(a,aa(e,C,o),U)}}var p=n.wrapper.getBoundingClientRect();var d=0;function m(t){var r=++d;var s=Ii(e,t,true,i.unit=="rectangle");if(!s){return}if(at(s,f)!=0){e.curOp.focus=P();h(s);var o=Ji(n,a);if(s.line>=o.to||s.linep.bottom?20:0;if(l){setTimeout(_n(e,function(){if(d!=r){return}n.scroller.scrollTop+=l;m(t)}),50)}}}function v(t){e.state.selectingText=false;d=Infinity;if(t){ke(t);n.input.focus()}ve(n.wrapper.ownerDocument,"mousemove",g);ve(n.wrapper.ownerDocument,"mouseup",y);a.history.lastSelOrigin=null}var g=_n(e,function(e){if(e.buttons===0||!Ae(e)){v(e)}else{m(e)}});var y=_n(e,v);e.state.selectingText=y;de(n.wrapper.ownerDocument,"mousemove",g);de(n.wrapper.ownerDocument,"mouseup",y)}function So(e,t){var r=t.anchor;var i=t.head;var n=Ye(e.doc,r.line);if(at(r,i)==0&&r.sticky==i.sticky){return t}var a=he(n);if(!a){return t}var s=ce(a,r.ch,r.sticky),o=a[s];if(o.from!=r.ch&&o.to!=r.ch){return t}var l=s+(o.from==r.ch==(o.level!=1)?0:1);if(l==0||l==a.length){return t}var u;if(i.line!=r.line){u=(i.line-r.line)*(e.doc.direction=="ltr"?1:-1)>0}else{var c=ce(a,i.ch,i.sticky);var f=c-s||(i.ch-r.ch)*(o.level==1?-1:1);if(c==l-1||c==l){u=f<0}else{u=f>0}}var h=a[l+(u?-1:0)];var p=u==(h.level==1);var d=p?h.from:h.to,m=p?"after":"before";return r.ch==d&&r.sticky==m?t:new na(new nt(r.line,d,m),i)}function Lo(e,t,r,i){var n,a;if(t.touches){n=t.touches[0].clientX;a=t.touches[0].clientY}else{try{n=t.clientX;a=t.clientY}catch(t){return false}}if(n>=Math.floor(e.display.gutters.getBoundingClientRect().right)){return false}if(i){ke(t)}var s=e.display;var o=s.lineDiv.getBoundingClientRect();if(a>o.bottom||!be(e,r)){return Se(t)}a-=o.top-s.viewOffset;for(var l=0;l=n){var c=tt(e.doc,a);var f=e.display.gutterSpecs[l];ge(e,r,e,c,f.className,t);return Se(t)}}}function To(e,t){return Lo(e,t,"gutterClick",true)}function Ao(e,t){if(Hr(e.display,t)||Eo(e,t)){return}if(ye(e,t,"contextmenu")){return}if(!C){e.display.input.onContextMenu(t)}}function Eo(e,t){if(!be(e,"gutterContextMenu")){return false}return Lo(e,t,"gutterContextMenu",false)}function Mo(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-");ui(e)}var No={toString:function(){return"CodeMirror.Init"}};var _o={};var Po={};function Io(e){var t=e.optionHandlers;function r(r,i,n,a){e.defaults[r]=i;if(n){t[r]=a?function(e,t,r){if(r!=No){n(e,t,r)}}:n}}e.defineOption=r;e.Init=No;r("value","",function(e,t){return e.setValue(t)},true);r("mode",null,function(e,t){e.doc.modeOption=t;ha(e)},true);r("indentUnit",2,ha,true);r("indentWithTabs",false);r("smartIndent",true);r("tabSize",4,function(e){pa(e);ui(e);Di(e)},true);r("lineSeparator",null,function(e,t){e.doc.lineSep=t;if(!t){return}var r=[],i=e.doc.first;e.doc.iter(function(e){for(var n=0;;){var a=e.text.indexOf(t,n);if(a==-1){break}n=a+t.length;r.push(nt(i,a))}i++});for(var n=r.length-1;n>=0;n--){rs(e.doc,t,r[n],nt(r[n].line,r[n].ch+t.length))}});r("specialChars",/[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g,function(e,t,r){e.state.specialChars=new RegExp(t.source+(t.test("\t")?"":"|\t"),"g");if(r!=No){e.refresh()}});r("specialCharPlaceholder",dr,function(e){return e.refresh()},true);r("electricChars",true);r("inputStyle",g?"contenteditable":"textarea",function(){throw new Error("inputStyle can not (yet) be changed in a running editor")},true);r("spellcheck",false,function(e,t){return e.getInputField().spellcheck=t},true);r("autocorrect",false,function(e,t){return e.getInputField().autocorrect=t},true);r("autocapitalize",false,function(e,t){return e.getInputField().autocapitalize=t},true);r("rtlMoveVisually",!b);r("wholeLineUpdateBefore",true);r("theme","default",function(e){Mo(e);Yn(e)},true);r("keyMap","default",function(e,t,r){var i=js(t);var n=r!=No&&js(r);if(n&&n.detach){n.detach(e,i)}if(i.attach){i.attach(e,n||null)}});r("extraKeys",null);r("configureMouse",null);r("lineWrapping",false,Do,true);r("gutters",[],function(e,t){e.display.gutterSpecs=$n(t,e.options.lineNumbers);Yn(e)},true);r("fixedGutter",true,function(e,t){e.display.gutters.style.left=t?Ni(e.display)+"px":"0";e.refresh()},true);r("coverGutterNextToScrollbar",false,function(e){return gn(e)},true);r("scrollbarStyle","native",function(e){bn(e);gn(e);e.display.scrollbars.setScrollTop(e.doc.scrollTop);e.display.scrollbars.setScrollLeft(e.doc.scrollLeft)},true);r("lineNumbers",false,function(e,t){e.display.gutterSpecs=$n(e.options.gutters,t);Yn(e)},true);r("firstLineNumber",1,Yn,true);r("lineNumberFormatter",function(e){return e},Yn,true);r("showCursorWhenSelecting",false,Bi,true);r("resetSelectionOnContextMenu",true);r("lineWiseCopyCut",true);r("pasteLinesPerSelection",true);r("selectionsMayTouch",false);r("readOnly",false,function(e,t){if(t=="nocursor"){Yi(e);e.display.input.blur()}e.display.input.readOnlyChanged(t)});r("disableInput",false,function(e,t){if(!t){e.display.input.reset()}},true);r("dragDrop",true,Oo);r("allowDropFileTypes",null);r("cursorBlinkRate",530);r("cursorScrollMargin",0);r("cursorHeight",1,Bi,true);r("singleCursorHeightPerLine",true,Bi,true);r("workTime",100);r("workDelay",100);r("flattenSpans",true,pa,true);r("addModeClass",false,pa,true);r("pollInterval",100);r("undoDepth",200,function(e,t){return e.doc.history.undoDepth=t});r("historyEventDelay",1250);r("viewportMargin",10,function(e){return e.refresh()},true);r("maxHighlightLength",1e4,pa,true);r("moveInputWithCursor",true,function(e,t){if(!t){e.display.input.resetPosition()}});r("tabindex",null,function(e,t){return e.display.input.getField().tabIndex=t||""});r("autofocus",null);r("direction","ltr",function(e,t){return e.doc.setDirection(t)},true);r("phrases",null)}function Oo(e,t,r){var i=r&&r!=No;if(!t!=!i){var n=e.display.dragFunctions;var a=t?de:ve;a(e.display.scroller,"dragstart",n.start);a(e.display.scroller,"dragenter",n.enter);a(e.display.scroller,"dragover",n.over);a(e.display.scroller,"dragleave",n.leave);a(e.display.scroller,"drop",n.drop)}}function Do(e){if(e.options.lineWrapping){I(e.display.wrapper,"CodeMirror-wrap");e.display.sizer.style.minWidth="";e.display.sizerWidth=null}else{L(e.display.wrapper,"CodeMirror-wrap");sr(e)}Pi(e);Di(e);ui(e);setTimeout(function(){return gn(e)},100)}function Ro(e,t){var r=this;if(!(this instanceof Ro)){return new Ro(e,t)}this.options=t=t?V(t):{};V(_o,t,false);var i=t.value;if(typeof i=="string"){i=new ws(i,t.mode,null,t.lineSeparator,t.direction)}else if(t.mode){i.modeOption=t.mode}this.doc=i;var n=new Ro.inputStyles[t.inputStyle](this);var a=this.display=new Qn(e,i,n,t);a.wrapper.CodeMirror=this;Mo(this);if(t.lineWrapping){this.display.wrapper.className+=" CodeMirror-wrap"}bn(this);this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:false,delayingBlurEvent:false,focused:false,suppressEdits:false,pasteIncoming:-1,cutIncoming:-1,selectingText:false,draggingText:false,highlight:new W,keySeq:null,specialChars:null};if(t.autofocus&&!g){a.input.focus()}if(s&&o<11){setTimeout(function(){return r.display.input.reset(true)},20)}Vo(this);Ms();kn(this);this.curOp.forceUpdate=true;ga(this,i);if(t.autofocus&&!g||this.hasFocus()){setTimeout(R(Xi,this),20)}else{Yi(this)}for(var u in Po){if(Po.hasOwnProperty(u)){Po[u](r,t[u],No)}}Kn(this);if(t.finishInit){t.finishInit(this)}for(var c=0;c20*20}de(t.scroller,"touchstart",function(n){if(!ye(e,n)&&!a(n)&&!To(e,n)){t.input.ensurePolled();clearTimeout(r);var s=+new Date;t.activeTouch={start:s,moved:false,prev:s-i.end<=300?i:null};if(n.touches.length==1){t.activeTouch.left=n.touches[0].pageX;t.activeTouch.top=n.touches[0].pageY}}});de(t.scroller,"touchmove",function(){if(t.activeTouch){t.activeTouch.moved=true}});de(t.scroller,"touchend",function(r){var i=t.activeTouch;if(i&&!Hr(t,r)&&i.left!=null&&!i.moved&&new Date-i.start<300){var a=e.coordsChar(t.activeTouch,"page"),s;if(!i.prev||l(i,i.prev)){s=new na(a,a)}else if(!i.prev.prev||l(i,i.prev.prev)){s=e.findWordAt(a)}else{s=new na(nt(a.line,0),ft(e.doc,nt(a.line+1,0)))}e.setSelection(s.anchor,s.head);e.focus();ke(r)}n()});de(t.scroller,"touchcancel",n);de(t.scroller,"scroll",function(){if(t.scroller.clientHeight){fn(e,t.scroller.scrollTop);pn(e,t.scroller.scrollLeft,true);ge(e,"scroll",e)}});de(t.scroller,"mousewheel",function(t){return ra(e,t)});de(t.scroller,"DOMMouseScroll",function(t){return ra(e,t)});de(t.wrapper,"scroll",function(){return t.wrapper.scrollTop=t.wrapper.scrollLeft=0});t.dragFunctions={enter:function(t){if(!ye(e,t)){Le(t)}},over:function(t){if(!ye(e,t)){Ls(e,t);Le(t)}},start:function(t){return Ss(e,t)},drop:_n(e,Cs),leave:function(t){if(!ye(e,t)){Ts(e)}}};var u=t.input.getField();de(u,"keyup",function(t){return uo.call(e,t)});de(u,"keydown",_n(e,oo));de(u,"keypress",_n(e,co));de(u,"focus",function(t){return Xi(e,t)});de(u,"blur",function(t){return Yi(e,t)})}var Fo=[];Ro.defineInitHook=function(e){return Fo.push(e)};function Wo(e,t,r,i){var n=e.doc,a;if(r==null){r="add"}if(r=="smart"){if(!n.mode.indent){r="prev"}else{a=yt(e,t).state}}var s=e.options.tabSize;var o=Ye(n,t),l=F(o.text,null,s);if(o.stateAfter){o.stateAfter=null}var u=o.text.match(/^\s*/)[0],c;if(!i&&!/\S/.test(o.text)){c=0;r="not"}else if(r=="smart"){c=n.mode.indent(a,o.text.slice(u.length),o.text);if(c==H||c>150){if(!i){return}r="prev"}}if(r=="prev"){if(t>n.first){c=F(Ye(n,t-1).text,null,s)}else{c=0}}else if(r=="add"){c=l+e.options.indentUnit}else if(r=="subtract"){c=l-e.options.indentUnit}else if(typeof r=="number"){c=l+r}c=Math.max(0,c);var f="",h=0;if(e.options.indentWithTabs){for(var p=Math.floor(c/s);p;--p){h+=s;f+="\t"}}if(hs;var l=Ie(t),u=null;if(o&&i.ranges.length>1){if(zo&&zo.text.join("\n")==t){if(i.ranges.length%zo.text.length==0){u=[];for(var c=0;c=0;h--){var p=i.ranges[h];var d=p.from(),m=p.to();if(p.empty()){if(r&&r>0){d=nt(d.line,d.ch-r)}else if(e.state.overwrite&&!o){m=nt(m.line,Math.min(Ye(a,m.line).text.length,m.ch+X(l).length))}else if(o&&zo&&zo.lineWise&&zo.text.join("\n")==t){d=m=nt(d.line,0)}}var v={from:d,to:m,text:u?u[h%u.length]:l,origin:n||(o?"paste":e.state.cutIncoming>s?"cut":"+input")};Ya(e.doc,v);Ar(e,"inputRead",e,v)}if(t&&!o){Uo(e,t)}sn(e);if(e.curOp.updateInput<2){e.curOp.updateInput=f}e.curOp.typing=true;e.state.pasteIncoming=e.state.cutIncoming=-1}function jo(e,t){var r=e.clipboardData&&e.clipboardData.getData("Text");if(r){e.preventDefault();if(!t.isReadOnly()&&!t.options.disableInput){Nn(t,function(){return Ho(t,r,0,null,"paste")})}return true}}function Uo(e,t){if(!e.options.electricChars||!e.options.smartIndent){return}var r=e.doc.sel;for(var i=r.ranges.length-1;i>=0;i--){var n=r.ranges[i];if(n.head.ch>100||i&&r.ranges[i-1].head.line==n.head.line){continue}var a=e.getModeAt(n.head);var s=false;if(a.electricChars){for(var o=0;o-1){s=Wo(e,n.head.line,"smart");break}}}else if(a.electricInput){if(a.electricInput.test(Ye(e.doc,n.head.line).text.slice(0,n.head.ch))){s=Wo(e,n.head.line,"smart")}}if(s){Ar(e,"electricInput",e,n.head.line)}}}function Go(e){var t=[],r=[];for(var i=0;i0){Ra(t.doc,n,new na(s,c[n].to()),j)}}else if(a.head.line>i){Wo(t,a.head.line,e,true);i=a.head.line;if(n==t.doc.sel.primIndex){sn(t)}}}}),getTokenAt:function(e,t){return Ct(this,e,t)},getLineTokens:function(e,t){return Ct(this,nt(e),t,true)},getTokenTypeAt:function(e){e=ft(this.doc,e);var t=gt(this,Ye(this.doc,e.line));var r=0,i=(t.length-1)/2,n=e.ch;var a;if(n==0){a=t[2]}else{for(;;){var s=r+i>>1;if((s?t[s*2-1]:0)>=n){i=s}else if(t[s*2+1]a){e=a;i=true}n=Ye(this.doc,e)}else{n=e}return pi(this,n,{top:0,left:0},t||"page",r||i).top+(i?this.doc.height-nr(n):0)},defaultTextHeight:function(){return Ai(this.display)},defaultCharWidth:function(){return Ei(this.display)},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(e,t,r,i,n){var a=this.display;e=vi(this,ft(this.doc,e));var s=e.bottom,o=e.left;t.style.position="absolute";t.setAttribute("cm-ignore-events","true");this.display.input.setUneditable(t);a.sizer.appendChild(t);if(i=="over"){s=e.top}else if(i=="above"||i=="near"){var l=Math.max(a.wrapper.clientHeight,this.doc.height),u=Math.max(a.sizer.clientWidth,a.lineSpace.clientWidth);if((i=="above"||e.bottom+t.offsetHeight>l)&&e.top>t.offsetHeight){s=e.top-t.offsetHeight}else if(e.bottom+t.offsetHeight<=l){s=e.bottom}if(o+t.offsetWidth>u){o=u-t.offsetWidth}}t.style.top=s+"px";t.style.left=t.style.right="";if(n=="right"){o=a.sizer.clientWidth-t.offsetWidth;t.style.right="0px"}else{if(n=="left"){o=0}else if(n=="middle"){o=(a.sizer.clientWidth-t.offsetWidth)/2}t.style.left=o+"px"}if(r){rn(this,{left:o,top:s,right:o+t.offsetWidth,bottom:s+t.offsetHeight})}},triggerOnKeyDown:Pn(oo),triggerOnKeyPress:Pn(co),triggerOnKeyUp:uo,triggerOnMouseDown:Pn(go),execCommand:function(e){if(Xs.hasOwnProperty(e)){return Xs[e].call(null,this)}},triggerElectric:Pn(function(e){Uo(this,e)}),findPosH:function(e,t,r,i){var n=this;var a=1;if(t<0){a=-1;t=-t}var s=ft(this.doc,e);for(var o=0;o0&&o(r.charAt(i-1))){--i}while(n.5){Pi(this)}ge(this,"refresh",this)}),swapDoc:Pn(function(e){var t=this.doc;t.cm=null;if(this.state.selectingText){this.state.selectingText()}ga(this,e);ui(this);this.display.input.reset();on(this,e.scrollLeft,e.scrollTop);this.curOp.forceScroll=true;Ar(this,"swapDoc",this,t);return t}),phrase:function(e){var t=this.options.phrases;return t&&Object.prototype.hasOwnProperty.call(t,e)?t[e]:e},getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}};we(e);e.registerHelper=function(t,i,n){if(!r.hasOwnProperty(t)){r[t]=e[t]={_global:[]}}r[t][i]=n};e.registerGlobalHelper=function(t,i,n,a){e.registerHelper(t,i,a);r[t]._global.push({pred:n,val:a})}}function Xo(e,t,r,i,n){var a=t;var s=r;var o=Ye(e,t.line);function l(){var i=t.line+r;if(i=e.first+e.size){return false}t=new nt(i,t.ch,t.sticky);return o=Ye(e,i)}function u(i){var a;if(n){a=$s(e.cm,o,t,r)}else{a=qs(o,t,r)}if(a==null){if(!i&&l()){t=Ks(n,e.cm,o,t.line,r)}else{return false}}else{t=a}return true}if(i=="char"){u()}else if(i=="column"){u(true)}else if(i=="word"||i=="group"){var c=null,f=i=="group";var h=e.cm&&e.cm.getHelper(t,"wordChars");for(var p=true;;p=false){if(r<0&&!u(!p)){break}var d=o.text.charAt(t.ch)||"\n";var m=re(d,h)?"w":f&&d=="\n"?"n":!f||/\s/.test(d)?null:"p";if(f&&!p&&!m){m="s"}if(c&&c!=m){if(r<0){r=1;u();t.sticky="after"}break}if(m){c=m}if(r>0&&!u(!p)){break}}}var v=qa(e,t,a,s,true);if(st(a,v)){v.hitSide=true}return v}function Yo(e,t,r,i){var n=e.doc,a=t.left,s;if(i=="page"){var o=Math.min(e.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight);var l=Math.max(o-.5*Ai(e.display),3);s=(r>0?t.bottom:t.top)+r*l}else if(i=="line"){s=r>0?t.bottom+3:t.top-3}var u;for(;;){u=xi(e,a,s);if(!u.outside){break}if(r<0?s<=0:s>=n.height){u.hitSide=true;break}s+=r*5}return u}var Qo=function(e){this.cm=e;this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null;this.polling=new W;this.composing=null;this.gracePeriod=false;this.readDOMTimeout=null};Qo.prototype.init=function(e){var t=this;var r=this,i=r.cm;var n=r.div=e.lineDiv;qo(n,i.options.spellcheck,i.options.autocorrect,i.options.autocapitalize);de(n,"paste",function(e){if(ye(i,e)||jo(e,i)){return}if(o<=11){setTimeout(_n(i,function(){return t.updateFromDOM()}),20)}});de(n,"compositionstart",function(e){t.composing={data:e.data,done:false}});de(n,"compositionupdate",function(e){if(!t.composing){t.composing={data:e.data,done:false}}});de(n,"compositionend",function(e){if(t.composing){if(e.data!=t.composing.data){t.readFromDOMSoon()}t.composing.done=true}});de(n,"touchstart",function(){return r.forceCompositionEnd()});de(n,"input",function(){if(!t.composing){t.readFromDOMSoon()}});function a(e){if(ye(i,e)){return}if(i.somethingSelected()){Bo({lineWise:false,text:i.getSelections()});if(e.type=="cut"){i.replaceSelection("",null,"cut")}}else if(!i.options.lineWiseCopyCut){return}else{var t=Go(i);Bo({lineWise:true,text:t.text});if(e.type=="cut"){i.operation(function(){i.setSelections(t.ranges,0,j) -;i.replaceSelection("",null,"cut")})}}if(e.clipboardData){e.clipboardData.clearData();var a=zo.text.join("\n");e.clipboardData.setData("Text",a);if(e.clipboardData.getData("Text")==a){e.preventDefault();return}}var s=Ko(),o=s.firstChild;i.display.lineSpace.insertBefore(s,i.display.lineSpace.firstChild);o.value=zo.text.join("\n");var l=document.activeElement;D(o);setTimeout(function(){i.display.lineSpace.removeChild(s);l.focus();if(l==n){r.showPrimarySelection()}},50)}de(n,"copy",a);de(n,"cut",a)};Qo.prototype.prepareSelection=function(){var e=Hi(this.cm,false);e.focus=this.cm.state.focused;return e};Qo.prototype.showSelection=function(e,t){if(!e||!this.cm.display.view.length){return}if(e.focus||t){this.showPrimarySelection()}this.showMultipleSelections(e)};Qo.prototype.getSelection=function(){return this.cm.display.wrapper.ownerDocument.getSelection()};Qo.prototype.showPrimarySelection=function(){var e=this.getSelection(),t=this.cm,i=t.doc.sel.primary();var n=i.from(),a=i.to();if(t.display.viewTo==t.display.viewFrom||n.line>=t.display.viewTo||a.line=t.display.viewFrom&&Zo(t,n)||{node:l[0].measure.map[2],offset:0};var c=a.linee.firstLine()){i=nt(i.line-1,Ye(e.doc,i.line-1).length)}if(n.ch==Ye(e.doc,n.line).text.length&&n.linet.viewTo-1){return false}var a,s,o;if(i.line==t.viewFrom||(a=Oi(e,i.line))==0){s=et(t.view[0].line);o=t.view[0].node}else{s=et(t.view[a].line);o=t.view[a-1].node.nextSibling}var l=Oi(e,n.line);var u,c;if(l==t.view.length-1){u=t.viewTo-1;c=t.lineDiv.lastChild}else{u=et(t.view[l+1].line)-1;c=t.view[l+1].node.previousSibling}if(!o){return false}var f=e.doc.splitLines(tl(e,o,c,s,u));var h=Qe(e.doc,nt(s,0),nt(u,Ye(e.doc,u).text.length));while(f.length>1&&h.length>1){if(X(f)==X(h)){f.pop();h.pop();u--}else if(f[0]==h[0]){f.shift();h.shift();s++}else{break}}var p=0,d=0;var m=f[0],v=h[0],g=Math.min(m.length,v.length);while(pi.ch&&y.charCodeAt(y.length-d-1)==x.charCodeAt(x.length-d-1)){p--;d++}}f[f.length-1]=y.slice(0,y.length-d).replace(/^\u200b+/,"");f[0]=f[0].slice(p).replace(/\u200b+$/,"");var w=nt(s,p);var k=nt(u,h.length?X(h).length-d:0);if(f.length>1||f[0]||at(w,k)){rs(e.doc,f,w,k,"+input");return true}};Qo.prototype.ensurePolled=function(){this.forceCompositionEnd()};Qo.prototype.reset=function(){this.forceCompositionEnd()};Qo.prototype.forceCompositionEnd=function(){if(!this.composing){return}clearTimeout(this.readDOMTimeout);this.composing=null;this.updateFromDOM();this.div.blur();this.div.focus()};Qo.prototype.readFromDOMSoon=function(){var e=this;if(this.readDOMTimeout!=null){return}this.readDOMTimeout=setTimeout(function(){e.readDOMTimeout=null;if(e.composing){if(e.composing.done){e.composing=null}else{return}}e.updateFromDOM()},80)};Qo.prototype.updateFromDOM=function(){var e=this;if(this.cm.isReadOnly()||!this.pollContent()){Nn(this.cm,function(){return Di(e.cm)})}};Qo.prototype.setUneditable=function(e){e.contentEditable="false"};Qo.prototype.onKeyPress=function(e){if(e.charCode==0||this.composing){return}e.preventDefault();if(!this.cm.isReadOnly()){_n(this.cm,Ho)(this.cm,String.fromCharCode(e.charCode==null?e.keyCode:e.charCode),0)}};Qo.prototype.readOnlyChanged=function(e){this.div.contentEditable=String(e!="nocursor")};Qo.prototype.onContextMenu=function(){};Qo.prototype.resetPosition=function(){};Qo.prototype.needsContentAttribute=true;function Zo(e,t){var r=Jr(e,t.line);if(!r||r.hidden){return null}var i=Ye(e.doc,t.line);var n=Yr(r,i,t.line);var a=he(i,e.doc.direction),s="left";if(a){var o=ce(a,t.ch);s=o%2?"right":"left"}var l=ii(n.map,t.ch,s);l.offset=l.collapse=="right"?l.end:l.start;return l}function Jo(e){for(var t=e;t;t=t.parentNode){if(/CodeMirror-gutter-wrapper/.test(t.className)){return true}}return false}function el(e,t){if(t){e.bad=true}return e}function tl(e,t,r,i,n){var a="",s=false,o=e.doc.lineSeparator(),l=false;function u(e){return function(t){return t.id==e}}function c(){if(s){a+=o;if(l){a+=o}s=l=false}}function f(e){if(e){c();a+=e}}function h(t){if(t.nodeType==1){var r=t.getAttribute("cm-text");if(r){f(r);return}var a=t.getAttribute("cm-marker"),p;if(a){var d=e.findMarks(nt(i,0),nt(n+1,0),u(+a));if(d.length&&(p=d[0].find(0))){f(Qe(e.doc,p.from,p.to).join(o))}return}if(t.getAttribute("contenteditable")=="false"){return}var m=/^(pre|div|p|li|table|br)$/i.test(t.nodeName);if(!/^br$/i.test(t.nodeName)&&t.textContent.length==0){return}if(m){c()}for(var v=0;v=9&&t.hasSelection){t.hasSelection=null}r.poll()});de(n,"paste",function(e){if(ye(i,e)||jo(e,i)){return}i.state.pasteIncoming=+new Date;r.fastPoll()});function a(e){if(ye(i,e)){return}if(i.somethingSelected()){Bo({lineWise:false,text:i.getSelections()})}else if(!i.options.lineWiseCopyCut){return}else{var t=Go(i);Bo({lineWise:true,text:t.text});if(e.type=="cut"){i.setSelections(t.ranges,null,j)}else{r.prevInput="";n.value=t.text.join("\n");D(n)}}if(e.type=="cut"){i.state.cutIncoming=+new Date}}de(n,"cut",a);de(n,"copy",a);de(e.scroller,"paste",function(t){if(Hr(e,t)||ye(i,t)){return}if(!n.dispatchEvent){i.state.pasteIncoming=+new Date;r.focus();return}var a=new Event("paste");a.clipboardData=t.clipboardData;n.dispatchEvent(a)});de(e.lineSpace,"selectstart",function(t){if(!Hr(e,t)){ke(t)}});de(n,"compositionstart",function(){var e=i.getCursor("from");if(r.composing){r.composing.range.clear()}r.composing={start:e,range:i.markText(e,i.getCursor("to"),{className:"CodeMirror-composing"})}});de(n,"compositionend",function(){if(r.composing){r.poll();r.composing.range.clear();r.composing=null}})};nl.prototype.createField=function(e){this.wrapper=Ko();this.textarea=this.wrapper.firstChild};nl.prototype.prepareSelection=function(){var e=this.cm,t=e.display,r=e.doc;var i=Hi(e);if(e.options.moveInputWithCursor){var n=vi(e,r.sel.primary().head,"div");var a=t.wrapper.getBoundingClientRect(),s=t.lineDiv.getBoundingClientRect();i.teTop=Math.max(0,Math.min(t.wrapper.clientHeight-10,n.top+s.top-a.top));i.teLeft=Math.max(0,Math.min(t.wrapper.clientWidth-10,n.left+s.left-a.left))}return i};nl.prototype.showSelection=function(e){var t=this.cm,r=t.display;A(r.cursorDiv,e.cursors);A(r.selectionDiv,e.selection);if(e.teTop!=null){this.wrapper.style.top=e.teTop+"px";this.wrapper.style.left=e.teLeft+"px"}};nl.prototype.reset=function(e){if(this.contextMenuPending||this.composing){return}var t=this.cm;if(t.somethingSelected()){this.prevInput="";var r=t.getSelection();this.textarea.value=r;if(t.state.focused){D(this.textarea)}if(s&&o>=9){this.hasSelection=r}}else if(!e){this.prevInput=this.textarea.value="";if(s&&o>=9){this.hasSelection=null}}};nl.prototype.getField=function(){return this.textarea};nl.prototype.supportsTouch=function(){return false};nl.prototype.focus=function(){if(this.cm.options.readOnly!="nocursor"&&(!g||P()!=this.textarea)){try{this.textarea.focus()}catch(e){}}};nl.prototype.blur=function(){this.textarea.blur()};nl.prototype.resetPosition=function(){this.wrapper.style.top=this.wrapper.style.left=0};nl.prototype.receivedFocus=function(){this.slowPoll()};nl.prototype.slowPoll=function(){var e=this;if(this.pollingFast){return}this.polling.set(this.cm.options.pollInterval,function(){e.poll();if(e.cm.state.focused){e.slowPoll()}})};nl.prototype.fastPoll=function(){var e=false,t=this;t.pollingFast=true;function r(){var i=t.poll();if(!i&&!e){e=true;t.polling.set(60,r)}else{t.pollingFast=false;t.slowPoll()}}t.polling.set(20,r)};nl.prototype.poll=function(){var e=this;var t=this.cm,r=this.textarea,i=this.prevInput;if(this.contextMenuPending||!t.state.focused||Oe(r)&&!i&&!this.composing||t.isReadOnly()||t.options.disableInput||t.state.keySeq){return false}var n=r.value;if(n==i&&!t.somethingSelected()){return false}if(s&&o>=9&&this.hasSelection===n||y&&/[\uf700-\uf7ff]/.test(n)){t.display.input.reset();return false}if(t.doc.sel==t.display.selForContextMenu){var a=n.charCodeAt(0);if(a==8203&&!i){i="​"}if(a==8666){this.reset();return this.cm.execCommand("undo")}}var l=0,u=Math.min(i.length,n.length);while(l1e3||n.indexOf("\n")>-1){r.value=e.prevInput=""}else{e.prevInput=n}if(e.composing){e.composing.range.clear();e.composing.range=t.markText(e.composing.start,t.getCursor("to"),{className:"CodeMirror-composing"})}});return true};nl.prototype.ensurePolled=function(){if(this.pollingFast&&this.poll()){this.pollingFast=false}};nl.prototype.onKeyPress=function(){if(s&&o>=9){this.hasSelection=null}this.fastPoll()};nl.prototype.onContextMenu=function(e){var t=this,r=t.cm,i=r.display,n=t.textarea;if(t.contextMenuPending){t.contextMenuPending()}var a=Ii(r,e),u=i.scroller.scrollTop;if(!a||f){return}var c=r.options.resetSelectionOnContextMenu;if(c&&r.doc.sel.contains(a)==-1){_n(r,za)(r.doc,sa(a),j)}var h=n.style.cssText,p=t.wrapper.style.cssText;var d=t.wrapper.offsetParent.getBoundingClientRect();t.wrapper.style.cssText="position: static";n.style.cssText="position: absolute; width: 30px; height: 30px;\n top: "+(e.clientY-d.top-5)+"px; left: "+(e.clientX-d.left-5)+"px;\n z-index: 1000; background: "+(s?"rgba(255, 255, 255, .05)":"transparent")+";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";var m;if(l){m=window.scrollY}i.input.focus();if(l){window.scrollTo(null,m)}i.input.reset();if(!r.somethingSelected()){n.value=t.prevInput=" "}t.contextMenuPending=g;i.selForContextMenu=r.doc.sel;clearTimeout(i.detectingSelectAll);function v(){if(n.selectionStart!=null){var e=r.somethingSelected();var a="​"+(e?n.value:"");n.value="⇚";n.value=a;t.prevInput=e?"":"​";n.selectionStart=1;n.selectionEnd=a.length;i.selForContextMenu=r.doc.sel}}function g(){if(t.contextMenuPending!=g){return}t.contextMenuPending=false;t.wrapper.style.cssText=p;n.style.cssText=h;if(s&&o<9){i.scrollbars.setScrollTop(i.scroller.scrollTop=u)}if(n.selectionStart!=null){if(!s||s&&o<9){v()}var e=0,a=function(){if(i.selForContextMenu==r.doc.sel&&n.selectionStart==0&&n.selectionEnd>0&&t.prevInput=="​"){_n(r,$a)(r)}else if(e++<10){i.detectingSelectAll=setTimeout(a,500)}else{i.selForContextMenu=null;i.input.reset()}};i.detectingSelectAll=setTimeout(a,200)}}if(s&&o>=9){v()}if(C){Le(e);var y=function(){ve(window,"mouseup",y);setTimeout(g,20)};de(window,"mouseup",y)}else{setTimeout(g,50)}};nl.prototype.readOnlyChanged=function(e){if(!e){this.reset()}this.textarea.disabled=e=="nocursor"};nl.prototype.setUneditable=function(){};nl.prototype.needsContentAttribute=false;function al(e,t){t=t?V(t):{};t.value=e.value;if(!t.tabindex&&e.tabIndex){t.tabindex=e.tabIndex}if(!t.placeholder&&e.placeholder){t.placeholder=e.placeholder}if(t.autofocus==null){var r=P();t.autofocus=r==e||e.getAttribute("autofocus")!=null&&r==document.body}function i(){e.value=o.getValue()}var n;if(e.form){de(e.form,"submit",i);if(!t.leaveSubmitMethodAlone){var a=e.form;n=a.submit;try{var s=a.submit=function(){i();a.submit=n;a.submit();a.submit=s}}catch(e){}}}t.finishInit=function(r){r.save=i;r.getTextArea=function(){return e};r.toTextArea=function(){r.toTextArea=isNaN;i();e.parentNode.removeChild(r.getWrapperElement());e.style.display="";if(e.form){ve(e.form,"submit",i);if(!t.leaveSubmitMethodAlone&&typeof e.form.submit=="function"){e.form.submit=n}}}};e.style.display="none";var o=Ro(function(t){return e.parentNode.insertBefore(t,e.nextSibling)},t);return o}function sl(e){e.off=ve;e.on=de;e.wheelEventPixels=ta;e.Doc=ws;e.splitLines=Ie;e.countColumn=F;e.findColumn=q;e.isWordChar=te;e.Pass=H;e.signal=ge;e.Line=or;e.changeEnd=oa;e.scrollbarModel=xn;e.Pos=nt;e.cmpPos=at;e.modes=Fe;e.mimeModes=We;e.resolveMode=He;e.getMode=je;e.modeExtensions=Ue;e.extendMode=Ge;e.copyState=qe;e.startState=$e;e.innerMode=Ke;e.commands=Xs;e.keyMap=Rs;e.keyName=Hs;e.isModifierKey=zs;e.lookupKey=Ws;e.normalizeKeyMap=Fs;e.StringStream=Xe;e.SharedTextMarker=ms;e.TextMarker=ps;e.LineWidget=us;e.e_preventDefault=ke;e.e_stopPropagation=Ce;e.e_stop=Le;e.addClass=I;e.contains=_;e.rmClass=L;e.keyNames=Ps}Io(Ro);$o(Ro);var ol="iter insert remove copy getEditor constructor".split(" ");for(var ll in ws.prototype){if(ws.prototype.hasOwnProperty(ll)&&z(ol,ll)<0){Ro.prototype[ll]=function(e){return function(){return e.apply(this.doc,arguments)}}(ws.prototype[ll])}}we(ws);Ro.inputStyles={textarea:nl,contenteditable:Qo};Ro.defineMode=function(e){if(!Ro.defaults.mode&&e!="null"){Ro.defaults.mode=e}ze.apply(this,arguments)};Ro.defineMIME=Be;Ro.defineMode("null",function(){return{token:function(e){return e.skipToEnd()}}});Ro.defineMIME("text/plain","null");Ro.defineExtension=function(e,t){Ro.prototype[e]=t};Ro.defineDocExtension=function(e,t){ws.prototype[e]=t};Ro.fromTextArea=al;sl(Ro);Ro.version="5.49.2";return Ro});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror"],e);else e(CodeMirror)})(function(e){"use strict";e.defineMode("javascript",function(t,r){var i=t.indentUnit;var n=r.statementIndent;var a=r.jsonld;var s=r.json||a;var o=r.typescript;var l=r.wordCharacters||/[\w$\xa1-\uffff]/;var u=function(){function e(e){return{type:e,style:"keyword"}}var t=e("keyword a"),r=e("keyword b"),i=e("keyword c"),n=e("keyword d");var a=e("operator"),s={type:"atom",style:"atom"};return{if:e("if"),while:t,with:t,else:r,do:r,try:r,finally:r,return:n,break:n,continue:n,new:e("new"),delete:i,void:i,throw:i,debugger:e("debugger"),var:e("var"),const:e("var"),let:e("var"),function:e("function"),catch:e("catch"),for:e("for"),switch:e("switch"),case:e("case"),default:e("default"),in:a,typeof:a,instanceof:a,true:s,false:s,null:s,undefined:s,NaN:s,Infinity:s,this:e("this"),class:e("class"),super:e("atom"),yield:i,export:e("export"),import:e("import"),extends:i,await:i}}();var c=/[+\-*&%=<>!?|~^@]/;var f=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;function h(e){var t=false,r,i=false;while((r=e.next())!=null){if(!t){if(r=="/"&&!i)return;if(r=="[")i=true;else if(i&&r=="]")i=false}t=!t&&r=="\\"}}var p,d;function m(e,t,r){p=e;d=r;return t}function v(e,t){var r=e.next();if(r=='"'||r=="'"){t.tokenize=g(r);return t.tokenize(e,t)}else if(r=="."&&e.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)){return m("number","number")}else if(r=="."&&e.match("..")){return m("spread","meta")}else if(/[\[\]{}\(\),;\:\.]/.test(r)){return m(r)}else if(r=="="&&e.eat(">")){return m("=>","operator")}else if(r=="0"&&e.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)){return m("number","number")}else if(/\d/.test(r)){e.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);return m("number","number")}else if(r=="/"){if(e.eat("*")){t.tokenize=y;return y(e,t)}else if(e.eat("/")){e.skipToEnd();return m("comment","comment")}else if(et(e,t,1)){h(e);e.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);return m("regexp","string-2")}else{e.eat("=");return m("operator","operator",e.current())}}else if(r=="`"){t.tokenize=x;return x(e,t)}else if(r=="#"){e.skipToEnd();return m("error","error")}else if(r=="<"&&e.match("!--")||r=="-"&&e.match("->")){e.skipToEnd();return m("comment","comment")}else if(c.test(r)){if(r!=">"||!t.lexical||t.lexical.type!=">"){if(e.eat("=")){if(r=="!"||r=="=")e.eat("=")}else if(/[<>*+\-]/.test(r)){e.eat(r);if(r==">")e.eat(r)}}return m("operator","operator",e.current())}else if(l.test(r)){e.eatWhile(l);var i=e.current();if(t.lastType!="."){if(u.propertyIsEnumerable(i)){var n=u[i];return m(n.type,n.style,i)}if(i=="async"&&e.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/,false))return m("async","keyword",i)}return m("variable","variable",i)}}function g(e){return function(t,r){var i=false,n;if(a&&t.peek()=="@"&&t.match(f)){r.tokenize=v;return m("jsonld-keyword","meta")}while((n=t.next())!=null){if(n==e&&!i)break;i=!i&&n=="\\"}if(!i)r.tokenize=v;return m("string","string")}}function y(e,t){var r=false,i;while(i=e.next()){if(i=="/"&&r){t.tokenize=v;break}r=i=="*"}return m("comment","comment")}function x(e,t){var r=false,i;while((i=e.next())!=null){if(!r&&(i=="`"||i=="$"&&e.eat("{"))){t.tokenize=v;break}r=!r&&i=="\\"}return m("quasi","string-2",e.current())}var b="([{}])";function w(e,t){if(t.fatArrowAt)t.fatArrowAt=null;var r=e.string.indexOf("=>",e.start);if(r<0)return;if(o){var i=/:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(e.string.slice(e.start,r));if(i)r=i.index}var n=0,a=false;for(var s=r-1;s>=0;--s){var u=e.string.charAt(s);var c=b.indexOf(u);if(c>=0&&c<3){if(!n){++s;break}if(--n==0){if(u=="(")a=true;break}}else if(c>=3&&c<6){++n}else if(l.test(u)){a=true}else if(/["'\/`]/.test(u)){for(;;--s){if(s==0)return;var f=e.string.charAt(s-1);if(f==u&&e.string.charAt(s-2)!="\\"){s--;break}}}else if(a&&!n){++s;break}}if(a&&!n)t.fatArrowAt=s}var k={atom:true,number:true,variable:true,string:true,regexp:true,this:true,"jsonld-keyword":true};function C(e,t,r,i,n,a){this.indented=e;this.column=t;this.type=r;this.prev=n;this.info=a;if(i!=null)this.align=i}function S(e,t){for(var r=e.localVars;r;r=r.next)if(r.name==t)return true;for(var i=e.context;i;i=i.prev){for(var r=i.vars;r;r=r.next)if(r.name==t)return true}}function L(e,t,r,i,n){var a=e.cc;T.state=e;T.stream=n;T.marked=null,T.cc=a;T.style=t;if(!e.lexical.hasOwnProperty("align"))e.lexical.align=true;while(true){var o=a.length?a.pop():s?U:H;if(o(r,i)){while(a.length&&a[a.length-1].lex)a.pop()();if(T.marked)return T.marked;if(r=="variable"&&S(e,i))return"variable-2";return t}}}var T={state:null,column:null,marked:null,cc:null};function A(){for(var e=arguments.length-1;e>=0;e--)T.cc.push(arguments[e])}function E(){A.apply(null,arguments);return true}function M(e,t){for(var r=t;r;r=r.next)if(r.name==e)return true;return false}function N(e){var t=T.state;T.marked="def";if(t.context){if(t.lexical.info=="var"&&t.context&&t.context.block){var i=_(e,t.context);if(i!=null){t.context=i;return}}else if(!M(e,t.localVars)){t.localVars=new O(e,t.localVars);return}}if(r.globalVars&&!M(e,t.globalVars))t.globalVars=new O(e,t.globalVars)}function _(e,t){if(!t){return null}else if(t.block){var r=_(e,t.prev);if(!r)return null;if(r==t.prev)return t;return new I(r,t.vars,true)}else if(M(e,t.vars)){return t}else{return new I(t.prev,new O(e,t.vars),false)}}function P(e){return e=="public"||e=="private"||e=="protected"||e=="abstract"||e=="readonly"}function I(e,t,r){this.prev=e;this.vars=t;this.block=r}function O(e,t){this.name=e;this.next=t}var D=new O("this",new O("arguments",null));function R(){T.state.context=new I(T.state.context,T.state.localVars,false);T.state.localVars=D}function V(){T.state.context=new I(T.state.context,T.state.localVars,true);T.state.localVars=null}function F(){T.state.localVars=T.state.context.vars;T.state.context=T.state.context.prev}F.lex=true;function W(e,t){var r=function(){var r=T.state,i=r.indented;if(r.lexical.type=="stat")i=r.lexical.indented;else for(var n=r.lexical;n&&n.type==")"&&n.align;n=n.prev)i=n.indented;r.lexical=new C(i,T.stream.column(),e,null,r.lexical,t)};r.lex=true;return r}function z(){var e=T.state;if(e.lexical.prev){if(e.lexical.type==")")e.indented=e.lexical.indented;e.lexical=e.lexical.prev}}z.lex=true;function B(e){function t(r){if(r==e)return E();else if(e==";"||r=="}"||r==")"||r=="]")return A();else return E(t)}return t}function H(e,t){if(e=="var")return E(W("vardef",t),Se,B(";"),z);if(e=="keyword a")return E(W("form"),q,H,z);if(e=="keyword b")return E(W("form"),H,z);if(e=="keyword d")return T.stream.match(/^\s*$/,false)?E():E(W("stat"),$,B(";"),z);if(e=="debugger")return E(B(";"));if(e=="{")return E(W("}"),V,fe,z,F);if(e==";")return E();if(e=="if"){if(T.state.lexical.info=="else"&&T.state.cc[T.state.cc.length-1]==z)T.state.cc.pop()();return E(W("form"),q,H,z,Ne)}if(e=="function")return E(Oe);if(e=="for")return E(W("form"),_e,H,z);if(e=="class"||o&&t=="interface"){T.marked="keyword";return E(W("form",e=="class"?e:t),We,z)}if(e=="variable"){if(o&&t=="declare"){T.marked="keyword";return E(H)}else if(o&&(t=="module"||t=="enum"||t=="type")&&T.stream.match(/^\s*\w/,false)){T.marked="keyword";if(t=="enum")return E(Qe);else if(t=="type")return E(Re,B("operator"),ve,B(";"));else return E(W("form"),Le,B("{"),W("}"),fe,z,z)}else if(o&&t=="namespace"){T.marked="keyword";return E(W("form"),U,H,z)}else if(o&&t=="abstract"){T.marked="keyword";return E(H)}else{return E(W("stat"),ne)}}if(e=="switch")return E(W("form"),q,B("{"),W("}","switch"),V,fe,z,z,F);if(e=="case")return E(U,B(":"));if(e=="default")return E(B(":"));if(e=="catch")return E(W("form"),R,j,H,z,F);if(e=="export")return E(W("stat"),je,z);if(e=="import")return E(W("stat"),Ge,z);if(e=="async")return E(H);if(t=="@")return E(U,H);return A(W("stat"),U,B(";"),z)}function j(e){if(e=="(")return E(Ve,B(")"))}function U(e,t){return K(e,t,false)}function G(e,t){return K(e,t,true)}function q(e){if(e!="(")return A();return E(W(")"),U,B(")"),z)}function K(e,t,r){if(T.state.fatArrowAt==T.stream.start){var i=r?ee:J;if(e=="(")return E(R,W(")"),ue(Ve,")"),z,B("=>"),i,F);else if(e=="variable")return A(R,Le,B("=>"),i,F)}var n=r?Y:X;if(k.hasOwnProperty(e))return E(n);if(e=="function")return E(Oe,n);if(e=="class"||o&&t=="interface"){T.marked="keyword";return E(W("form"),Fe,z)}if(e=="keyword c"||e=="async")return E(r?G:U);if(e=="(")return E(W(")"),$,B(")"),z,n);if(e=="operator"||e=="spread")return E(r?G:U);if(e=="[")return E(W("]"),Ye,z,n);if(e=="{")return ce(se,"}",null,n);if(e=="quasi")return A(Q,n);if(e=="new")return E(te(r));if(e=="import")return E(U);return E()}function $(e){if(e.match(/[;\}\)\],]/))return A();return A(U)}function X(e,t){if(e==",")return E(U);return Y(e,t,false)}function Y(e,t,r){var i=r==false?X:Y;var n=r==false?U:G;if(e=="=>")return E(R,r?ee:J,F);if(e=="operator"){if(/\+\+|--/.test(t)||o&&t=="!")return E(i);if(o&&t=="<"&&T.stream.match(/^([^>]|<.*?>)*>\s*\(/,false))return E(W(">"),ue(ve,">"),z,i);if(t=="?")return E(U,B(":"),n);return E(n)}if(e=="quasi"){return A(Q,i)}if(e==";")return;if(e=="(")return ce(G,")","call",i);if(e==".")return E(ae,i);if(e=="[")return E(W("]"),$,B("]"),z,i);if(o&&t=="as"){T.marked="keyword";return E(ve,i)}if(e=="regexp"){T.state.lastType=T.marked="operator";T.stream.backUp(T.stream.pos-T.stream.start-1);return E(n)}}function Q(e,t){if(e!="quasi")return A();if(t.slice(t.length-2)!="${")return E(Q);return E(U,Z)}function Z(e){if(e=="}"){T.marked="string-2";T.state.tokenize=x;return E(Q)}}function J(e){w(T.stream,T.state);return A(e=="{"?H:U)}function ee(e){w(T.stream,T.state);return A(e=="{"?H:G)}function te(e){return function(t){if(t==".")return E(e?ie:re);else if(t=="variable"&&o)return E(we,e?Y:X);else return A(e?G:U)}}function re(e,t){if(t=="target"){T.marked="keyword";return E(X)}}function ie(e,t){if(t=="target"){T.marked="keyword";return E(Y)}}function ne(e){if(e==":")return E(z,H);return A(X,B(";"),z)}function ae(e){if(e=="variable"){T.marked="property";return E()}}function se(e,t){if(e=="async"){T.marked="property";return E(se)}else if(e=="variable"||T.style=="keyword"){T.marked="property";if(t=="get"||t=="set")return E(oe);var r;if(o&&T.state.fatArrowAt==T.stream.start&&(r=T.stream.match(/^\s*:\s*/,false)))T.state.fatArrowAt=T.stream.pos+r[0].length;return E(le)}else if(e=="number"||e=="string"){T.marked=a?"property":T.style+" property";return E(le)}else if(e=="jsonld-keyword"){return E(le)}else if(o&&P(t)){T.marked="keyword";return E(se)}else if(e=="["){return E(U,he,B("]"),le)}else if(e=="spread"){return E(G,le)}else if(t=="*"){T.marked="keyword";return E(se)}else if(e==":"){return A(le)}}function oe(e){if(e!="variable")return A(le);T.marked="property";return E(Oe)}function le(e){if(e==":")return E(G);if(e=="(")return A(Oe)}function ue(e,t,r){function i(n,a){if(r?r.indexOf(n)>-1:n==","){var s=T.state.lexical;if(s.info=="call")s.pos=(s.pos||0)+1;return E(function(r,i){if(r==t||i==t)return A();return A(e)},i)}if(n==t||a==t)return E();if(r&&r.indexOf(";")>-1)return A(e);return E(B(t))}return function(r,n){if(r==t||n==t)return E();return A(e,i)}}function ce(e,t,r){for(var i=3;i"),ve)}function ge(e){if(e=="=>")return E(ve)}function ye(e,t){if(e=="variable"||T.style=="keyword"){T.marked="property";return E(ye)}else if(t=="?"||e=="number"||e=="string"){return E(ye)}else if(e==":"){return E(ve)}else if(e=="["){return E(B("variable"),pe,B("]"),ye)}else if(e=="("){return A(De,ye)}}function xe(e,t){if(e=="variable"&&T.stream.match(/^\s*[?:]/,false)||t=="?")return E(xe);if(e==":")return E(ve);if(e=="spread")return E(xe);return A(ve)}function be(e,t){if(t=="<")return E(W(">"),ue(ve,">"),z,be);if(t=="|"||e=="."||t=="&")return E(ve);if(e=="[")return E(ve,B("]"),be);if(t=="extends"||t=="implements"){T.marked="keyword";return E(ve)}if(t=="?")return E(ve,B(":"),ve)}function we(e,t){if(t=="<")return E(W(">"),ue(ve,">"),z,be)}function ke(){return A(ve,Ce)}function Ce(e,t){if(t=="=")return E(ve)}function Se(e,t){if(t=="enum"){T.marked="keyword";return E(Qe)}return A(Le,he,Ee,Me)}function Le(e,t){if(o&&P(t)){T.marked="keyword";return E(Le)}if(e=="variable"){N(t);return E()}if(e=="spread")return E(Le);if(e=="[")return ce(Ae,"]");if(e=="{")return ce(Te,"}")}function Te(e,t){if(e=="variable"&&!T.stream.match(/^\s*:/,false)){N(t);return E(Ee)}if(e=="variable")T.marked="property";if(e=="spread")return E(Le);if(e=="}")return A();if(e=="[")return E(U,B("]"),B(":"),Te);return E(B(":"),Le,Ee)}function Ae(){return A(Le,Ee)}function Ee(e,t){if(t=="=")return E(G)}function Me(e){if(e==",")return E(Se)}function Ne(e,t){if(e=="keyword b"&&t=="else")return E(W("form","else"),H,z)}function _e(e,t){if(t=="await")return E(_e);if(e=="(")return E(W(")"),Pe,z)}function Pe(e){if(e=="var")return E(Se,Ie);if(e=="variable")return E(Ie);return A(Ie)}function Ie(e,t){if(e==")")return E();if(e==";")return E(Ie);if(t=="in"||t=="of"){T.marked="keyword";return E(U,Ie)}return A(U,Ie)}function Oe(e,t){if(t=="*"){T.marked="keyword";return E(Oe) -}if(e=="variable"){N(t);return E(Oe)}if(e=="(")return E(R,W(")"),ue(Ve,")"),z,de,H,F);if(o&&t=="<")return E(W(">"),ue(ke,">"),z,Oe)}function De(e,t){if(t=="*"){T.marked="keyword";return E(De)}if(e=="variable"){N(t);return E(De)}if(e=="(")return E(R,W(")"),ue(Ve,")"),z,de,F);if(o&&t=="<")return E(W(">"),ue(ke,">"),z,De)}function Re(e,t){if(e=="keyword"||e=="variable"){T.marked="type";return E(Re)}else if(t=="<"){return E(W(">"),ue(ke,">"),z)}}function Ve(e,t){if(t=="@")E(U,Ve);if(e=="spread")return E(Ve);if(o&&P(t)){T.marked="keyword";return E(Ve)}if(o&&e=="this")return E(he,Ee);return A(Le,he,Ee)}function Fe(e,t){if(e=="variable")return We(e,t);return ze(e,t)}function We(e,t){if(e=="variable"){N(t);return E(ze)}}function ze(e,t){if(t=="<")return E(W(">"),ue(ke,">"),z,ze);if(t=="extends"||t=="implements"||o&&e==","){if(t=="implements")T.marked="keyword";return E(o?ve:U,ze)}if(e=="{")return E(W("}"),Be,z)}function Be(e,t){if(e=="async"||e=="variable"&&(t=="static"||t=="get"||t=="set"||o&&P(t))&&T.stream.match(/^\s+[\w$\xa1-\uffff]/,false)){T.marked="keyword";return E(Be)}if(e=="variable"||T.style=="keyword"){T.marked="property";return E(o?He:Oe,Be)}if(e=="number"||e=="string")return E(o?He:Oe,Be);if(e=="[")return E(U,he,B("]"),o?He:Oe,Be);if(t=="*"){T.marked="keyword";return E(Be)}if(o&&e=="(")return A(De,Be);if(e==";"||e==",")return E(Be);if(e=="}")return E();if(t=="@")return E(U,Be)}function He(e,t){if(t=="?")return E(He);if(e==":")return E(ve,Ee);if(t=="=")return E(G);var r=T.state.lexical.prev,i=r&&r.info=="interface";return A(i?De:Oe)}function je(e,t){if(t=="*"){T.marked="keyword";return E(Xe,B(";"))}if(t=="default"){T.marked="keyword";return E(U,B(";"))}if(e=="{")return E(ue(Ue,"}"),Xe,B(";"));return A(H)}function Ue(e,t){if(t=="as"){T.marked="keyword";return E(B("variable"))}if(e=="variable")return A(G,Ue)}function Ge(e){if(e=="string")return E();if(e=="(")return A(U);return A(qe,Ke,Xe)}function qe(e,t){if(e=="{")return ce(qe,"}");if(e=="variable")N(t);if(t=="*")T.marked="keyword";return E($e)}function Ke(e){if(e==",")return E(qe,Ke)}function $e(e,t){if(t=="as"){T.marked="keyword";return E(qe)}}function Xe(e,t){if(t=="from"){T.marked="keyword";return E(U)}}function Ye(e){if(e=="]")return E();return A(ue(G,"]"))}function Qe(){return A(W("form"),Le,B("{"),W("}"),ue(Ze,"}"),z,z)}function Ze(){return A(Le,Ee)}function Je(e,t){return e.lastType=="operator"||e.lastType==","||c.test(t.charAt(0))||/[,.]/.test(t.charAt(0))}function et(e,t,r){return t.tokenize==v&&/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(t.lastType)||t.lastType=="quasi"&&/\{\s*$/.test(e.string.slice(0,e.pos-(r||0)))}return{startState:function(e){var t={tokenize:v,lastType:"sof",cc:[],lexical:new C((e||0)-i,0,"block",false),localVars:r.localVars,context:r.localVars&&new I(null,null,false),indented:e||0};if(r.globalVars&&typeof r.globalVars=="object")t.globalVars=r.globalVars;return t},token:function(e,t){if(e.sol()){if(!t.lexical.hasOwnProperty("align"))t.lexical.align=false;t.indented=e.indentation();w(e,t)}if(t.tokenize!=y&&e.eatSpace())return null;var r=t.tokenize(e,t);if(p=="comment")return r;t.lastType=p=="operator"&&(d=="++"||d=="--")?"incdec":p;return L(t,r,p,d,e)},indent:function(t,a){if(t.tokenize==y)return e.Pass;if(t.tokenize!=v)return 0;var s=a&&a.charAt(0),o=t.lexical,l;if(!/^\s*else\b/.test(a))for(var u=t.cc.length-1;u>=0;--u){var c=t.cc[u];if(c==z)o=o.prev;else if(c!=Ne)break}while((o.type=="stat"||o.type=="form")&&(s=="}"||(l=t.cc[t.cc.length-1])&&(l==X||l==Y)&&!/^[,\.=+\-*:?[\(]/.test(a)))o=o.prev;if(n&&o.type==")"&&o.prev.type=="stat")o=o.prev;var f=o.type,h=s==f;if(f=="vardef")return o.indented+(t.lastType=="operator"||t.lastType==","?o.info.length+1:0);else if(f=="form"&&s=="{")return o.indented;else if(f=="form")return o.indented+i;else if(f=="stat")return o.indented+(Je(t,a)?n||i:0);else if(o.info=="switch"&&!h&&r.doubleIndentSwitch!=false)return o.indented+(/^(?:case|default)\b/.test(a)?i:2*i);else if(o.align)return o.column+(h?0:1);else return o.indented+(h?0:i)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:s?null:"/*",blockCommentEnd:s?null:"*/",blockCommentContinue:s?null:" * ",lineComment:s?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:s?"json":"javascript",jsonldMode:a,jsonMode:s,expressionAllowed:et,skipExpression:function(e){var t=e.cc[e.cc.length-1];if(t==U||t==G)e.cc.pop()}}});e.registerHelper("wordChars","javascript",/[\w$]/);e.defineMIME("text/javascript","javascript");e.defineMIME("text/ecmascript","javascript");e.defineMIME("application/javascript","javascript");e.defineMIME("application/x-javascript","javascript");e.defineMIME("application/ecmascript","javascript");e.defineMIME("application/json",{name:"javascript",json:true});e.defineMIME("application/x-json",{name:"javascript",json:true});e.defineMIME("application/ld+json",{name:"javascript",jsonld:true});e.defineMIME("text/typescript",{name:"javascript",typescript:true});e.defineMIME("application/typescript",{name:"javascript",typescript:true})});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror"],e);else e(CodeMirror)})(function(e){"use strict";e.defineMode("css",function(t,r){var i=r.inline;if(!r.propertyKeywords)r=e.resolveMode("text/css");var n=t.indentUnit,a=r.tokenHooks,s=r.documentTypes||{},o=r.mediaTypes||{},l=r.mediaFeatures||{},u=r.mediaValueKeywords||{},c=r.propertyKeywords||{},f=r.nonStandardPropertyKeywords||{},h=r.fontProperties||{},p=r.counterDescriptors||{},d=r.colorKeywords||{},m=r.valueKeywords||{},v=r.allowNested,g=r.lineComment,y=r.supportsAtComponent===true;var x,b;function w(e,t){x=t;return e}function k(e,t){var r=e.next();if(a[r]){var i=a[r](e,t);if(i!==false)return i}if(r=="@"){e.eatWhile(/[\w\\\-]/);return w("def",e.current())}else if(r=="="||(r=="~"||r=="|")&&e.eat("=")){return w(null,"compare")}else if(r=='"'||r=="'"){t.tokenize=C(r);return t.tokenize(e,t)}else if(r=="#"){e.eatWhile(/[\w\\\-]/);return w("atom","hash")}else if(r=="!"){e.match(/^\s*\w*/);return w("keyword","important")}else if(/\d/.test(r)||r=="."&&e.eat(/\d/)){e.eatWhile(/[\w.%]/);return w("number","unit")}else if(r==="-"){if(/[\d.]/.test(e.peek())){e.eatWhile(/[\w.%]/);return w("number","unit")}else if(e.match(/^-[\w\\\-]*/)){e.eatWhile(/[\w\\\-]/);if(e.match(/^\s*:/,false))return w("variable-2","variable-definition");return w("variable-2","variable")}else if(e.match(/^\w+-/)){return w("meta","meta")}}else if(/[,+>*\/]/.test(r)){return w(null,"select-op")}else if(r=="."&&e.match(/^-?[_a-z][_a-z0-9-]*/i)){return w("qualifier","qualifier")}else if(/[:;{}\[\]\(\)]/.test(r)){return w(null,r)}else if(e.match(/[\w-.]+(?=\()/)){if(/^(url(-prefix)?|domain|regexp)$/.test(e.current().toLowerCase())){t.tokenize=S}return w("variable callee","variable")}else if(/[\w\\\-]/.test(r)){e.eatWhile(/[\w\\\-]/);return w("property","word")}else{return w(null,null)}}function C(e){return function(t,r){var i=false,n;while((n=t.next())!=null){if(n==e&&!i){if(e==")")t.backUp(1);break}i=!i&&n=="\\"}if(n==e||!i&&e!=")")r.tokenize=null;return w("string","string")}}function S(e,t){e.next();if(!e.match(/\s*[\"\')]/,false))t.tokenize=C(")");else t.tokenize=null;return w(null,"(")}function L(e,t,r){this.type=e;this.indent=t;this.prev=r}function T(e,t,r,i){e.context=new L(r,t.indentation()+(i===false?0:n),e.context);return r}function A(e){if(e.context.prev)e.context=e.context.prev;return e.context.type}function E(e,t,r){return _[r.context.type](e,t,r)}function M(e,t,r,i){for(var n=i||1;n>0;n--)r.context=r.context.prev;return E(e,t,r)}function N(e){var t=e.current().toLowerCase();if(m.hasOwnProperty(t))b="atom";else if(d.hasOwnProperty(t))b="keyword";else b="variable"}var _={};_.top=function(e,t,r){if(e=="{"){return T(r,t,"block")}else if(e=="}"&&r.context.prev){return A(r)}else if(y&&/@component/i.test(e)){return T(r,t,"atComponentBlock")}else if(/^@(-moz-)?document$/i.test(e)){return T(r,t,"documentTypes")}else if(/^@(media|supports|(-moz-)?document|import)$/i.test(e)){return T(r,t,"atBlock")}else if(/^@(font-face|counter-style)/i.test(e)){r.stateArg=e;return"restricted_atBlock_before"}else if(/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(e)){return"keyframes"}else if(e&&e.charAt(0)=="@"){return T(r,t,"at")}else if(e=="hash"){b="builtin"}else if(e=="word"){b="tag"}else if(e=="variable-definition"){return"maybeprop"}else if(e=="interpolation"){return T(r,t,"interpolation")}else if(e==":"){return"pseudo"}else if(v&&e=="("){return T(r,t,"parens")}return r.context.type};_.block=function(e,t,r){if(e=="word"){var i=t.current().toLowerCase();if(c.hasOwnProperty(i)){b="property";return"maybeprop"}else if(f.hasOwnProperty(i)){b="string-2";return"maybeprop"}else if(v){b=t.match(/^\s*:(?:\s|$)/,false)?"property":"tag";return"block"}else{b+=" error";return"maybeprop"}}else if(e=="meta"){return"block"}else if(!v&&(e=="hash"||e=="qualifier")){b="error";return"block"}else{return _.top(e,t,r)}};_.maybeprop=function(e,t,r){if(e==":")return T(r,t,"prop");return E(e,t,r)};_.prop=function(e,t,r){if(e==";")return A(r);if(e=="{"&&v)return T(r,t,"propBlock");if(e=="}"||e=="{")return M(e,t,r);if(e=="(")return T(r,t,"parens");if(e=="hash"&&!/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(t.current())){b+=" error"}else if(e=="word"){N(t)}else if(e=="interpolation"){return T(r,t,"interpolation")}return"prop"};_.propBlock=function(e,t,r){if(e=="}")return A(r);if(e=="word"){b="property";return"maybeprop"}return r.context.type};_.parens=function(e,t,r){if(e=="{"||e=="}")return M(e,t,r);if(e==")")return A(r);if(e=="(")return T(r,t,"parens");if(e=="interpolation")return T(r,t,"interpolation");if(e=="word")N(t);return"parens"};_.pseudo=function(e,t,r){if(e=="meta")return"pseudo";if(e=="word"){b="variable-3";return r.context.type}return E(e,t,r)};_.documentTypes=function(e,t,r){if(e=="word"&&s.hasOwnProperty(t.current())){b="tag";return r.context.type}else{return _.atBlock(e,t,r)}};_.atBlock=function(e,t,r){if(e=="(")return T(r,t,"atBlock_parens");if(e=="}"||e==";")return M(e,t,r);if(e=="{")return A(r)&&T(r,t,v?"block":"top");if(e=="interpolation")return T(r,t,"interpolation");if(e=="word"){var i=t.current().toLowerCase();if(i=="only"||i=="not"||i=="and"||i=="or")b="keyword";else if(o.hasOwnProperty(i))b="attribute";else if(l.hasOwnProperty(i))b="property";else if(u.hasOwnProperty(i))b="keyword";else if(c.hasOwnProperty(i))b="property";else if(f.hasOwnProperty(i))b="string-2";else if(m.hasOwnProperty(i))b="atom";else if(d.hasOwnProperty(i))b="keyword";else b="error"}return r.context.type};_.atComponentBlock=function(e,t,r){if(e=="}")return M(e,t,r);if(e=="{")return A(r)&&T(r,t,v?"block":"top",false);if(e=="word")b="error";return r.context.type};_.atBlock_parens=function(e,t,r){if(e==")")return A(r);if(e=="{"||e=="}")return M(e,t,r,2);return _.atBlock(e,t,r)};_.restricted_atBlock_before=function(e,t,r){if(e=="{")return T(r,t,"restricted_atBlock");if(e=="word"&&r.stateArg=="@counter-style"){b="variable";return"restricted_atBlock_before"}return E(e,t,r)};_.restricted_atBlock=function(e,t,r){if(e=="}"){r.stateArg=null;return A(r)}if(e=="word"){if(r.stateArg=="@font-face"&&!h.hasOwnProperty(t.current().toLowerCase())||r.stateArg=="@counter-style"&&!p.hasOwnProperty(t.current().toLowerCase()))b="error";else b="property";return"maybeprop"}return"restricted_atBlock"};_.keyframes=function(e,t,r){if(e=="word"){b="variable";return"keyframes"}if(e=="{")return T(r,t,"top");return E(e,t,r)};_.at=function(e,t,r){if(e==";")return A(r);if(e=="{"||e=="}")return M(e,t,r);if(e=="word")b="tag";else if(e=="hash")b="builtin";return"at"};_.interpolation=function(e,t,r){if(e=="}")return A(r);if(e=="{"||e==";")return M(e,t,r);if(e=="word")b="variable";else if(e!="variable"&&e!="("&&e!=")")b="error";return"interpolation"};return{startState:function(e){return{tokenize:null,state:i?"block":"top",stateArg:null,context:new L(i?"block":"top",e||0,null)}},token:function(e,t){if(!t.tokenize&&e.eatSpace())return null;var r=(t.tokenize||k)(e,t);if(r&&typeof r=="object"){x=r[1];r=r[0]}b=r;if(x!="comment")t.state=_[t.state](x,e,t);return b},indent:function(e,t){var r=e.context,i=t&&t.charAt(0);var a=r.indent;if(r.type=="prop"&&(i=="}"||i==")"))r=r.prev;if(r.prev){if(i=="}"&&(r.type=="block"||r.type=="top"||r.type=="interpolation"||r.type=="restricted_atBlock")){r=r.prev;a=r.indent}else if(i==")"&&(r.type=="parens"||r.type=="atBlock_parens")||i=="{"&&(r.type=="at"||r.type=="atBlock")){a=Math.max(0,r.indent-n)}}return a},electricChars:"}",blockCommentStart:"/*",blockCommentEnd:"*/",blockCommentContinue:" * ",lineComment:g,fold:"brace"}});function t(e){var t={};for(var r=0;r"));else return null}else if(e.match("--")){return r(d("comment","--\x3e"))}else if(e.match("DOCTYPE",true,true)){e.eatWhile(/[\w\._\-]/);return r(m(1))}else{return null}}else if(e.eat("?")){e.eatWhile(/[\w\._\-]/);t.tokenize=d("meta","?>");return"meta"}else{u=e.eat("/")?"closeTag":"openTag";t.tokenize=h;return"tag bracket"}}else if(i=="&"){var n;if(e.eat("#")){if(e.eat("x")){n=e.eatWhile(/[a-fA-F\d]/)&&e.eat(";")}else{n=e.eatWhile(/[\d]/)&&e.eat(";")}}else{n=e.eatWhile(/[\w\.\-:]/)&&e.eat(";")}return n?"atom":"error"}else{e.eatWhile(/[^&<]/);return null}}f.isInText=true;function h(e,t){var r=e.next();if(r==">"||r=="/"&&e.eat(">")){t.tokenize=f;u=r==">"?"endTag":"selfcloseTag";return"tag bracket"}else if(r=="="){u="equals";return null}else if(r=="<"){t.tokenize=f;t.state=x;t.tagName=t.tagStart=null;var i=t.tokenize(e,t);return i?i+" tag error":"tag error"}else if(/[\'\"]/.test(r)){t.tokenize=p(r);t.stringStartCol=e.column();return t.tokenize(e,t)}else{e.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);return"word"}}function p(e){var t=function(t,r){while(!t.eol()){if(t.next()==e){r.tokenize=h;break}}return"string"};t.isInAttribute=true;return t}function d(e,t){return function(r,i){while(!r.eol()){if(r.match(t)){i.tokenize=f;break}r.next()}return e}}function m(e){return function(t,r){var i;while((i=t.next())!=null){if(i=="<"){r.tokenize=m(e+1);return r.tokenize(t,r)}else if(i==">"){if(e==1){r.tokenize=f;break}else{r.tokenize=m(e-1);return r.tokenize(t,r)}}}return"meta"}}function v(e,t,r){this.prev=e.context;this.tagName=t;this.indent=e.indented;this.startOfLine=r;if(s.doNotIndent.hasOwnProperty(t)||e.context&&e.context.noIndent)this.noIndent=true}function g(e){if(e.context)e.context=e.context.prev}function y(e,t){var r;while(true){if(!e.context){return}r=e.context.tagName;if(!s.contextGrabbers.hasOwnProperty(r)||!s.contextGrabbers[r].hasOwnProperty(t)){return}g(e)}}function x(e,t,r){if(e=="openTag"){r.tagStart=t.column();return b}else if(e=="closeTag"){return w}else{return x}}function b(e,t,r){if(e=="word"){r.tagName=t.current();c="tag";return S}else if(s.allowMissingTagName&&e=="endTag"){c="tag bracket";return S(e,t,r)}else{c="error";return b}}function w(e,t,r){if(e=="word"){var i=t.current();if(r.context&&r.context.tagName!=i&&s.implicitlyClosed.hasOwnProperty(r.context.tagName))g(r);if(r.context&&r.context.tagName==i||s.matchClosing===false){c="tag";return k}else{c="tag error";return C}}else if(s.allowMissingTagName&&e=="endTag"){c="tag bracket";return k(e,t,r)}else{c="error";return C}}function k(e,t,r){if(e!="endTag"){c="error";return k}g(r);return x}function C(e,t,r){c="error";return k(e,t,r)}function S(e,t,r){if(e=="word"){c="attribute";return L}else if(e=="endTag"||e=="selfcloseTag"){var i=r.tagName,n=r.tagStart;r.tagName=r.tagStart=null;if(e=="selfcloseTag"||s.autoSelfClosers.hasOwnProperty(i)){y(r,i)}else{y(r,i);r.context=new v(r,i,n==r.indented)}return x}c="error";return S}function L(e,t,r){if(e=="equals")return T;if(!s.allowMissing)c="error";return S(e,t,r)}function T(e,t,r){if(e=="string")return A;if(e=="word"&&s.allowUnquoted){c="string";return S}c="error";return S(e,t,r)}function A(e,t,r){if(e=="string")return A;return S(e,t,r)}return{startState:function(e){var t={tokenize:f,state:x,indented:e||0,tagName:null,tagStart:null,context:null};if(e!=null)t.baseIndent=e;return t},token:function(e,t){if(!t.tagName&&e.sol())t.indented=e.indentation();if(e.eatSpace())return null;u=null;var r=t.tokenize(e,t);if((r||u)&&r!="comment"){c=null;t.state=t.state(u||r,e,t);if(c)r=c=="error"?r+" error":c}return r},indent:function(t,r,i){var n=t.context;if(t.tokenize.isInAttribute){if(t.tagStart==t.indented)return t.stringStartCol+1;else return t.indented+a}if(n&&n.noIndent)return e.Pass;if(t.tokenize!=h&&t.tokenize!=f)return i?i.match(/^(\s*)/)[0].length:0;if(t.tagName){if(s.multilineTagIndentPastTag!==false)return t.tagStart+t.tagName.length+2;else return t.tagStart+a*(s.multilineTagIndentFactor||1)}if(s.alignCDATA&&/$/,blockCommentStart:"\x3c!--",blockCommentEnd:"--\x3e",configuration:s.htmlMode?"html":"xml",helperType:s.htmlMode?"html":"xml",skipAttribute:function(e){if(e.state==T)e.state=S},xmlCurrentTag:function(e){return e.tagName?{name:e.tagName,close:e.type=="closeTag"}:null},xmlCurrentContext:function(e){var t=[];for(var r=e.context;r;r=r.prev)if(r.tagName)t.push(r.tagName);return t.reverse()}}});e.defineMIME("text/xml","xml");e.defineMIME("application/xml","xml");if(!e.mimeModes.hasOwnProperty("text/html"))e.defineMIME("text/html",{name:"xml",htmlMode:true})});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"),require("../xml/xml"),require("../javascript/javascript"),require("../css/css"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror","../xml/xml","../javascript/javascript","../css/css"],e);else e(CodeMirror)})(function(e){"use strict";var t={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};function r(e,t,r){var i=e.current(),n=i.search(t);if(n>-1){e.backUp(i.length-n)}else if(i.match(/<\/?$/)){e.backUp(i.length);if(!e.match(t,false))e.match(i)}return r}var i={};function n(e){var t=i[e];if(t)return t;return i[e]=new RegExp("\\s+"+e+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")}function a(e,t){var r=e.match(n(t));return r?/^\s*(.*?)\s*$/.exec(r[2])[1]:""}function s(e,t){return new RegExp((t?"^":"")+"","i")}function o(e,t){for(var r in e){var i=t[r]||(t[r]=[]);var n=e[r];for(var a=n.length-1;a>=0;a--)i.unshift(n[a])}}function l(e,t){for(var r=0;r=0;h--)u.script.unshift(["type",f[h].matches,f[h].mode]);function p(t,n){var o=a.token(t,n.htmlState),c=/\btag\b/.test(o),f;if(c&&!/[<>\s\/]/.test(t.current())&&(f=n.htmlState.tagName&&n.htmlState.tagName.toLowerCase())&&u.hasOwnProperty(f)){n.inTag=f+" "}else if(n.inTag&&c&&/>$/.test(t.current())){var h=/^([\S]+) (.*)/.exec(n.inTag);n.inTag=null;var d=t.current()==">"&&l(u[h[1]],h[2]);var m=e.getMode(i,d);var v=s(h[1],true),g=s(h[1],false);n.token=function(e,t){if(e.match(v,false)){t.token=p;t.localState=t.localMode=null;return null}return r(e,g,t.localMode.token(e,t.localState))};n.localMode=m;n.localState=e.startState(m,a.indent(n.htmlState,"",""))}else if(n.inTag){n.inTag+=t.current();if(t.eol())n.inTag+=" "}return o}return{startState:function(){var t=e.startState(a);return{token:p,inTag:null,localMode:null,localState:null,htmlState:t}},copyState:function(t){var r;if(t.localState){r=e.copyState(t.localMode,t.localState)}return{token:t.token,inTag:t.inTag,localMode:t.localMode,localState:r,htmlState:e.copyState(a,t.htmlState)}},token:function(e,t){return t.token(e,t)},indent:function(t,r,i){if(!t.localMode||/^\s*<\//.test(r))return a.indent(t.htmlState,r,i);else if(t.localMode.indent)return t.localMode.indent(t.localState,r,i);else return e.Pass},innerMode:function(e){return{state:e.localState||e.htmlState,mode:e.localMode||a}}}},"xml","javascript","css");e.defineMIME("text/html","htmlmixed")});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror"],e);else e(CodeMirror)})(function(e){var t=/MSIE \d/.test(navigator.userAgent)&&(document.documentMode==null||document.documentMode<8);var r=e.Pos;var i={"(":")>",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<","<":">>",">":"<<"};function n(e){return e&&e.bracketRegex||/[(){}[\]]/}function a(e,t,a){var o=e.getLineHandle(t.line),l=t.ch-1;var u=a&&a.afterCursor;if(u==null)u=/(^| )cm-fat-cursor($| )/.test(e.getWrapperElement().className);var c=n(a);var f=!u&&l>=0&&c.test(o.text.charAt(l))&&i[o.text.charAt(l)]||c.test(o.text.charAt(l+1))&&i[o.text.charAt(++l)];if(!f)return null;var h=f.charAt(1)==">"?1:-1;if(a&&a.strict&&h>0!=(l==t.ch))return null;var p=e.getTokenTypeAt(r(t.line,l+1));var d=s(e,r(t.line,l+(h>0?1:0)),h,p||null,a);if(d==null)return null;return{from:r(t.line,l),to:d&&d.pos,match:d&&d.ch==f.charAt(0),forward:h>0}}function s(e,t,a,s,o){var l=o&&o.maxScanLineLength||1e4;var u=o&&o.maxScanLines||1e3;var c=[];var f=n(o);var h=a>0?Math.min(t.line+u,e.lastLine()+1):Math.max(e.firstLine()-1,t.line-u);for(var p=t.line;p!=h;p+=a){var d=e.getLine(p);if(!d)continue;var m=a>0?0:d.length-1,v=a>0?d.length:-1;if(d.length>l)continue;if(p==t.line)m=t.ch-(a<0?1:0);for(;m!=v;m+=a){var g=d.charAt(m);if(f.test(g)&&(s===undefined||e.getTokenTypeAt(r(p,m+1))==s)){var y=i[g];if(y&&y.charAt(1)==">"==a>0)c.push(g);else if(!c.length)return{pos:r(p,m),ch:g};else c.pop()}}}return p-a==(a>0?e.lastLine():e.firstLine())?false:null}function o(e,i,n){var s=e.state.matchBrackets.maxHighlightLineLength||1e3;var o=[],l=e.listSelections();for(var u=0;ue){return false}r+=t[i+1];if(r>=e){return true}}}function h(e,t){if(e<65){return e===36}if(e<91){return true}if(e<97){return e===95}if(e<123){return true}if(e<=65535){return e>=170&&o.test(String.fromCharCode(e))}if(t===false){return false}return f(e,u)}function p(e,t){if(e<48){return e===36}if(e<58){return true}if(e<65){return false}if(e<91){return true}if(e<97){return e===95}if(e<123){return true}if(e<=65535){return e>=170&&l.test(String.fromCharCode(e))}if(t===false){return false}return f(e,u)||f(e,c)}var d=function e(t,r){if(r===void 0)r={};this.label=t;this.keyword=r.keyword;this.beforeExpr=!!r.beforeExpr;this.startsExpr=!!r.startsExpr;this.isLoop=!!r.isLoop;this.isAssign=!!r.isAssign;this.prefix=!!r.prefix;this.postfix=!!r.postfix;this.binop=r.binop||null;this.updateContext=null};function m(e,t){return new d(e,{beforeExpr:true,binop:t})}var v={beforeExpr:true};var g={startsExpr:true};var y={};function x(e,t){if(t===void 0)t={};t.keyword=e;return y[e]=new d(e,t)}var b={num:new d("num",g),regexp:new d("regexp",g),string:new d("string",g),name:new d("name",g),eof:new d("eof"),bracketL:new d("[",{beforeExpr:true,startsExpr:true}),bracketR:new d("]"),braceL:new d("{",{beforeExpr:true,startsExpr:true}),braceR:new d("}"),parenL:new d("(",{beforeExpr:true,startsExpr:true}),parenR:new d(")"),comma:new d(",",v),semi:new d(";",v),colon:new d(":",v),dot:new d("."),question:new d("?",v),arrow:new d("=>",v),template:new d("template"),invalidTemplate:new d("invalidTemplate"),ellipsis:new d("...",v),backQuote:new d("`",g),dollarBraceL:new d("${",{beforeExpr:true,startsExpr:true}),eq:new d("=",{beforeExpr:true,isAssign:true}),assign:new d("_=",{beforeExpr:true,isAssign:true}),incDec:new d("++/--",{prefix:true,postfix:true,startsExpr:true}),prefix:new d("!/~",{beforeExpr:true,prefix:true,startsExpr:true}),logicalOR:m("||",1),logicalAND:m("&&",2),bitwiseOR:m("|",3),bitwiseXOR:m("^",4),bitwiseAND:m("&",5),equality:m("==/!=/===/!==",6),relational:m("/<=/>=",7),bitShift:m("<>/>>>",8),plusMin:new d("+/-",{beforeExpr:true,binop:9,prefix:true,startsExpr:true}),modulo:m("%",10),star:m("*",10),slash:m("/",10),starstar:new d("**",{beforeExpr:true}),_break:x("break"),_case:x("case",v),_catch:x("catch"),_continue:x("continue"),_debugger:x("debugger"),_default:x("default",v),_do:x("do",{isLoop:true,beforeExpr:true}),_else:x("else",v),_finally:x("finally"),_for:x("for",{isLoop:true}),_function:x("function",g),_if:x("if"),_return:x("return",v),_switch:x("switch"),_throw:x("throw",v),_try:x("try"),_var:x("var"),_const:x("const"),_while:x("while",{isLoop:true}),_with:x("with"),_new:x("new",{beforeExpr:true,startsExpr:true}),_this:x("this",g),_super:x("super",g),_class:x("class",g),_extends:x("extends",v),_export:x("export"),_import:x("import"),_null:x("null",g),_true:x("true",g),_false:x("false",g),_in:x("in",{beforeExpr:true,binop:7}),_instanceof:x("instanceof",{beforeExpr:true,binop:7}),_typeof:x("typeof",{beforeExpr:true,prefix:true,startsExpr:true}),_void:x("void",{beforeExpr:true,prefix:true,startsExpr:true}),_delete:x("delete",{beforeExpr:true,prefix:true,startsExpr:true})};var w=/\r\n?|\n|\u2028|\u2029/;var k=new RegExp(w.source,"g");function C(e,t){return e===10||e===13||!t&&(e===8232||e===8233)}var S=/[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;var L=/(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g;var T=Object.prototype;var A=T.hasOwnProperty;var E=T.toString;function M(e,t){return A.call(e,t)}var N=Array.isArray||function(e){return E.call(e)==="[object Array]"};var _=function e(t,r){this.line=t;this.column=r};_.prototype.offset=function e(t){return new _(this.line,this.column+t)};var P=function e(t,r,i){this.start=r;this.end=i;if(t.sourceFile!==null){this.source=t.sourceFile}};function I(e,t){for(var r=1,i=0;;){k.lastIndex=i;var n=k.exec(e);if(n&&n.index=2015){t.ecmaVersion-=2009}if(t.allowReserved==null){t.allowReserved=t.ecmaVersion<5}if(N(t.onToken)){var i=t.onToken;t.onToken=function(e){return i.push(e)}}if(N(t.onComment)){t.onComment=R(t,t.onComment)}return t}function R(e,t){return function(r,i,n,a,s,o){var l={type:r?"Block":"Line",value:i,start:n,end:a};if(e.locations){l.loc=new P(this,s,o)}if(e.ranges){l.range=[n,a]}t.push(l)}}var V={};function F(e){return new RegExp("^(?:"+e.replace(/ /g,"|")+")$")}var W=function e(r,n,a){this.options=r=D(r);this.sourceFile=r.sourceFile;this.keywords=F(i[r.ecmaVersion>=6?6:5]);var s="";if(!r.allowReserved){for(var o=r.ecmaVersion;;o--){if(s=t[o]){break}}if(r.sourceType==="module"){s+=" await"}}this.reservedWords=F(s);var l=(s?s+" ":"")+t.strict;this.reservedWordsStrict=F(l);this.reservedWordsStrictBind=F(l+" "+t.strictBind);this.input=String(n);this.containsEsc=false;this.loadPlugins(r.plugins);if(a){this.pos=a;this.lineStart=this.input.lastIndexOf("\n",a-1)+1;this.curLine=this.input.slice(0,this.lineStart).split(w).length}else{this.pos=this.lineStart=0;this.curLine=1}this.type=b.eof;this.value=null;this.start=this.end=this.pos;this.startLoc=this.endLoc=this.curPosition();this.lastTokEndLoc=this.lastTokStartLoc=null;this.lastTokStart=this.lastTokEnd=this.pos;this.context=this.initialContext();this.exprAllowed=true;this.inModule=r.sourceType==="module";this.strict=this.inModule||this.strictDirective(this.pos);this.potentialArrowAt=-1;this.inFunction=this.inGenerator=this.inAsync=false;this.yieldPos=this.awaitPos=0;this.labels=[];if(this.pos===0&&r.allowHashBang&&this.input.slice(0,2)==="#!"){this.skipLineComment(2)}this.scopeStack=[];this.enterFunctionScope();this.regexpState=null};W.prototype.isKeyword=function e(t){return this.keywords.test(t)};W.prototype.isReservedWord=function e(t){return this.reservedWords.test(t)};W.prototype.extend=function e(t,r){this[t]=r(this[t])};W.prototype.loadPlugins=function e(t){var r=this;for(var i in t){var n=V[i];if(!n){throw new Error("Plugin '"+i+"' not found")}n(r,t[i])}};W.prototype.parse=function e(){var t=this.options.program||this.startNode();this.nextToken();return this.parseTopLevel(t)};var z=W.prototype;var B=/^(?:'((?:\\.|[^'])*?)'|"((?:\\.|[^"])*?)"|;)/;z.strictDirective=function(e){var t=this;for(;;){L.lastIndex=e;e+=L.exec(t.input)[0].length;var r=B.exec(t.input.slice(e));if(!r){return false}if((r[1]||r[2])==="use strict"){return true}e+=r[0].length}};z.eat=function(e){if(this.type===e){this.next();return true}else{return false}};z.isContextual=function(e){return this.type===b.name&&this.value===e&&!this.containsEsc};z.eatContextual=function(e){if(!this.isContextual(e)){return false}this.next();return true};z.expectContextual=function(e){if(!this.eatContextual(e)){this.unexpected()}};z.canInsertSemicolon=function(){return this.type===b.eof||this.type===b.braceR||w.test(this.input.slice(this.lastTokEnd,this.start))};z.insertSemicolon=function(){if(this.canInsertSemicolon()){if(this.options.onInsertedSemicolon){this.options.onInsertedSemicolon(this.lastTokEnd,this.lastTokEndLoc)}return true}};z.semicolon=function(){if(!this.eat(b.semi)&&!this.insertSemicolon()){this.unexpected()}};z.afterTrailingComma=function(e,t){if(this.type===e){if(this.options.onTrailingComma){this.options.onTrailingComma(this.lastTokStart,this.lastTokStartLoc)}if(!t){this.next()}return true}};z.expect=function(e){this.eat(e)||this.unexpected()};z.unexpected=function(e){this.raise(e!=null?e:this.start,"Unexpected token")};function H(){this.shorthandAssign=this.trailingComma=this.parenthesizedAssign=this.parenthesizedBind=this.doubleProto=-1}z.checkPatternErrors=function(e,t){if(!e){return}if(e.trailingComma>-1){this.raiseRecoverable(e.trailingComma,"Comma is not permitted after the rest element")}var r=t?e.parenthesizedAssign:e.parenthesizedBind;if(r>-1){this.raiseRecoverable(r,"Parenthesized pattern")}};z.checkExpressionErrors=function(e,t){if(!e){return false}var r=e.shorthandAssign;var i=e.doubleProto;if(!t){return r>=0||i>=0}if(r>=0){this.raise(r,"Shorthand property assignments are valid only in destructuring patterns")}if(i>=0){this.raiseRecoverable(i,"Redefinition of __proto__ property")}};z.checkYieldAwaitInDefaultParams=function(){if(this.yieldPos&&(!this.awaitPos||this.yieldPos=6){e.sourceType=this.options.sourceType}return this.finishNode(e,"Program")};var U={kind:"loop"};var G={kind:"switch"};j.isLet=function(){if(this.options.ecmaVersion<6||!this.isContextual("let")){return false}L.lastIndex=this.pos;var e=L.exec(this.input);var t=this.pos+e[0].length,r=this.input.charCodeAt(t);if(r===91||r===123){return true}if(h(r,true)){var i=t+1;while(p(this.input.charCodeAt(i),true)){++i}var a=this.input.slice(t,i);if(!n.test(a)){return true}}return false};j.isAsyncFunction=function(){if(this.options.ecmaVersion<8||!this.isContextual("async")){return false}L.lastIndex=this.pos;var e=L.exec(this.input);var t=this.pos+e[0].length;return!w.test(this.input.slice(this.pos,t))&&this.input.slice(t,t+8)==="function"&&(t+8===this.input.length||!p(this.input.charAt(t+8)))};j.parseStatement=function(e,t,r){var i=this.type,n=this.startNode(),a;if(this.isLet()){i=b._var;a="let"}switch(i){case b._break:case b._continue:return this.parseBreakContinueStatement(n,i.keyword);case b._debugger:return this.parseDebuggerStatement(n);case b._do:return this.parseDoStatement(n);case b._for:return this.parseForStatement(n);case b._function:if(!e&&this.options.ecmaVersion>=6){this.unexpected()}return this.parseFunctionStatement(n,false);case b._class:if(!e){this.unexpected()}return this.parseClass(n,true);case b._if:return this.parseIfStatement(n);case b._return:return this.parseReturnStatement(n);case b._switch:return this.parseSwitchStatement(n);case b._throw:return this.parseThrowStatement(n);case b._try:return this.parseTryStatement(n);case b._const:case b._var:a=a||this.value;if(!e&&a!=="var"){this.unexpected()}return this.parseVarStatement(n,a);case b._while:return this.parseWhileStatement(n);case b._with:return this.parseWithStatement(n);case b.braceL:return this.parseBlock();case b.semi:return this.parseEmptyStatement(n);case b._export:case b._import:if(!this.options.allowImportExportEverywhere){if(!t){this.raise(this.start,"'import' and 'export' may only appear at the top level")}if(!this.inModule){this.raise(this.start,"'import' and 'export' may appear only with 'sourceType: module'")}}return i===b._import?this.parseImport(n):this.parseExport(n,r);default:if(this.isAsyncFunction()){if(!e){this.unexpected()}this.next();return this.parseFunctionStatement(n,true)}var s=this.value,o=this.parseExpression();if(i===b.name&&o.type==="Identifier"&&this.eat(b.colon)){return this.parseLabeledStatement(n,s,o)}else{return this.parseExpressionStatement(n,o)}}};j.parseBreakContinueStatement=function(e,t){var r=this;var i=t==="break";this.next();if(this.eat(b.semi)||this.insertSemicolon()){e.label=null}else if(this.type!==b.name){this.unexpected()}else{e.label=this.parseIdent();this.semicolon()}var n=0;for(;n=6){this.eat(b.semi)}else{this.semicolon()}return this.finishNode(e,"DoWhileStatement")};j.parseForStatement=function(e){this.next();var t=this.options.ecmaVersion>=9&&(this.inAsync||!this.inFunction&&this.options.allowAwaitOutsideFunction)&&this.eatContextual("await")?this.lastTokStart:-1;this.labels.push(U);this.enterLexicalScope();this.expect(b.parenL);if(this.type===b.semi){if(t>-1){this.unexpected(t)}return this.parseFor(e,null)}var r=this.isLet();if(this.type===b._var||this.type===b._const||r){var i=this.startNode(),n=r?"let":this.value;this.next();this.parseVar(i,true,n);this.finishNode(i,"VariableDeclaration");if((this.type===b._in||this.options.ecmaVersion>=6&&this.isContextual("of"))&&i.declarations.length===1&&!(n!=="var"&&i.declarations[0].init)){if(this.options.ecmaVersion>=9){if(this.type===b._in){if(t>-1){this.unexpected(t)}}else{e.await=t>-1}}return this.parseForIn(e,i)}if(t>-1){this.unexpected(t)}return this.parseFor(e,i)}var a=new H;var s=this.parseExpression(true,a);if(this.type===b._in||this.options.ecmaVersion>=6&&this.isContextual("of")){if(this.options.ecmaVersion>=9){if(this.type===b._in){if(t>-1){this.unexpected(t)}}else{e.await=t>-1}}this.toAssignable(s,false,a);this.checkLVal(s);return this.parseForIn(e,s)}else{this.checkExpressionErrors(a,true)}if(t>-1){this.unexpected(t)}return this.parseFor(e,s)};j.parseFunctionStatement=function(e,t){this.next();return this.parseFunction(e,true,false,t)};j.parseIfStatement=function(e){this.next();e.test=this.parseParenExpression();e.consequent=this.parseStatement(!this.strict&&this.type===b._function);e.alternate=this.eat(b._else)?this.parseStatement(!this.strict&&this.type===b._function):null;return this.finishNode(e,"IfStatement")};j.parseReturnStatement=function(e){if(!this.inFunction&&!this.options.allowReturnOutsideFunction){this.raise(this.start,"'return' outside of function")}this.next();if(this.eat(b.semi)||this.insertSemicolon()){e.argument=null}else{e.argument=this.parseExpression();this.semicolon()}return this.finishNode(e,"ReturnStatement")};j.parseSwitchStatement=function(e){var t=this;this.next();e.discriminant=this.parseParenExpression();e.cases=[];this.expect(b.braceL);this.labels.push(G);this.enterLexicalScope();var r;for(var i=false;this.type!==b.braceR;){if(t.type===b._case||t.type===b._default){var n=t.type===b._case;if(r){t.finishNode(r,"SwitchCase")}e.cases.push(r=t.startNode());r.consequent=[];t.next();if(n){r.test=t.parseExpression()}else{if(i){t.raiseRecoverable(t.lastTokStart,"Multiple default clauses")}i=true;r.test=null}t.expect(b.colon)}else{if(!r){t.unexpected()}r.consequent.push(t.parseStatement(true))}}this.exitLexicalScope();if(r){this.finishNode(r,"SwitchCase")}this.next();this.labels.pop();return this.finishNode(e,"SwitchStatement")};j.parseThrowStatement=function(e){this.next();if(w.test(this.input.slice(this.lastTokEnd,this.start))){this.raise(this.lastTokEnd,"Illegal newline after throw")}e.argument=this.parseExpression();this.semicolon();return this.finishNode(e,"ThrowStatement")};var q=[];j.parseTryStatement=function(e){this.next();e.block=this.parseBlock();e.handler=null;if(this.type===b._catch){var t=this.startNode();this.next();if(this.eat(b.parenL)){t.param=this.parseBindingAtom();this.enterLexicalScope();this.checkLVal(t.param,"let");this.expect(b.parenR)}else{if(this.options.ecmaVersion<10){this.unexpected()}t.param=null;this.enterLexicalScope()}t.body=this.parseBlock(false);this.exitLexicalScope();e.handler=this.finishNode(t,"CatchClause")} -e.finalizer=this.eat(b._finally)?this.parseBlock():null;if(!e.handler&&!e.finalizer){this.raise(e.start,"Missing catch or finally clause")}return this.finishNode(e,"TryStatement")};j.parseVarStatement=function(e,t){this.next();this.parseVar(e,false,t);this.semicolon();return this.finishNode(e,"VariableDeclaration")};j.parseWhileStatement=function(e){this.next();e.test=this.parseParenExpression();this.labels.push(U);e.body=this.parseStatement(false);this.labels.pop();return this.finishNode(e,"WhileStatement")};j.parseWithStatement=function(e){if(this.strict){this.raise(this.start,"'with' in strict mode")}this.next();e.object=this.parseParenExpression();e.body=this.parseStatement(false);return this.finishNode(e,"WithStatement")};j.parseEmptyStatement=function(e){this.next();return this.finishNode(e,"EmptyStatement")};j.parseLabeledStatement=function(e,t,r){var i=this;for(var n=0,a=i.labels;n=0;l--){var u=i.labels[l];if(u.statementStart===e.start){u.statementStart=i.start;u.kind=o}else{break}}this.labels.push({name:t,kind:o,statementStart:this.start});e.body=this.parseStatement(true);if(e.body.type==="ClassDeclaration"||e.body.type==="VariableDeclaration"&&e.body.kind!=="var"||e.body.type==="FunctionDeclaration"&&(this.strict||e.body.generator||e.body.async)){this.raiseRecoverable(e.body.start,"Invalid labeled declaration")}this.labels.pop();e.label=r;return this.finishNode(e,"LabeledStatement")};j.parseExpressionStatement=function(e,t){e.expression=t;this.semicolon();return this.finishNode(e,"ExpressionStatement")};j.parseBlock=function(e){var t=this;if(e===void 0)e=true;var r=this.startNode();r.body=[];this.expect(b.braceL);if(e){this.enterLexicalScope()}while(!this.eat(b.braceR)){var i=t.parseStatement(true);r.body.push(i)}if(e){this.exitLexicalScope()}return this.finishNode(r,"BlockStatement")};j.parseFor=function(e,t){e.init=t;this.expect(b.semi);e.test=this.type===b.semi?null:this.parseExpression();this.expect(b.semi);e.update=this.type===b.parenR?null:this.parseExpression();this.expect(b.parenR);this.exitLexicalScope();e.body=this.parseStatement(false);this.labels.pop();return this.finishNode(e,"ForStatement")};j.parseForIn=function(e,t){var r=this.type===b._in?"ForInStatement":"ForOfStatement";this.next();if(r==="ForInStatement"){if(t.type==="AssignmentPattern"||t.type==="VariableDeclaration"&&t.declarations[0].init!=null&&(this.strict||t.declarations[0].id.type!=="Identifier")){this.raise(t.start,"Invalid assignment in for-in loop head")}}e.left=t;e.right=r==="ForInStatement"?this.parseExpression():this.parseMaybeAssign();this.expect(b.parenR);this.exitLexicalScope();e.body=this.parseStatement(false);this.labels.pop();return this.finishNode(e,r)};j.parseVar=function(e,t,r){var i=this;e.declarations=[];e.kind=r;for(;;){var n=i.startNode();i.parseVarId(n,r);if(i.eat(b.eq)){n.init=i.parseMaybeAssign(t)}else if(r==="const"&&!(i.type===b._in||i.options.ecmaVersion>=6&&i.isContextual("of"))){i.unexpected()}else if(n.id.type!=="Identifier"&&!(t&&(i.type===b._in||i.isContextual("of")))){i.raise(i.lastTokEnd,"Complex binding patterns require an initialization value")}else{n.init=null}e.declarations.push(i.finishNode(n,"VariableDeclarator"));if(!i.eat(b.comma)){break}}return e};j.parseVarId=function(e,t){e.id=this.parseBindingAtom(t);this.checkLVal(e.id,t,false)};j.parseFunction=function(e,t,r,i){this.initFunction(e);if(this.options.ecmaVersion>=9||this.options.ecmaVersion>=6&&!i){e.generator=this.eat(b.star)}if(this.options.ecmaVersion>=8){e.async=!!i}if(t){e.id=t==="nullableID"&&this.type!==b.name?null:this.parseIdent();if(e.id){this.checkLVal(e.id,this.inModule&&!this.inFunction?"let":"var")}}var n=this.inGenerator,a=this.inAsync,s=this.yieldPos,o=this.awaitPos,l=this.inFunction;this.inGenerator=e.generator;this.inAsync=e.async;this.yieldPos=0;this.awaitPos=0;this.inFunction=true;this.enterFunctionScope();if(!t){e.id=this.type===b.name?this.parseIdent():null}this.parseFunctionParams(e);this.parseFunctionBody(e,r);this.inGenerator=n;this.inAsync=a;this.yieldPos=s;this.awaitPos=o;this.inFunction=l;return this.finishNode(e,t?"FunctionDeclaration":"FunctionExpression")};j.parseFunctionParams=function(e){this.expect(b.parenL);e.params=this.parseBindingList(b.parenR,false,this.options.ecmaVersion>=8);this.checkYieldAwaitInDefaultParams()};j.parseClass=function(e,t){var r=this;this.next();this.parseClassId(e,t);this.parseClassSuper(e);var i=this.startNode();var n=false;i.body=[];this.expect(b.braceL);while(!this.eat(b.braceR)){var a=r.parseClassMember(i);if(a&&a.type==="MethodDefinition"&&a.kind==="constructor"){if(n){r.raise(a.start,"Duplicate constructor in the same class")}n=true}}e.body=this.finishNode(i,"ClassBody");return this.finishNode(e,t?"ClassDeclaration":"ClassExpression")};j.parseClassMember=function(e){var t=this;if(this.eat(b.semi)){return null}var r=this.startNode();var i=function(e,i){if(i===void 0)i=false;var n=t.start,a=t.startLoc;if(!t.eatContextual(e)){return false}if(t.type!==b.parenL&&(!i||!t.canInsertSemicolon())){return true}if(r.key){t.unexpected()}r.computed=false;r.key=t.startNodeAt(n,a);r.key.name=e;t.finishNode(r.key,"Identifier");return false};r.kind="method";r.static=i("static");var n=this.eat(b.star);var a=false;if(!n){if(this.options.ecmaVersion>=8&&i("async",true)){a=true;n=this.options.ecmaVersion>=9&&this.eat(b.star)}else if(i("get")){r.kind="get"}else if(i("set")){r.kind="set"}}if(!r.key){this.parsePropertyName(r)}var s=r.key;if(!r.computed&&!r.static&&(s.type==="Identifier"&&s.name==="constructor"||s.type==="Literal"&&s.value==="constructor")){if(r.kind!=="method"){this.raise(s.start,"Constructor can't have get/set modifier")}if(n){this.raise(s.start,"Constructor can't be a generator")}if(a){this.raise(s.start,"Constructor can't be an async method")}r.kind="constructor"}else if(r.static&&s.type==="Identifier"&&s.name==="prototype"){this.raise(s.start,"Classes may not have a static property named prototype")}this.parseClassMethod(e,r,n,a);if(r.kind==="get"&&r.value.params.length!==0){this.raiseRecoverable(r.value.start,"getter should have no params")}if(r.kind==="set"&&r.value.params.length!==1){this.raiseRecoverable(r.value.start,"setter should have exactly one param")}if(r.kind==="set"&&r.value.params[0].type==="RestElement"){this.raiseRecoverable(r.value.params[0].start,"Setter cannot use rest params")}return r};j.parseClassMethod=function(e,t,r,i){t.value=this.parseMethod(r,i);e.body.push(this.finishNode(t,"MethodDefinition"))};j.parseClassId=function(e,t){e.id=this.type===b.name?this.parseIdent():t===true?this.unexpected():null};j.parseClassSuper=function(e){e.superClass=this.eat(b._extends)?this.parseExprSubscripts():null};j.parseExport=function(e,t){var r=this;this.next();if(this.eat(b.star)){this.expectContextual("from");if(this.type!==b.string){this.unexpected()}e.source=this.parseExprAtom();this.semicolon();return this.finishNode(e,"ExportAllDeclaration")}if(this.eat(b._default)){this.checkExport(t,"default",this.lastTokStart);var i;if(this.type===b._function||(i=this.isAsyncFunction())){var n=this.startNode();this.next();if(i){this.next()}e.declaration=this.parseFunction(n,"nullableID",false,i)}else if(this.type===b._class){var a=this.startNode();e.declaration=this.parseClass(a,"nullableID")}else{e.declaration=this.parseMaybeAssign();this.semicolon()}return this.finishNode(e,"ExportDefaultDeclaration")}if(this.shouldParseExportStatement()){e.declaration=this.parseStatement(true);if(e.declaration.type==="VariableDeclaration"){this.checkVariableExport(t,e.declaration.declarations)}else{this.checkExport(t,e.declaration.id.name,e.declaration.id.start)}e.specifiers=[];e.source=null}else{e.declaration=null;e.specifiers=this.parseExportSpecifiers(t);if(this.eatContextual("from")){if(this.type!==b.string){this.unexpected()}e.source=this.parseExprAtom()}else{for(var s=0,o=e.specifiers;s=6&&e){switch(e.type){case"Identifier":if(this.inAsync&&e.name==="await"){this.raise(e.start,"Can not use 'await' as identifier inside an async function")}break;case"ObjectPattern":case"ArrayPattern":case"RestElement":break;case"ObjectExpression":e.type="ObjectPattern";if(r){this.checkPatternErrors(r,true)}for(var n=0,a=e.properties;n=9&&e.type==="SpreadElement"){return}if(this.options.ecmaVersion>=6&&(e.computed||e.method||e.shorthand)){return}var i=e.key;var n;switch(i.type){case"Identifier":n=i.name;break;case"Literal":n=String(i.value);break;default:return}var a=e.kind;if(this.options.ecmaVersion>=6){if(n==="__proto__"&&a==="init"){if(t.proto){if(r&&r.doubleProto<0){r.doubleProto=i.start}else{this.raiseRecoverable(i.start,"Redefinition of __proto__ property")}}t.proto=true}return}n="$"+n;var s=t[n];if(s){var o;if(a==="init"){o=this.strict&&s.init||s.get||s.set}else{o=s.init||s[a]}if(o){this.raiseRecoverable(i.start,"Redefinition of property")}}else{s=t[n]={init:false,get:false,set:false}}s[a]=true};$.parseExpression=function(e,t){var r=this;var i=this.start,n=this.startLoc;var a=this.parseMaybeAssign(e,t);if(this.type===b.comma){var s=this.startNodeAt(i,n);s.expressions=[a];while(this.eat(b.comma)){s.expressions.push(r.parseMaybeAssign(e,t))}return this.finishNode(s,"SequenceExpression")}return a};$.parseMaybeAssign=function(e,t,r){if(this.inGenerator&&this.isContextual("yield")){return this.parseYield()}var i=false,n=-1,a=-1;if(t){n=t.parenthesizedAssign;a=t.trailingComma;t.parenthesizedAssign=t.trailingComma=-1}else{t=new H;i=true}var s=this.start,o=this.startLoc;if(this.type===b.parenL||this.type===b.name){this.potentialArrowAt=this.start}var l=this.parseMaybeConditional(e,t);if(r){l=r.call(this,l,s,o)}if(this.type.isAssign){var u=this.startNodeAt(s,o);u.operator=this.value;u.left=this.type===b.eq?this.toAssignable(l,false,t):l;if(!i){H.call(t)}t.shorthandAssign=-1;this.checkLVal(l);this.next();u.right=this.parseMaybeAssign(e);return this.finishNode(u,"AssignmentExpression")}else{if(i){this.checkExpressionErrors(t,true)}}if(n>-1){t.parenthesizedAssign=n}if(a>-1){t.trailingComma=a}return l};$.parseMaybeConditional=function(e,t){var r=this.start,i=this.startLoc;var n=this.parseExprOps(e,t);if(this.checkExpressionErrors(t)){return n}if(this.eat(b.question)){var a=this.startNodeAt(r,i);a.test=n;a.consequent=this.parseMaybeAssign();this.expect(b.colon);a.alternate=this.parseMaybeAssign(e);return this.finishNode(a,"ConditionalExpression")}return n};$.parseExprOps=function(e,t){var r=this.start,i=this.startLoc;var n=this.parseMaybeUnary(t,false);if(this.checkExpressionErrors(t)){return n}return n.start===r&&n.type==="ArrowFunctionExpression"?n:this.parseExprOp(n,r,i,-1,e)};$.parseExprOp=function(e,t,r,i,n){var a=this.type.binop;if(a!=null&&(!n||this.type!==b._in)){if(a>i){var s=this.type===b.logicalOR||this.type===b.logicalAND;var o=this.value;this.next();var l=this.start,u=this.startLoc;var c=this.parseExprOp(this.parseMaybeUnary(null,false),l,u,a,n);var f=this.buildBinary(t,r,e,c,o,s);return this.parseExprOp(f,t,r,i,n)}}return e};$.buildBinary=function(e,t,r,i,n,a){var s=this.startNodeAt(e,t);s.left=r;s.operator=n;s.right=i;return this.finishNode(s,a?"LogicalExpression":"BinaryExpression")};$.parseMaybeUnary=function(e,t){var r=this;var i=this.start,n=this.startLoc,a;if(this.isContextual("await")&&(this.inAsync||!this.inFunction&&this.options.allowAwaitOutsideFunction)){a=this.parseAwait();t=true}else if(this.type.prefix){var s=this.startNode(),o=this.type===b.incDec;s.operator=this.value;s.prefix=true;this.next();s.argument=this.parseMaybeUnary(null,true);this.checkExpressionErrors(e,true);if(o){this.checkLVal(s.argument)}else if(this.strict&&s.operator==="delete"&&s.argument.type==="Identifier"){this.raiseRecoverable(s.start,"Deleting local variable in strict mode")}else{t=true}a=this.finishNode(s,o?"UpdateExpression":"UnaryExpression")}else{a=this.parseExprSubscripts(e);if(this.checkExpressionErrors(e)){return a}while(this.type.postfix&&!this.canInsertSemicolon()){var l=r.startNodeAt(i,n);l.operator=r.value;l.prefix=false;l.argument=a;r.checkLVal(a);r.next();a=r.finishNode(l,"UpdateExpression")}}if(!t&&this.eat(b.starstar)){return this.buildBinary(i,n,a,this.parseMaybeUnary(null,false),"**",false)}else{return a}};$.parseExprSubscripts=function(e){var t=this.start,r=this.startLoc;var i=this.parseExprAtom(e);var n=i.type==="ArrowFunctionExpression"&&this.input.slice(this.lastTokStart,this.lastTokEnd)!==")";if(this.checkExpressionErrors(e)||n){return i}var a=this.parseSubscripts(i,t,r);if(e&&a.type==="MemberExpression"){if(e.parenthesizedAssign>=a.start){e.parenthesizedAssign=-1}if(e.parenthesizedBind>=a.start){e.parenthesizedBind=-1}}return a};$.parseSubscripts=function(e,t,r,i){var n=this;var a=this.options.ecmaVersion>=8&&e.type==="Identifier"&&e.name==="async"&&this.lastTokEnd===e.end&&!this.canInsertSemicolon()&&this.input.slice(e.start,e.end)==="async";for(var s=void 0;;){if((s=n.eat(b.bracketL))||n.eat(b.dot)){var o=n.startNodeAt(t,r);o.object=e;o.property=s?n.parseExpression():n.parseIdent(true);o.computed=!!s;if(s){n.expect(b.bracketR)}e=n.finishNode(o,"MemberExpression")}else if(!i&&n.eat(b.parenL)){var l=new H,u=n.yieldPos,c=n.awaitPos;n.yieldPos=0;n.awaitPos=0;var f=n.parseExprList(b.parenR,n.options.ecmaVersion>=8,false,l);if(a&&!n.canInsertSemicolon()&&n.eat(b.arrow)){n.checkPatternErrors(l,false);n.checkYieldAwaitInDefaultParams();n.yieldPos=u;n.awaitPos=c;return n.parseArrowExpression(n.startNodeAt(t,r),f,true)}n.checkExpressionErrors(l,true);n.yieldPos=u||n.yieldPos;n.awaitPos=c||n.awaitPos;var h=n.startNodeAt(t,r);h.callee=e;h.arguments=f;e=n.finishNode(h,"CallExpression")}else if(n.type===b.backQuote){var p=n.startNodeAt(t,r);p.tag=e;p.quasi=n.parseTemplate({isTagged:true});e=n.finishNode(p,"TaggedTemplateExpression")}else{return e}}};$.parseExprAtom=function(e){var t,r=this.potentialArrowAt===this.start;switch(this.type){case b._super:if(!this.inFunction){this.raise(this.start,"'super' outside of function or class")}t=this.startNode();this.next();if(this.type!==b.dot&&this.type!==b.bracketL&&this.type!==b.parenL){this.unexpected()}return this.finishNode(t,"Super");case b._this:t=this.startNode();this.next();return this.finishNode(t,"ThisExpression");case b.name:var i=this.start,n=this.startLoc,a=this.containsEsc;var s=this.parseIdent(this.type!==b.name);if(this.options.ecmaVersion>=8&&!a&&s.name==="async"&&!this.canInsertSemicolon()&&this.eat(b._function)){return this.parseFunction(this.startNodeAt(i,n),false,false,true)}if(r&&!this.canInsertSemicolon()){if(this.eat(b.arrow)){return this.parseArrowExpression(this.startNodeAt(i,n),[s],false)}if(this.options.ecmaVersion>=8&&s.name==="async"&&this.type===b.name&&!a){s=this.parseIdent();if(this.canInsertSemicolon()||!this.eat(b.arrow)){this.unexpected()}return this.parseArrowExpression(this.startNodeAt(i,n),[s],true)}}return s;case b.regexp:var o=this.value;t=this.parseLiteral(o.value);t.regex={pattern:o.pattern,flags:o.flags};return t;case b.num:case b.string:return this.parseLiteral(this.value);case b._null:case b._true:case b._false:t=this.startNode();t.value=this.type===b._null?null:this.type===b._true;t.raw=this.type.keyword;this.next();return this.finishNode(t,"Literal");case b.parenL:var l=this.start,u=this.parseParenAndDistinguishExpression(r);if(e){if(e.parenthesizedAssign<0&&!this.isSimpleAssignTarget(u)){e.parenthesizedAssign=l}if(e.parenthesizedBind<0){e.parenthesizedBind=l}}return u;case b.bracketL:t=this.startNode();this.next();t.elements=this.parseExprList(b.bracketR,true,true,e);return this.finishNode(t,"ArrayExpression");case b.braceL:return this.parseObj(false,e);case b._function:t=this.startNode();this.next();return this.parseFunction(t,false);case b._class:return this.parseClass(this.startNode(),false);case b._new:return this.parseNew();case b.backQuote:return this.parseTemplate();default:this.unexpected()}};$.parseLiteral=function(e){var t=this.startNode();t.value=e;t.raw=this.input.slice(this.start,this.end);this.next();return this.finishNode(t,"Literal")};$.parseParenExpression=function(){this.expect(b.parenL);var e=this.parseExpression();this.expect(b.parenR);return e};$.parseParenAndDistinguishExpression=function(e){var t=this;var r=this.start,i=this.startLoc,n,a=this.options.ecmaVersion>=8;if(this.options.ecmaVersion>=6){this.next();var s=this.start,o=this.startLoc;var l=[],u=true,c=false;var f=new H,h=this.yieldPos,p=this.awaitPos,d;this.yieldPos=0;this.awaitPos=0;while(this.type!==b.parenR){u?u=false:t.expect(b.comma);if(a&&t.afterTrailingComma(b.parenR,true)){c=true;break}else if(t.type===b.ellipsis){d=t.start;l.push(t.parseParenItem(t.parseRestBinding()));if(t.type===b.comma){t.raise(t.start,"Comma is not permitted after the rest element")}break}else{l.push(t.parseMaybeAssign(false,f,t.parseParenItem))}}var m=this.start,v=this.startLoc;this.expect(b.parenR);if(e&&!this.canInsertSemicolon()&&this.eat(b.arrow)){this.checkPatternErrors(f,false);this.checkYieldAwaitInDefaultParams();this.yieldPos=h;this.awaitPos=p;return this.parseParenArrowList(r,i,l)}if(!l.length||c){this.unexpected(this.lastTokStart)}if(d){this.unexpected(d)}this.checkExpressionErrors(f,true);this.yieldPos=h||this.yieldPos;this.awaitPos=p||this.awaitPos;if(l.length>1){n=this.startNodeAt(s,o);n.expressions=l;this.finishNodeAt(n,"SequenceExpression",m,v)}else{n=l[0]}}else{n=this.parseParenExpression()}if(this.options.preserveParens){var g=this.startNodeAt(r,i);g.expression=n;return this.finishNode(g,"ParenthesizedExpression")}else{return n}};$.parseParenItem=function(e){return e};$.parseParenArrowList=function(e,t,r){return this.parseArrowExpression(this.startNodeAt(e,t),r)};var X=[];$.parseNew=function(){var e=this.startNode();var t=this.parseIdent(true);if(this.options.ecmaVersion>=6&&this.eat(b.dot)){e.meta=t;var r=this.containsEsc;e.property=this.parseIdent(true);if(e.property.name!=="target"||r){this.raiseRecoverable(e.property.start,"The only valid meta property for new is new.target")}if(!this.inFunction){this.raiseRecoverable(e.start,"new.target can only be used in functions")}return this.finishNode(e,"MetaProperty")}var i=this.start,n=this.startLoc;e.callee=this.parseSubscripts(this.parseExprAtom(),i,n,true);if(this.eat(b.parenL)){e.arguments=this.parseExprList(b.parenR,this.options.ecmaVersion>=8,false)}else{e.arguments=X}return this.finishNode(e,"NewExpression")};$.parseTemplateElement=function(e){var t=e.isTagged;var r=this.startNode();if(this.type===b.invalidTemplate){if(!t){this.raiseRecoverable(this.start,"Bad escape sequence in untagged template literal")}r.value={raw:this.value,cooked:null}}else{r.value={raw:this.input.slice(this.start,this.end).replace(/\r\n?/g,"\n"),cooked:this.value}}this.next();r.tail=this.type===b.backQuote;return this.finishNode(r,"TemplateElement")};$.parseTemplate=function(e){var t=this;if(e===void 0)e={};var r=e.isTagged;if(r===void 0)r=false;var i=this.startNode();this.next();i.expressions=[];var n=this.parseTemplateElement({isTagged:r});i.quasis=[n];while(!n.tail){if(t.type===b.eof){t.raise(t.pos,"Unterminated template literal")}t.expect(b.dollarBraceL);i.expressions.push(t.parseExpression());t.expect(b.braceR);i.quasis.push(n=t.parseTemplateElement({isTagged:r}))}this.next();return this.finishNode(i,"TemplateLiteral")};$.isAsyncProp=function(e){return!e.computed&&e.key.type==="Identifier"&&e.key.name==="async"&&(this.type===b.name||this.type===b.num||this.type===b.string||this.type===b.bracketL||this.type.keyword||this.options.ecmaVersion>=9&&this.type===b.star)&&!w.test(this.input.slice(this.lastTokEnd,this.start))};$.parseObj=function(e,t){var r=this;var i=this.startNode(),n=true,a={};i.properties=[];this.next();while(!this.eat(b.braceR)){if(!n){r.expect(b.comma);if(r.afterTrailingComma(b.braceR)){break}}else{n=false}var s=r.parseProperty(e,t);if(!e){r.checkPropClash(s,a,t)}i.properties.push(s)}return this.finishNode(i,e?"ObjectPattern":"ObjectExpression")};$.parseProperty=function(e,t){var r=this.startNode(),i,n,a,s;if(this.options.ecmaVersion>=9&&this.eat(b.ellipsis)){if(e){r.argument=this.parseIdent(false);if(this.type===b.comma){this.raise(this.start,"Comma is not permitted after the rest element")}return this.finishNode(r,"RestElement")}if(this.type===b.parenL&&t){if(t.parenthesizedAssign<0){t.parenthesizedAssign=this.start}if(t.parenthesizedBind<0){t.parenthesizedBind=this.start}}r.argument=this.parseMaybeAssign(false,t);if(this.type===b.comma&&t&&t.trailingComma<0){t.trailingComma=this.start}return this.finishNode(r,"SpreadElement")}if(this.options.ecmaVersion>=6){r.method=false;r.shorthand=false;if(e||t){a=this.start;s=this.startLoc}if(!e){i=this.eat(b.star)}}var o=this.containsEsc;this.parsePropertyName(r);if(!e&&!o&&this.options.ecmaVersion>=8&&!i&&this.isAsyncProp(r)){n=true;i=this.options.ecmaVersion>=9&&this.eat(b.star);this.parsePropertyName(r,t)}else{n=false}this.parsePropertyValue(r,e,i,n,a,s,t,o);return this.finishNode(r,"Property")};$.parsePropertyValue=function(e,t,r,i,n,a,s,o){if((r||i)&&this.type===b.colon){this.unexpected()}if(this.eat(b.colon)){e.value=t?this.parseMaybeDefault(this.start,this.startLoc):this.parseMaybeAssign(false,s);e.kind="init"}else if(this.options.ecmaVersion>=6&&this.type===b.parenL){if(t){this.unexpected()}e.kind="init";e.method=true;e.value=this.parseMethod(r,i)}else if(!t&&!o&&this.options.ecmaVersion>=5&&!e.computed&&e.key.type==="Identifier"&&(e.key.name==="get"||e.key.name==="set")&&(this.type!==b.comma&&this.type!==b.braceR)){if(r||i){this.unexpected()}e.kind=e.key.name;this.parsePropertyName(e);e.value=this.parseMethod(false);var l=e.kind==="get"?0:1;if(e.value.params.length!==l){var u=e.value.start;if(e.kind==="get"){this.raiseRecoverable(u,"getter should have no params")}else{this.raiseRecoverable(u,"setter should have exactly one param")}}else{if(e.kind==="set"&&e.value.params[0].type==="RestElement"){this.raiseRecoverable(e.value.params[0].start,"Setter cannot use rest params")}}}else if(this.options.ecmaVersion>=6&&!e.computed&&e.key.type==="Identifier"){this.checkUnreserved(e.key);e.kind="init";if(t){e.value=this.parseMaybeDefault(n,a,e.key)}else if(this.type===b.eq&&s){if(s.shorthandAssign<0){s.shorthandAssign=this.start}e.value=this.parseMaybeDefault(n,a,e.key)}else{e.value=e.key}e.shorthand=true}else{this.unexpected()}};$.parsePropertyName=function(e){if(this.options.ecmaVersion>=6){if(this.eat(b.bracketL)){e.computed=true;e.key=this.parseMaybeAssign();this.expect(b.bracketR);return e.key}else{e.computed=false}}return e.key=this.type===b.num||this.type===b.string?this.parseExprAtom():this.parseIdent(true)};$.initFunction=function(e){e.id=null;if(this.options.ecmaVersion>=6){e.generator=false;e.expression=false}if(this.options.ecmaVersion>=8){e.async=false}};$.parseMethod=function(e,t){var r=this.startNode(),i=this.inGenerator,n=this.inAsync,a=this.yieldPos,s=this.awaitPos,o=this.inFunction;this.initFunction(r);if(this.options.ecmaVersion>=6){r.generator=e}if(this.options.ecmaVersion>=8){r.async=!!t}this.inGenerator=r.generator;this.inAsync=r.async;this.yieldPos=0;this.awaitPos=0;this.inFunction=true;this.enterFunctionScope();this.expect(b.parenL);r.params=this.parseBindingList(b.parenR,false,this.options.ecmaVersion>=8);this.checkYieldAwaitInDefaultParams();this.parseFunctionBody(r,false);this.inGenerator=i;this.inAsync=n;this.yieldPos=a;this.awaitPos=s;this.inFunction=o;return this.finishNode(r,"FunctionExpression")};$.parseArrowExpression=function(e,t,r){var i=this.inGenerator,n=this.inAsync,a=this.yieldPos,s=this.awaitPos,o=this.inFunction;this.enterFunctionScope();this.initFunction(e);if(this.options.ecmaVersion>=8){e.async=!!r}this.inGenerator=false;this.inAsync=e.async;this.yieldPos=0;this.awaitPos=0;this.inFunction=true;e.params=this.toAssignableList(t,true);this.parseFunctionBody(e,true);this.inGenerator=i;this.inAsync=n;this.yieldPos=a;this.awaitPos=s;this.inFunction=o;return this.finishNode(e,"ArrowFunctionExpression")};$.parseFunctionBody=function(e,t){var r=t&&this.type!==b.braceL;var i=this.strict,n=false;if(r){e.body=this.parseMaybeAssign();e.expression=true;this.checkParams(e,false)}else{var a=this.options.ecmaVersion>=7&&!this.isSimpleParamList(e.params);if(!i||a){n=this.strictDirective(this.end);if(n&&a){this.raiseRecoverable(e.start,"Illegal 'use strict' directive in function with non-simple parameter list")}}var s=this.labels;this.labels=[];if(n){this.strict=true}this.checkParams(e,!i&&!n&&!t&&this.isSimpleParamList(e.params));e.body=this.parseBlock(false);e.expression=false;this.adaptDirectivePrologue(e.body.body);this.labels=s}this.exitFunctionScope();if(this.strict&&e.id){ -this.checkLVal(e.id,"none")}this.strict=i};$.isSimpleParamList=function(e){for(var t=0,r=e;t0)t[r]=arguments[r+1];for(var i=0,n=t;i=1;t--){var r=e.context[t];if(r.token==="function"){return r.generator}}return false};ne.updateContext=function(e){var t,r=this.type;if(r.keyword&&e===b.dot){this.exprAllowed=false}else if(t=r.updateContext){t.call(this,e)}else{this.exprAllowed=r.beforeExpr}};b.parenR.updateContext=b.braceR.updateContext=function(){if(this.context.length===1){this.exprAllowed=true;return}var e=this.context.pop();if(e===ie.b_stat&&this.curContext().token==="function"){e=this.context.pop()}this.exprAllowed=!e.isExpr};b.braceL.updateContext=function(e){this.context.push(this.braceIsBlock(e)?ie.b_stat:ie.b_expr);this.exprAllowed=true};b.dollarBraceL.updateContext=function(){this.context.push(ie.b_tmpl);this.exprAllowed=true};b.parenL.updateContext=function(e){var t=e===b._if||e===b._for||e===b._with||e===b._while;this.context.push(t?ie.p_stat:ie.p_expr);this.exprAllowed=true};b.incDec.updateContext=function(){};b._function.updateContext=b._class.updateContext=function(e){if(e.beforeExpr&&e!==b.semi&&e!==b._else&&!((e===b.colon||e===b.braceL)&&this.curContext()===ie.b_stat)){this.context.push(ie.f_expr)}else{this.context.push(ie.f_stat)}this.exprAllowed=false};b.backQuote.updateContext=function(){if(this.curContext()===ie.q_tmpl){this.context.pop()}else{this.context.push(ie.q_tmpl)}this.exprAllowed=false};b.star.updateContext=function(e){if(e===b._function){var t=this.context.length-1;if(this.context[t]===ie.f_expr){this.context[t]=ie.f_expr_gen}else{this.context[t]=ie.f_gen}}this.exprAllowed=true};b.name.updateContext=function(e){var t=false;if(this.options.ecmaVersion>=6&&e!==b.dot){if(this.value==="of"&&!this.exprAllowed||this.value==="yield"&&this.inGeneratorContext()){t=true}}this.exprAllowed=t};var ae={$LONE:["ASCII","ASCII_Hex_Digit","AHex","Alphabetic","Alpha","Any","Assigned","Bidi_Control","Bidi_C","Bidi_Mirrored","Bidi_M","Case_Ignorable","CI","Cased","Changes_When_Casefolded","CWCF","Changes_When_Casemapped","CWCM","Changes_When_Lowercased","CWL","Changes_When_NFKC_Casefolded","CWKCF","Changes_When_Titlecased","CWT","Changes_When_Uppercased","CWU","Dash","Default_Ignorable_Code_Point","DI","Deprecated","Dep","Diacritic","Dia","Emoji","Emoji_Component","Emoji_Modifier","Emoji_Modifier_Base","Emoji_Presentation","Extender","Ext","Grapheme_Base","Gr_Base","Grapheme_Extend","Gr_Ext","Hex_Digit","Hex","IDS_Binary_Operator","IDSB","IDS_Trinary_Operator","IDST","ID_Continue","IDC","ID_Start","IDS","Ideographic","Ideo","Join_Control","Join_C","Logical_Order_Exception","LOE","Lowercase","Lower","Math","Noncharacter_Code_Point","NChar","Pattern_Syntax","Pat_Syn","Pattern_White_Space","Pat_WS","Quotation_Mark","QMark","Radical","Regional_Indicator","RI","Sentence_Terminal","STerm","Soft_Dotted","SD","Terminal_Punctuation","Term","Unified_Ideograph","UIdeo","Uppercase","Upper","Variation_Selector","VS","White_Space","space","XID_Continue","XIDC","XID_Start","XIDS"],General_Category:["Cased_Letter","LC","Close_Punctuation","Pe","Connector_Punctuation","Pc","Control","Cc","cntrl","Currency_Symbol","Sc","Dash_Punctuation","Pd","Decimal_Number","Nd","digit","Enclosing_Mark","Me","Final_Punctuation","Pf","Format","Cf","Initial_Punctuation","Pi","Letter","L","Letter_Number","Nl","Line_Separator","Zl","Lowercase_Letter","Ll","Mark","M","Combining_Mark","Math_Symbol","Sm","Modifier_Letter","Lm","Modifier_Symbol","Sk","Nonspacing_Mark","Mn","Number","N","Open_Punctuation","Ps","Other","C","Other_Letter","Lo","Other_Number","No","Other_Punctuation","Po","Other_Symbol","So","Paragraph_Separator","Zp","Private_Use","Co","Punctuation","P","punct","Separator","Z","Space_Separator","Zs","Spacing_Mark","Mc","Surrogate","Cs","Symbol","S","Titlecase_Letter","Lt","Unassigned","Cn","Uppercase_Letter","Lu"],Script:["Adlam","Adlm","Ahom","Anatolian_Hieroglyphs","Hluw","Arabic","Arab","Armenian","Armn","Avestan","Avst","Balinese","Bali","Bamum","Bamu","Bassa_Vah","Bass","Batak","Batk","Bengali","Beng","Bhaiksuki","Bhks","Bopomofo","Bopo","Brahmi","Brah","Braille","Brai","Buginese","Bugi","Buhid","Buhd","Canadian_Aboriginal","Cans","Carian","Cari","Caucasian_Albanian","Aghb","Chakma","Cakm","Cham","Cherokee","Cher","Common","Zyyy","Coptic","Copt","Qaac","Cuneiform","Xsux","Cypriot","Cprt","Cyrillic","Cyrl","Deseret","Dsrt","Devanagari","Deva","Duployan","Dupl","Egyptian_Hieroglyphs","Egyp","Elbasan","Elba","Ethiopic","Ethi","Georgian","Geor","Glagolitic","Glag","Gothic","Goth","Grantha","Gran","Greek","Grek","Gujarati","Gujr","Gurmukhi","Guru","Han","Hani","Hangul","Hang","Hanunoo","Hano","Hatran","Hatr","Hebrew","Hebr","Hiragana","Hira","Imperial_Aramaic","Armi","Inherited","Zinh","Qaai","Inscriptional_Pahlavi","Phli","Inscriptional_Parthian","Prti","Javanese","Java","Kaithi","Kthi","Kannada","Knda","Katakana","Kana","Kayah_Li","Kali","Kharoshthi","Khar","Khmer","Khmr","Khojki","Khoj","Khudawadi","Sind","Lao","Laoo","Latin","Latn","Lepcha","Lepc","Limbu","Limb","Linear_A","Lina","Linear_B","Linb","Lisu","Lycian","Lyci","Lydian","Lydi","Mahajani","Mahj","Malayalam","Mlym","Mandaic","Mand","Manichaean","Mani","Marchen","Marc","Masaram_Gondi","Gonm","Meetei_Mayek","Mtei","Mende_Kikakui","Mend","Meroitic_Cursive","Merc","Meroitic_Hieroglyphs","Mero","Miao","Plrd","Modi","Mongolian","Mong","Mro","Mroo","Multani","Mult","Myanmar","Mymr","Nabataean","Nbat","New_Tai_Lue","Talu","Newa","Nko","Nkoo","Nushu","Nshu","Ogham","Ogam","Ol_Chiki","Olck","Old_Hungarian","Hung","Old_Italic","Ital","Old_North_Arabian","Narb","Old_Permic","Perm","Old_Persian","Xpeo","Old_South_Arabian","Sarb","Old_Turkic","Orkh","Oriya","Orya","Osage","Osge","Osmanya","Osma","Pahawh_Hmong","Hmng","Palmyrene","Palm","Pau_Cin_Hau","Pauc","Phags_Pa","Phag","Phoenician","Phnx","Psalter_Pahlavi","Phlp","Rejang","Rjng","Runic","Runr","Samaritan","Samr","Saurashtra","Saur","Sharada","Shrd","Shavian","Shaw","Siddham","Sidd","SignWriting","Sgnw","Sinhala","Sinh","Sora_Sompeng","Sora","Soyombo","Soyo","Sundanese","Sund","Syloti_Nagri","Sylo","Syriac","Syrc","Tagalog","Tglg","Tagbanwa","Tagb","Tai_Le","Tale","Tai_Tham","Lana","Tai_Viet","Tavt","Takri","Takr","Tamil","Taml","Tangut","Tang","Telugu","Telu","Thaana","Thaa","Thai","Tibetan","Tibt","Tifinagh","Tfng","Tirhuta","Tirh","Ugaritic","Ugar","Vai","Vaii","Warang_Citi","Wara","Yi","Yiii","Zanabazar_Square","Zanb"]};Array.prototype.push.apply(ae.$LONE,ae.General_Category);ae.gc=ae.General_Category;ae.sc=ae.Script_Extensions=ae.scx=ae.Script;var se=W.prototype;var oe=function e(t){this.parser=t;this.validFlags="gim"+(t.options.ecmaVersion>=6?"uy":"")+(t.options.ecmaVersion>=9?"s":"");this.source="";this.flags="";this.start=0;this.switchU=false;this.switchN=false;this.pos=0;this.lastIntValue=0;this.lastStringValue="";this.lastAssertionIsQuantifiable=false;this.numCapturingParens=0;this.maxBackReference=0;this.groupNames=[];this.backReferenceNames=[]};oe.prototype.reset=function e(t,r,i){var n=i.indexOf("u")!==-1;this.start=t|0;this.source=r+"";this.flags=i;this.switchU=n&&this.parser.options.ecmaVersion>=6;this.switchN=n&&this.parser.options.ecmaVersion>=9};oe.prototype.raise=function e(t){this.parser.raiseRecoverable(this.start,"Invalid regular expression: /"+this.source+"/: "+t)};oe.prototype.at=function e(t){var r=this.source;var i=r.length;if(t>=i){return-1}var n=r.charCodeAt(t);if(!this.switchU||n<=55295||n>=57344||t+1>=i){return n}return(n<<10)+r.charCodeAt(t+1)-56613888};oe.prototype.nextIndex=function e(t){var r=this.source;var i=r.length;if(t>=i){return i}var n=r.charCodeAt(t);if(!this.switchU||n<=55295||n>=57344||t+1>=i){return t+1}return t+2};oe.prototype.current=function e(){return this.at(this.pos)};oe.prototype.lookahead=function e(){return this.at(this.nextIndex(this.pos))};oe.prototype.advance=function e(){this.pos=this.nextIndex(this.pos)};oe.prototype.eat=function e(t){if(this.current()===t){this.advance();return true}return false};function le(e){if(e<=65535){return String.fromCharCode(e)}e-=65536;return String.fromCharCode((e>>10)+55296,(e&1023)+56320)}se.validateRegExpFlags=function(e){var t=this;var r=e.validFlags;var i=e.flags;for(var n=0;n-1){t.raise(e.start,"Duplicate regular expression flag")}}};se.validateRegExpPattern=function(e){this.regexp_pattern(e);if(!e.switchN&&this.options.ecmaVersion>=9&&e.groupNames.length>0){e.switchN=true;this.regexp_pattern(e)}};se.regexp_pattern=function(e){e.pos=0;e.lastIntValue=0;e.lastStringValue="";e.lastAssertionIsQuantifiable=false;e.numCapturingParens=0;e.maxBackReference=0;e.groupNames.length=0;e.backReferenceNames.length=0;this.regexp_disjunction(e);if(e.pos!==e.source.length){if(e.eat(41)){e.raise("Unmatched ')'")}if(e.eat(93)||e.eat(125)){e.raise("Lone quantifier brackets")}}if(e.maxBackReference>e.numCapturingParens){e.raise("Invalid escape")}for(var t=0,r=e.backReferenceNames;t=9){r=e.eat(60)}if(e.eat(61)||e.eat(33)){this.regexp_disjunction(e);if(!e.eat(41)){e.raise("Unterminated group")}e.lastAssertionIsQuantifiable=!r;return true}}e.pos=t;return false};se.regexp_eatQuantifier=function(e,t){if(t===void 0)t=false;if(this.regexp_eatQuantifierPrefix(e,t)){e.eat(63);return true}return false};se.regexp_eatQuantifierPrefix=function(e,t){return e.eat(42)||e.eat(43)||e.eat(63)||this.regexp_eatBracedQuantifier(e,t)};se.regexp_eatBracedQuantifier=function(e,t){var r=e.pos;if(e.eat(123)){var i=0,n=-1;if(this.regexp_eatDecimalDigits(e)){i=e.lastIntValue;if(e.eat(44)&&this.regexp_eatDecimalDigits(e)){n=e.lastIntValue}if(e.eat(125)){if(n!==-1&&n=9){this.regexp_groupSpecifier(e)}else if(e.current()===63){e.raise("Invalid group")}this.regexp_disjunction(e);if(e.eat(41)){e.numCapturingParens+=1;return true}e.raise("Unterminated group")}return false};se.regexp_eatExtendedAtom=function(e){return e.eat(46)||this.regexp_eatReverseSolidusAtomEscape(e)||this.regexp_eatCharacterClass(e)||this.regexp_eatUncapturingGroup(e)||this.regexp_eatCapturingGroup(e)||this.regexp_eatInvalidBracedQuantifier(e)||this.regexp_eatExtendedPatternCharacter(e)};se.regexp_eatInvalidBracedQuantifier=function(e){if(this.regexp_eatBracedQuantifier(e,true)){e.raise("Nothing to repeat")}return false};se.regexp_eatSyntaxCharacter=function(e){var t=e.current();if(ue(t)){e.lastIntValue=t;e.advance();return true}return false};function ue(e){return e===36||e>=40&&e<=43||e===46||e===63||e>=91&&e<=94||e>=123&&e<=125}se.regexp_eatPatternCharacters=function(e){var t=e.pos;var r=0;while((r=e.current())!==-1&&!ue(r)){e.advance()}return e.pos!==t};se.regexp_eatExtendedPatternCharacter=function(e){var t=e.current();if(t!==-1&&t!==36&&!(t>=40&&t<=43)&&t!==46&&t!==63&&t!==91&&t!==94&&t!==124){e.advance();return true}return false};se.regexp_groupSpecifier=function(e){if(e.eat(63)){if(this.regexp_eatGroupName(e)){if(e.groupNames.indexOf(e.lastStringValue)!==-1){e.raise("Duplicate capture group name")}e.groupNames.push(e.lastStringValue);return}e.raise("Invalid group")}};se.regexp_eatGroupName=function(e){e.lastStringValue="";if(e.eat(60)){if(this.regexp_eatRegExpIdentifierName(e)&&e.eat(62)){return true}e.raise("Invalid capture group name")}return false};se.regexp_eatRegExpIdentifierName=function(e){e.lastStringValue="";if(this.regexp_eatRegExpIdentifierStart(e)){e.lastStringValue+=le(e.lastIntValue);while(this.regexp_eatRegExpIdentifierPart(e)){e.lastStringValue+=le(e.lastIntValue)}return true}return false};se.regexp_eatRegExpIdentifierStart=function(e){var t=e.pos;var r=e.current();e.advance();if(r===92&&this.regexp_eatRegExpUnicodeEscapeSequence(e)){r=e.lastIntValue}if(ce(r)){e.lastIntValue=r;return true}e.pos=t;return false};function ce(e){return h(e,true)||e===36||e===95}se.regexp_eatRegExpIdentifierPart=function(e){var t=e.pos;var r=e.current();e.advance();if(r===92&&this.regexp_eatRegExpUnicodeEscapeSequence(e)){r=e.lastIntValue}if(fe(r)){e.lastIntValue=r;return true}e.pos=t;return false};function fe(e){return p(e,true)||e===36||e===95||e===8204||e===8205}se.regexp_eatAtomEscape=function(e){if(this.regexp_eatBackReference(e)||this.regexp_eatCharacterClassEscape(e)||this.regexp_eatCharacterEscape(e)||e.switchN&&this.regexp_eatKGroupName(e)){return true}if(e.switchU){if(e.current()===99){e.raise("Invalid unicode escape")}e.raise("Invalid escape")}return false};se.regexp_eatBackReference=function(e){var t=e.pos;if(this.regexp_eatDecimalEscape(e)){var r=e.lastIntValue;if(e.switchU){if(r>e.maxBackReference){e.maxBackReference=r}return true}if(r<=e.numCapturingParens){return true}e.pos=t}return false};se.regexp_eatKGroupName=function(e){if(e.eat(107)){if(this.regexp_eatGroupName(e)){e.backReferenceNames.push(e.lastStringValue);return true}e.raise("Invalid named reference")}return false};se.regexp_eatCharacterEscape=function(e){return this.regexp_eatControlEscape(e)||this.regexp_eatCControlLetter(e)||this.regexp_eatZero(e)||this.regexp_eatHexEscapeSequence(e)||this.regexp_eatRegExpUnicodeEscapeSequence(e)||!e.switchU&&this.regexp_eatLegacyOctalEscapeSequence(e)||this.regexp_eatIdentityEscape(e)};se.regexp_eatCControlLetter=function(e){var t=e.pos;if(e.eat(99)){if(this.regexp_eatControlLetter(e)){return true}e.pos=t}return false};se.regexp_eatZero=function(e){if(e.current()===48&&!ge(e.lookahead())){e.lastIntValue=0;e.advance();return true}return false};se.regexp_eatControlEscape=function(e){var t=e.current();if(t===116){e.lastIntValue=9;e.advance();return true}if(t===110){e.lastIntValue=10;e.advance();return true}if(t===118){e.lastIntValue=11;e.advance();return true}if(t===102){e.lastIntValue=12;e.advance();return true}if(t===114){e.lastIntValue=13;e.advance();return true}return false};se.regexp_eatControlLetter=function(e){var t=e.current();if(he(t)){e.lastIntValue=t%32;e.advance();return true}return false};function he(e){return e>=65&&e<=90||e>=97&&e<=122}se.regexp_eatRegExpUnicodeEscapeSequence=function(e){var t=e.pos;if(e.eat(117)){if(this.regexp_eatFixedHexDigits(e,4)){var r=e.lastIntValue;if(e.switchU&&r>=55296&&r<=56319){var i=e.pos;if(e.eat(92)&&e.eat(117)&&this.regexp_eatFixedHexDigits(e,4)){var n=e.lastIntValue;if(n>=56320&&n<=57343){e.lastIntValue=(r-55296)*1024+(n-56320)+65536;return true}}e.pos=i;e.lastIntValue=r}return true}if(e.switchU&&e.eat(123)&&this.regexp_eatHexDigits(e)&&e.eat(125)&&pe(e.lastIntValue)){return true}if(e.switchU){e.raise("Invalid unicode escape")}e.pos=t}return false};function pe(e){return e>=0&&e<=1114111}se.regexp_eatIdentityEscape=function(e){if(e.switchU){if(this.regexp_eatSyntaxCharacter(e)){return true}if(e.eat(47)){e.lastIntValue=47;return true}return false}var t=e.current();if(t!==99&&(!e.switchN||t!==107)){e.lastIntValue=t;e.advance();return true}return false};se.regexp_eatDecimalEscape=function(e){e.lastIntValue=0;var t=e.current();if(t>=49&&t<=57){do{e.lastIntValue=10*e.lastIntValue+(t-48);e.advance()}while((t=e.current())>=48&&t<=57);return true}return false};se.regexp_eatCharacterClassEscape=function(e){var t=e.current();if(de(t)){e.lastIntValue=-1;e.advance();return true}if(e.switchU&&this.options.ecmaVersion>=9&&(t===80||t===112)){e.lastIntValue=-1;e.advance();if(e.eat(123)&&this.regexp_eatUnicodePropertyValueExpression(e)&&e.eat(125)){return true}e.raise("Invalid property name")}return false};function de(e){return e===100||e===68||e===115||e===83||e===119||e===87}se.regexp_eatUnicodePropertyValueExpression=function(e){var t=e.pos;if(this.regexp_eatUnicodePropertyName(e)&&e.eat(61)){var r=e.lastStringValue;if(this.regexp_eatUnicodePropertyValue(e)){var i=e.lastStringValue;this.regexp_validateUnicodePropertyNameAndValue(e,r,i);return true}}e.pos=t;if(this.regexp_eatLoneUnicodePropertyNameOrValue(e)){var n=e.lastStringValue;this.regexp_validateUnicodePropertyNameOrValue(e,n);return true}return false};se.regexp_validateUnicodePropertyNameAndValue=function(e,t,r){if(!ae.hasOwnProperty(t)||ae[t].indexOf(r)===-1){e.raise("Invalid property name")}};se.regexp_validateUnicodePropertyNameOrValue=function(e,t){if(ae.$LONE.indexOf(t)===-1){e.raise("Invalid property name")}};se.regexp_eatUnicodePropertyName=function(e){var t=0;e.lastStringValue="";while(me(t=e.current())){e.lastStringValue+=le(t);e.advance()}return e.lastStringValue!==""};function me(e){return he(e)||e===95}se.regexp_eatUnicodePropertyValue=function(e){var t=0;e.lastStringValue="";while(ve(t=e.current())){e.lastStringValue+=le(t);e.advance()}return e.lastStringValue!==""};function ve(e){return me(e)||ge(e)}se.regexp_eatLoneUnicodePropertyNameOrValue=function(e){return this.regexp_eatUnicodePropertyValue(e)};se.regexp_eatCharacterClass=function(e){if(e.eat(91)){e.eat(94);this.regexp_classRanges(e);if(e.eat(93)){return true}e.raise("Unterminated character class")}return false};se.regexp_classRanges=function(e){var t=this;while(this.regexp_eatClassAtom(e)){var r=e.lastIntValue;if(e.eat(45)&&t.regexp_eatClassAtom(e)){var i=e.lastIntValue;if(e.switchU&&(r===-1||i===-1)){e.raise("Invalid character class")}if(r!==-1&&i!==-1&&r>i){e.raise("Range out of order in character class")}}}};se.regexp_eatClassAtom=function(e){var t=e.pos;if(e.eat(92)){if(this.regexp_eatClassEscape(e)){return true}if(e.switchU){var r=e.current();if(r===99||be(r)){e.raise("Invalid class escape")}e.raise("Invalid escape")}e.pos=t}var i=e.current();if(i!==93){e.lastIntValue=i;e.advance();return true}return false};se.regexp_eatClassEscape=function(e){var t=e.pos;if(e.eat(98)){e.lastIntValue=8;return true}if(e.switchU&&e.eat(45)){e.lastIntValue=45;return true}if(!e.switchU&&e.eat(99)){if(this.regexp_eatClassControlLetter(e)){return true}e.pos=t}return this.regexp_eatCharacterClassEscape(e)||this.regexp_eatCharacterEscape(e)};se.regexp_eatClassControlLetter=function(e){var t=e.current();if(ge(t)||t===95){e.lastIntValue=t%32;e.advance();return true}return false};se.regexp_eatHexEscapeSequence=function(e){var t=e.pos;if(e.eat(120)){if(this.regexp_eatFixedHexDigits(e,2)){return true}if(e.switchU){e.raise("Invalid escape")}e.pos=t}return false};se.regexp_eatDecimalDigits=function(e){var t=e.pos;var r=0;e.lastIntValue=0;while(ge(r=e.current())){e.lastIntValue=10*e.lastIntValue+(r-48);e.advance()}return e.pos!==t};function ge(e){return e>=48&&e<=57}se.regexp_eatHexDigits=function(e){var t=e.pos;var r=0;e.lastIntValue=0;while(ye(r=e.current())){e.lastIntValue=16*e.lastIntValue+xe(r);e.advance()}return e.pos!==t};function ye(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function xe(e){if(e>=65&&e<=70){return 10+(e-65)}if(e>=97&&e<=102){return 10+(e-97)}return e-48}se.regexp_eatLegacyOctalEscapeSequence=function(e){if(this.regexp_eatOctalDigit(e)){var t=e.lastIntValue;if(this.regexp_eatOctalDigit(e)){var r=e.lastIntValue;if(t<=3&&this.regexp_eatOctalDigit(e)){e.lastIntValue=t*64+r*8+e.lastIntValue}else{e.lastIntValue=t*8+r}}else{e.lastIntValue=t}return true}return false};se.regexp_eatOctalDigit=function(e){var t=e.current();if(be(t)){e.lastIntValue=t-48;e.advance();return true}e.lastIntValue=0;return false};function be(e){return e>=48&&e<=55}se.regexp_eatFixedHexDigits=function(e,t){var r=e.pos;e.lastIntValue=0;for(var i=0;i=this.input.length){return this.finishToken(b.eof)}if(e.override){return e.override(this)}else{this.readToken(this.fullCharCodeAtPos())}};ke.readToken=function(e){if(h(e,this.options.ecmaVersion>=6)||e===92){return this.readWord()}return this.getTokenFromCode(e)};ke.fullCharCodeAtPos=function(){var e=this.input.charCodeAt(this.pos);if(e<=55295||e>=57344){return e}var t=this.input.charCodeAt(this.pos+1);return(e<<10)+t-56613888};ke.skipBlockComment=function(){var e=this;var t=this.options.onComment&&this.curPosition();var r=this.pos,i=this.input.indexOf("*/",this.pos+=2);if(i===-1){this.raise(this.pos-2,"Unterminated comment")}this.pos=i+2;if(this.options.locations){k.lastIndex=r;var n;while((n=k.exec(this.input))&&n.index8&&t<14||t>=5760&&S.test(String.fromCharCode(t))){++e.pos}else{break e}}}};ke.finishToken=function(e,t){this.end=this.pos;if(this.options.locations){this.endLoc=this.curPosition()}var r=this.type;this.type=e;this.value=t;this.updateContext(r)};ke.readToken_dot=function(){var e=this.input.charCodeAt(this.pos+1);if(e>=48&&e<=57){return this.readNumber(true)}var t=this.input.charCodeAt(this.pos+2);if(this.options.ecmaVersion>=6&&e===46&&t===46){this.pos+=3;return this.finishToken(b.ellipsis)}else{++this.pos;return this.finishToken(b.dot)}};ke.readToken_slash=function(){var e=this.input.charCodeAt(this.pos+1);if(this.exprAllowed){++this.pos;return this.readRegexp()}if(e===61){return this.finishOp(b.assign,2)}return this.finishOp(b.slash,1)};ke.readToken_mult_modulo_exp=function(e){var t=this.input.charCodeAt(this.pos+1);var r=1;var i=e===42?b.star:b.modulo;if(this.options.ecmaVersion>=7&&e===42&&t===42){++r;i=b.starstar;t=this.input.charCodeAt(this.pos+2)}if(t===61){return this.finishOp(b.assign,r+1)}return this.finishOp(i,r)};ke.readToken_pipe_amp=function(e){var t=this.input.charCodeAt(this.pos+1);if(t===e){return this.finishOp(e===124?b.logicalOR:b.logicalAND,2)}if(t===61){return this.finishOp(b.assign,2)}return this.finishOp(e===124?b.bitwiseOR:b.bitwiseAND,1)};ke.readToken_caret=function(){var e=this.input.charCodeAt(this.pos+1);if(e===61){return this.finishOp(b.assign,2)}return this.finishOp(b.bitwiseXOR,1)};ke.readToken_plus_min=function(e){var t=this.input.charCodeAt(this.pos+1);if(t===e){if(t===45&&!this.inModule&&this.input.charCodeAt(this.pos+2)===62&&(this.lastTokEnd===0||w.test(this.input.slice(this.lastTokEnd,this.pos)))){this.skipLineComment(3);this.skipSpace();return this.nextToken()}return this.finishOp(b.incDec,2)}if(t===61){return this.finishOp(b.assign,2)}return this.finishOp(b.plusMin,1)};ke.readToken_lt_gt=function(e){var t=this.input.charCodeAt(this.pos+1);var r=1;if(t===e){r=e===62&&this.input.charCodeAt(this.pos+2)===62?3:2;if(this.input.charCodeAt(this.pos+r)===61){return this.finishOp(b.assign,r+1)}return this.finishOp(b.bitShift,r)}if(t===33&&e===60&&!this.inModule&&this.input.charCodeAt(this.pos+2)===45&&this.input.charCodeAt(this.pos+3)===45){this.skipLineComment(4);this.skipSpace();return this.nextToken()}if(t===61){r=2}return this.finishOp(b.relational,r)};ke.readToken_eq_excl=function(e){var t=this.input.charCodeAt(this.pos+1);if(t===61){return this.finishOp(b.equality,this.input.charCodeAt(this.pos+2)===61?3:2)}if(e===61&&t===62&&this.options.ecmaVersion>=6){this.pos+=2;return this.finishToken(b.arrow)}return this.finishOp(e===61?b.eq:b.prefix,1)};ke.getTokenFromCode=function(e){switch(e){case 46:return this.readToken_dot();case 40:++this.pos;return this.finishToken(b.parenL);case 41:++this.pos;return this.finishToken(b.parenR);case 59:++this.pos;return this.finishToken(b.semi);case 44:++this.pos;return this.finishToken(b.comma);case 91:++this.pos -;return this.finishToken(b.bracketL);case 93:++this.pos;return this.finishToken(b.bracketR);case 123:++this.pos;return this.finishToken(b.braceL);case 125:++this.pos;return this.finishToken(b.braceR);case 58:++this.pos;return this.finishToken(b.colon);case 63:++this.pos;return this.finishToken(b.question);case 96:if(this.options.ecmaVersion<6){break}++this.pos;return this.finishToken(b.backQuote);case 48:var t=this.input.charCodeAt(this.pos+1);if(t===120||t===88){return this.readRadixNumber(16)}if(this.options.ecmaVersion>=6){if(t===111||t===79){return this.readRadixNumber(8)}if(t===98||t===66){return this.readRadixNumber(2)}}case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return this.readNumber(false);case 34:case 39:return this.readString(e);case 47:return this.readToken_slash();case 37:case 42:return this.readToken_mult_modulo_exp(e);case 124:case 38:return this.readToken_pipe_amp(e);case 94:return this.readToken_caret();case 43:case 45:return this.readToken_plus_min(e);case 60:case 62:return this.readToken_lt_gt(e);case 61:case 33:return this.readToken_eq_excl(e);case 126:return this.finishOp(b.prefix,1)}this.raise(this.pos,"Unexpected character '"+Ce(e)+"'")};ke.finishOp=function(e,t){var r=this.input.slice(this.pos,this.pos+t);this.pos+=t;return this.finishToken(e,r)};ke.readRegexp=function(){var e=this;var t,r,i=this.pos;for(;;){if(e.pos>=e.input.length){e.raise(i,"Unterminated regular expression")}var n=e.input.charAt(e.pos);if(w.test(n)){e.raise(i,"Unterminated regular expression")}if(!t){if(n==="["){r=true}else if(n==="]"&&r){r=false}else if(n==="/"&&!r){break}t=n==="\\"}else{t=false}++e.pos}var a=this.input.slice(i,this.pos);++this.pos;var s=this.pos;var o=this.readWord1();if(this.containsEsc){this.unexpected(s)}var l=this.regexpState||(this.regexpState=new oe(this));l.reset(i,a,o);this.validateRegExpFlags(l);this.validateRegExpPattern(l);var u=null;try{u=new RegExp(a,o)}catch(e){}return this.finishToken(b.regexp,{pattern:a,flags:o,value:u})};ke.readInt=function(e,t){var r=this;var i=this.pos,n=0;for(var a=0,s=t==null?Infinity:t;a=97){l=o-97+10}else if(o>=65){l=o-65+10}else if(o>=48&&o<=57){l=o-48}else{l=Infinity}if(l>=e){break}++r.pos;n=n*e+l}if(this.pos===i||t!=null&&this.pos-i!==t){return null}return n};ke.readRadixNumber=function(e){this.pos+=2;var t=this.readInt(e);if(t==null){this.raise(this.start+2,"Expected number in radix "+e)}if(h(this.fullCharCodeAtPos())){this.raise(this.pos,"Identifier directly after number")}return this.finishToken(b.num,t)};ke.readNumber=function(e){var t=this.pos;if(!e&&this.readInt(10)===null){this.raise(t,"Invalid number")}var r=this.pos-t>=2&&this.input.charCodeAt(t)===48;if(r&&this.strict){this.raise(t,"Invalid number")}if(r&&/[89]/.test(this.input.slice(t,this.pos))){r=false}var i=this.input.charCodeAt(this.pos);if(i===46&&!r){++this.pos;this.readInt(10);i=this.input.charCodeAt(this.pos)}if((i===69||i===101)&&!r){i=this.input.charCodeAt(++this.pos);if(i===43||i===45){++this.pos}if(this.readInt(10)===null){this.raise(t,"Invalid number")}}if(h(this.fullCharCodeAtPos())){this.raise(this.pos,"Identifier directly after number")}var n=this.input.slice(t,this.pos);var a=r?parseInt(n,8):parseFloat(n);return this.finishToken(b.num,a)};ke.readCodePoint=function(){var e=this.input.charCodeAt(this.pos),t;if(e===123){if(this.options.ecmaVersion<6){this.unexpected()}var r=++this.pos;t=this.readHexChar(this.input.indexOf("}",this.pos)-this.pos);++this.pos;if(t>1114111){this.invalidStringToken(r,"Code point out of bounds")}}else{t=this.readHexChar(4)}return t};function Ce(e){if(e<=65535){return String.fromCharCode(e)}e-=65536;return String.fromCharCode((e>>10)+55296,(e&1023)+56320)}ke.readString=function(e){var t=this;var r="",i=++this.pos;for(;;){if(t.pos>=t.input.length){t.raise(t.start,"Unterminated string constant")}var n=t.input.charCodeAt(t.pos);if(n===e){break}if(n===92){r+=t.input.slice(i,t.pos);r+=t.readEscapedChar(false);i=t.pos}else{if(C(n,t.options.ecmaVersion>=10)){t.raise(t.start,"Unterminated string constant")}++t.pos}}r+=this.input.slice(i,this.pos++);return this.finishToken(b.string,r)};var Se={};ke.tryReadTemplateToken=function(){this.inTemplateElement=true;try{this.readTmplToken()}catch(e){if(e===Se){this.readInvalidTemplateToken()}else{throw e}}this.inTemplateElement=false};ke.invalidStringToken=function(e,t){if(this.inTemplateElement&&this.options.ecmaVersion>=9){throw Se}else{this.raise(e,t)}};ke.readTmplToken=function(){var e=this;var t="",r=this.pos;for(;;){if(e.pos>=e.input.length){e.raise(e.start,"Unterminated template")}var i=e.input.charCodeAt(e.pos);if(i===96||i===36&&e.input.charCodeAt(e.pos+1)===123){if(e.pos===e.start&&(e.type===b.template||e.type===b.invalidTemplate)){if(i===36){e.pos+=2;return e.finishToken(b.dollarBraceL)}else{++e.pos;return e.finishToken(b.backQuote)}}t+=e.input.slice(r,e.pos);return e.finishToken(b.template,t)}if(i===92){t+=e.input.slice(r,e.pos);t+=e.readEscapedChar(true);r=e.pos}else if(C(i)){t+=e.input.slice(r,e.pos);++e.pos;switch(i){case 13:if(e.input.charCodeAt(e.pos)===10){++e.pos}case 10:t+="\n";break;default:t+=String.fromCharCode(i);break}if(e.options.locations){++e.curLine;e.lineStart=e.pos}r=e.pos}else{++e.pos}}};ke.readInvalidTemplateToken=function(){var e=this;for(;this.pos=48&&t<=55){var r=this.input.substr(this.pos-1,3).match(/^[0-7]+/)[0];var i=parseInt(r,8);if(i>255){r=r.slice(0,-1);i=parseInt(r,8)}this.pos+=r.length-1;t=this.input.charCodeAt(this.pos);if((r!=="0"||t===56||t===57)&&(this.strict||e)){this.invalidStringToken(this.pos-1-r.length,e?"Octal literal in template string":"Octal literal in strict mode")}return String.fromCharCode(i)}return String.fromCharCode(t)}};ke.readHexChar=function(e){var t=this.pos;var r=this.readInt(16,e);if(r===null){this.invalidStringToken(t,"Bad character escape sequence")}return r};ke.readWord1=function(){var e=this;this.containsEsc=false;var t="",r=true,i=this.pos;var n=this.options.ecmaVersion>=6;while(this.pos=r)){s[u](n,o,e)}if((t==null||n.start===t)&&(r==null||n.end===r)&&i(u,n)){throw new a(n,o)}})(e,o)}catch(e){if(e instanceof a){return e}throw e}}function u(e,t,r,i,s){r=n(r);if(!i){i=v}try{(function e(n,s,o){var l=o||n.type;if(n.start>t||n.end=t&&r(l,n)){throw new a(n,s)}i[l](n,s,e)})(e,s)}catch(e){if(e instanceof a){return e}throw e}}function f(e,t,r,i,s){r=n(r);if(!i){i=v}var o;(function e(n,s,l){if(n.start>t){return}var u=l||n.type;if(n.end<=t&&(!o||o.node.end 0.1 || correlation < -0.1) {\n console.log(event + \":\", correlation);\n }\n}\n// → brushed teeth: -0.3805211953\n// → work: -0.1371988681\n// → reading: 0.1106828054\n", - "exercises": [ - { - "name": "The sum of a range", - "file": "code/solutions/04_1_the_sum_of_a_range.js", - "number": 1, - "type": "js", - "code": "// Your code here.\n\nconsole.log(range(1, 10));\n// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\nconsole.log(range(5, 2, -1));\n// → [5, 4, 3, 2]\nconsole.log(sum(range(1, 10)));\n// → 55", - "solution": "function range(start, end, step = start < end ? 1 : -1) {\n let array = [];\n\n if (step > 0) {\n for (let i = start; i <= end; i += step) array.push(i);\n } else {\n for (let i = start; i >= end; i += step) array.push(i);\n }\n return array;\n}\n\nfunction sum(array) {\n let total = 0;\n for (let value of array) {\n total += value;\n }\n return total;\n}\n\nconsole.log(range(1, 10))\n// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\nconsole.log(range(5, 2, -1));\n// → [5, 4, 3, 2]\nconsole.log(sum(range(1, 10)));\n// → 55" - }, - { - "name": "Reversing an array", - "file": "code/solutions/04_2_reversing_an_array.js", - "number": 2, - "type": "js", - "code": "// Your code here.\n\nconsole.log(reverseArray([\"A\", \"B\", \"C\"]));\n// → [\"C\", \"B\", \"A\"];\nlet arrayValue = [1, 2, 3, 4, 5];\nreverseArrayInPlace(arrayValue);\nconsole.log(arrayValue);\n// → [5, 4, 3, 2, 1]", - "solution": "function reverseArray(array) {\n let output = [];\n for (let i = array.length - 1; i >= 0; i--) {\n output.push(array[i]);\n }\n return output;\n}\n\nfunction reverseArrayInPlace(array) {\n for (let i = 0; i < Math.floor(array.length / 2); i++) {\n let old = array[i];\n array[i] = array[array.length - 1 - i];\n array[array.length - 1 - i] = old;\n }\n return array;\n}\n\nconsole.log(reverseArray([\"A\", \"B\", \"C\"]));\n// → [\"C\", \"B\", \"A\"];\nlet arrayValue = [1, 2, 3, 4, 5];\nreverseArrayInPlace(arrayValue);\nconsole.log(arrayValue);\n// → [5, 4, 3, 2, 1]" - }, - { - "name": "A list", - "file": "code/solutions/04_3_a_list.js", - "number": 3, - "type": "js", - "code": "// Your code here.\n\nconsole.log(arrayToList([10, 20]));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(listToArray(arrayToList([10, 20, 30])));\n// → [10, 20, 30]\nconsole.log(prepend(10, prepend(20, null)));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(nth(arrayToList([10, 20, 30]), 1));\n// → 20", - "solution": "function arrayToList(array) {\n let list = null;\n for (let i = array.length - 1; i >= 0; i--) {\n list = {value: array[i], rest: list};\n }\n return list;\n}\n\nfunction listToArray(list) {\n let array = [];\n for (let node = list; node; node = node.rest) {\n array.push(node.value);\n }\n return array;\n}\n\nfunction prepend(value, list) {\n return {value, rest: list};\n}\n\nfunction nth(list, n) {\n if (!list) return undefined;\n else if (n == 0) return list.value;\n else return nth(list.rest, n - 1);\n}\n\nconsole.log(arrayToList([10, 20]));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(listToArray(arrayToList([10, 20, 30])));\n// → [10, 20, 30]\nconsole.log(prepend(10, prepend(20, null)));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(nth(arrayToList([10, 20, 30]), 1));\n// → 20" - }, - { - "name": "Deep comparison", - "file": "code/solutions/04_4_deep_comparison.js", - "number": 4, - "type": "js", - "code": "// Your code here.\n\nlet obj = {here: {is: \"an\"}, object: 2};\nconsole.log(deepEqual(obj, obj));\n// → true\nconsole.log(deepEqual(obj, {here: 1, object: 2}));\n// → false\nconsole.log(deepEqual(obj, {here: {is: \"an\"}, object: 2}));\n// → true", - "solution": "function deepEqual(a, b) {\n if (a === b) return true;\n \n if (a == null || typeof a != \"object\" ||\n b == null || typeof b != \"object\") return false;\n\n let keysA = Object.keys(a), keysB = Object.keys(b);\n\n if (keysA.length != keysB.length) return false;\n\n for (let key of keysA) {\n if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;\n }\n\n return true;\n}\n\nlet obj = {here: {is: \"an\"}, object: 2};\nconsole.log(deepEqual(obj, obj));\n// → true\nconsole.log(deepEqual(obj, {here: 1, object: 2}));\n// → false\nconsole.log(deepEqual(obj, {here: {is: \"an\"}, object: 2}));\n// → true" - } - ], - "include": [ - "code/journal.js", - "code/chapter/04_data.js" - ] - }, - { - "number": 5, - "id": "05_higher_order", - "title": "Higher-Order Functions", - "start_code": "function textScripts(text) {\n let scripts = countBy(text, char => {\n let script = characterScript(char.codePointAt(0));\n return script ? script.name : \"none\";\n }).filter(({name}) => name != \"none\");\n\n let total = scripts.reduce((n, {count}) => n + count, 0);\n if (total == 0) return \"No scripts found\";\n\n return scripts.map(({name, count}) => {\n return `${Math.round(count * 100 / total)}% ${name}`;\n }).join(\", \");\n}\n\nconsole.log(textScripts('英国的狗说\"woof\", 俄罗斯的狗说\"тяв\"'));\n", - "exercises": [ - { - "name": "Flattening", - "file": "code/solutions/05_1_flattening.js", - "number": 1, - "type": "js", - "code": "let arrays = [[1, 2, 3], [4, 5], [6]];\n// Your code here.\n// → [1, 2, 3, 4, 5, 6]", - "solution": "let arrays = [[1, 2, 3], [4, 5], [6]];\n\nconsole.log(arrays.reduce((flat, current) => flat.concat(current), []));\n// → [1, 2, 3, 4, 5, 6]" - }, - { - "name": "Your own loop", - "file": "code/solutions/05_2_your_own_loop.js", - "number": 2, - "type": "js", - "code": "// Your code here.\n\nloop(3, n => n > 0, n => n - 1, console.log);\n// → 3\n// → 2\n// → 1", - "solution": "function loop(start, test, update, body) {\n for (let value = start; test(value); value = update(value)) {\n body(value);\n }\n}\n\nloop(3, n => n > 0, n => n - 1, console.log);\n// → 3\n// → 2\n// → 1" - }, - { - "name": "Everything", - "file": "code/solutions/05_3_everything.js", - "number": 3, - "type": "js", - "code": "function every(array, test) {\n // Your code here.\n}\n\nconsole.log(every([1, 3, 5], n => n < 10));\n// → true\nconsole.log(every([2, 4, 16], n => n < 10));\n// → false\nconsole.log(every([], n => n < 10));\n// → true", - "solution": "function every(array, predicate) {\n for (let element of array) {\n if (!predicate(element)) return false;\n }\n return true;\n}\n\nfunction every2(array, predicate) {\n return !array.some(element => !predicate(element));\n}\n\nconsole.log(every([1, 3, 5], n => n < 10));\n// → true\nconsole.log(every([2, 4, 16], n => n < 10));\n// → false\nconsole.log(every([], n => n < 10));\n// → true" - }, - { - "name": "Dominant writing direction", - "file": "code/solutions/05_4_dominant_writing_direction.js", - "number": 4, - "type": "js", - "code": "function dominantDirection(text) {\n // Your code here.\n}\n\nconsole.log(dominantDirection(\"Hello!\"));\n// → ltr\nconsole.log(dominantDirection(\"Hey, مساء الخير\"));\n// → rtl", - "solution": "function dominantDirection(text) {\n let counted = countBy(text, char => {\n let script = characterScript(char.codePointAt(0));\n return script ? script.direction : \"none\";\n }).filter(({name}) => name != \"none\");\n\n if (counted.length == 0) return \"ltr\";\n\n return counted.reduce((a, b) => a.count > b.count ? a : b).name;\n}\n\nconsole.log(dominantDirection(\"Hello!\"));\n// → ltr\nconsole.log(dominantDirection(\"Hey, مساء الخير\"));\n// → rtl" - } - ], - "include": [ - "code/scripts.js", - "code/chapter/05_higher_order.js", - "code/intro.js" - ] - }, - { - "number": 6, - "id": "06_object", - "title": "The Secret Life of Objects", - "start_code": "class Temperature {\n constructor(celsius) {\n this.celsius = celsius;\n }\n get fahrenheit() {\n return this.celsius * 1.8 + 32;\n }\n set fahrenheit(value) {\n this.celsius = (value - 32) / 1.8;\n }\n\n static fromFahrenheit(value) {\n return new Temperature((value - 32) / 1.8);\n }\n}\n\nlet temp = new Temperature(22);\nconsole.log(temp.fahrenheit);\ntemp.fahrenheit = 86;\nconsole.log(temp.celsius);\n", - "exercises": [ - { - "name": "A vector type", - "file": "code/solutions/06_1_a_vector_type.js", - "number": 1, - "type": "js", - "code": "// Your code here.\n\nconsole.log(new Vec(1, 2).plus(new Vec(2, 3)));\n// → Vec{x: 3, y: 5}\nconsole.log(new Vec(1, 2).minus(new Vec(2, 3)));\n// → Vec{x: -1, y: -1}\nconsole.log(new Vec(3, 4).length);\n// → 5", - "solution": "class Vec {\n constructor(x, y) {\n this.x = x;\n this.y = y;\n }\n\n plus(other) {\n return new Vec(this.x + other.x, this.y + other.y);\n }\n\n minus(other) {\n return new Vec(this.x - other.x, this.y - other.y);\n }\n\n get length() {\n return Math.sqrt(this.x * this.x + this.y * this.y);\n }\n}\n\nconsole.log(new Vec(1, 2).plus(new Vec(2, 3)));\n// → Vec{x: 3, y: 5}\nconsole.log(new Vec(1, 2).minus(new Vec(2, 3)));\n// → Vec{x: -1, y: -1}\nconsole.log(new Vec(3, 4).length);\n// → 5" - }, - { - "name": "Groups", - "file": "code/solutions/06_2_groups.js", - "number": 2, - "type": "js", - "code": "class Group {\n // Your code here.\n}\n\nlet group = Group.from([10, 20]);\nconsole.log(group.has(10));\n// → true\nconsole.log(group.has(30));\n// → false\ngroup.add(10);\ngroup.delete(10);\nconsole.log(group.has(10));\n// → false", - "solution": "class Group {\n constructor() {\n this.members = [];\n }\n\n add(value) {\n if (!this.has(value)) {\n this.members.push(value);\n }\n }\n\n delete(value) {\n this.members = this.members.filter(v => v !== value);\n }\n\n has(value) {\n return this.members.includes(value);\n }\n\n static from(collection) {\n let group = new Group;\n for (let value of collection) {\n group.add(value);\n }\n return group;\n }\n}\n\nlet group = Group.from([10, 20]);\nconsole.log(group.has(10));\n// → true\nconsole.log(group.has(30));\n// → false\ngroup.add(10);\ngroup.delete(10);\nconsole.log(group.has(10));" - }, - { - "name": "Iterable groups", - "file": "code/solutions/06_3_iterable_groups.js", - "number": 3, - "type": "js", - "code": "// Your code here (and the code from the previous exercise)\n\nfor (let value of Group.from([\"a\", \"b\", \"c\"])) {\n console.log(value);\n}\n// → a\n// → b\n// → c", - "solution": "class Group {\n constructor() {\n this.members = [];\n }\n\n add(value) {\n if (!this.has(value)) {\n this.members.push(value);\n }\n }\n\n delete(value) {\n this.members = this.members.filter(v => v !== value);\n }\n\n has(value) {\n return this.members.includes(value);\n }\n\n static from(collection) {\n let group = new Group;\n for (let value of collection) {\n group.add(value);\n }\n return group;\n }\n\n [Symbol.iterator]() {\n return new GroupIterator(this);\n }\n}\n\nclass GroupIterator {\n constructor(group) {\n this.group = group;\n this.position = 0;\n }\n\n next() {\n if (this.position >= this.group.members.length) {\n return {done: true};\n } else {\n let result = {value: this.group.members[this.position],\n done: false};\n this.position++;\n return result;\n }\n }\n}\n\nfor (let value of Group.from([\"a\", \"b\", \"c\"])) {\n console.log(value);\n}\n// → a\n// → b\n// → c" - }, - { - "name": "Borrowing a method", - "file": "code/solutions/06_4_borrowing_a_method.js", - "number": 4, - "type": "js", - "code": "let map = {one: true, two: true, hasOwnProperty: true};\n\n// Fix this call\nconsole.log(map.hasOwnProperty(\"one\"));\n// → true", - "solution": "let map = {one: true, two: true, hasOwnProperty: true};\n\nconsole.log(Object.prototype.hasOwnProperty.call(map, \"one\"));\n// → true" - } - ], - "include": [ - "code/chapter/06_object.js" - ] - }, - { - "number": 7, - "id": "07_robot", - "title": "Project: A Robot", - "start_code": "runRobotAnimation(VillageState.random(),\n goalOrientedRobot, []);\n", - "exercises": [ - { - "name": "Measuring a robot", - "file": "code/solutions/07_1_measuring_a_robot.js", - "number": 1, - "type": "js", - "code": "function compareRobots(robot1, memory1, robot2, memory2) {\n // Your code here\n}\n\ncompareRobots(routeRobot, [], goalOrientedRobot, []);", - "solution": "function countSteps(state, robot, memory) {\n for (let steps = 0;; steps++) {\n if (state.parcels.length == 0) return steps;\n let action = robot(state, memory);\n state = state.move(action.direction);\n memory = action.memory;\n }\n}\n\nfunction compareRobots(robot1, memory1, robot2, memory2) {\n let total1 = 0, total2 = 0;\n for (let i = 0; i < 100; i++) {\n let state = VillageState.random();\n total1 += countSteps(state, robot1, memory1);\n total2 += countSteps(state, robot2, memory2);\n }\n console.log(`Robot 1 needed ${total1 / 100} steps per task`)\n console.log(`Robot 2 needed ${total2 / 100}`)\n}\n\ncompareRobots(routeRobot, [], goalOrientedRobot, []);" - }, - { - "name": "Robot efficiency", - "file": "code/solutions/07_2_robot_efficiency.js", - "number": 2, - "type": "js", - "code": "// Your code here\n\nrunRobotAnimation(VillageState.random(), yourRobot, memory);", - "solution": "function lazyRobot({place, parcels}, route) {\n if (route.length == 0) {\n // Describe a route for every parcel\n let routes = parcels.map(parcel => {\n if (parcel.place != place) {\n return {route: findRoute(roadGraph, place, parcel.place),\n pickUp: true};\n } else {\n return {route: findRoute(roadGraph, place, parcel.address),\n pickUp: false};\n }\n });\n\n // This determines the precedence a route gets when choosing.\n // Route length counts negatively, routes that pick up a package\n // get a small bonus.\n function score({route, pickUp}) {\n return (pickUp ? 0.5 : 0) - route.length;\n }\n route = routes.reduce((a, b) => score(a) > score(b) ? a : b).route;\n }\n\n return {direction: route[0], memory: route.slice(1)};\n}\n\nrunRobotAnimation(VillageState.random(), lazyRobot, []);" - }, - { - "name": "Persistent group", - "file": "code/solutions/07_3_persistent_group.js", - "number": 3, - "type": "js", - "code": "class PGroup {\n // Your code here\n}\n\nlet a = PGroup.empty.add(\"a\");\nlet ab = a.add(\"b\");\nlet b = ab.delete(\"a\");\n\nconsole.log(b.has(\"b\"));\n// → true\nconsole.log(a.has(\"b\"));\n// → false\nconsole.log(b.has(\"a\"));\n// → false", - "solution": "class PGroup {\n constructor(members) {\n this.members = members;\n }\n\n add(value) {\n if (this.has(value)) return this;\n return new PGroup(this.members.concat([value]));\n }\n\n delete(value) {\n if (!this.has(value)) return this;\n return new PGroup(this.members.filter(m => m !== value));\n }\n\n has(value) {\n return this.members.includes(value);\n }\n}\n\nPGroup.empty = new PGroup([]);\n\nlet a = PGroup.empty.add(\"a\");\nlet ab = a.add(\"b\");\nlet b = ab.delete(\"a\");\n\nconsole.log(b.has(\"b\"));\n// → true\nconsole.log(a.has(\"b\"));\n// → false\nconsole.log(b.has(\"a\"));\n// → false" - } - ], - "include": [ - "code/chapter/07_robot.js", - "code/animatevillage.js" - ] - }, - { - "number": 8, - "id": "08_error", - "title": "Bugs and Errors", - "start_code": "", - "exercises": [ - { - "name": "Retry", - "file": "code/solutions/08_1_retry.js", - "number": 1, - "type": "js", - "code": "class MultiplicatorUnitFailure extends Error {}\n\nfunction primitiveMultiply(a, b) {\n if (Math.random() < 0.2) {\n return a * b;\n } else {\n throw new MultiplicatorUnitFailure(\"Klunk\");\n }\n}\n\nfunction reliableMultiply(a, b) {\n // Your code here.\n}\n\nconsole.log(reliableMultiply(8, 8));\n// → 64", - "solution": "class MultiplicatorUnitFailure extends Error {}\n\nfunction primitiveMultiply(a, b) {\n if (Math.random() < 0.2) {\n return a * b;\n } else {\n throw new MultiplicatorUnitFailure(\"Klunk\");\n }\n}\n\nfunction reliableMultiply(a, b) {\n for (;;) {\n try {\n return primitiveMultiply(a, b);\n } catch (e) {\n if (!(e instanceof MultiplicatorUnitFailure))\n throw e;\n }\n }\n}\n\nconsole.log(reliableMultiply(8, 8));\n// → 64" - }, - { - "name": "The locked box", - "file": "code/solutions/08_2_the_locked_box.js", - "number": 2, - "type": "js", - "code": "const box = {\n locked: true,\n unlock() { this.locked = false; },\n lock() { this.locked = true; },\n _content: [],\n get content() {\n if (this.locked) throw new Error(\"Locked!\");\n return this._content;\n }\n};\n\nfunction withBoxUnlocked(body) {\n // Your code here.\n}\n\nwithBoxUnlocked(function() {\n box.content.push(\"gold piece\");\n});\n\ntry {\n withBoxUnlocked(function() {\n throw new Error(\"Pirates on the horizon! Abort!\");\n });\n} catch (e) {\n console.log(\"Error raised: \" + e);\n}\nconsole.log(box.locked);\n// → true", - "solution": "const box = {\n locked: true,\n unlock() { this.locked = false; },\n lock() { this.locked = true; },\n _content: [],\n get content() {\n if (this.locked) throw new Error(\"Locked!\");\n return this._content;\n }\n};\n\nfunction withBoxUnlocked(body) {\n let locked = box.locked;\n if (!locked) {\n return body();\n }\n\n box.unlock();\n try {\n return body();\n } finally {\n box.lock();\n }\n}\n\nwithBoxUnlocked(function() {\n box.content.push(\"gold piece\");\n});\n\ntry {\n withBoxUnlocked(function() {\n throw new Error(\"Pirates on the horizon! Abort!\");\n });\n} catch (e) {\n console.log(\"Error raised:\", e);\n}\n\nconsole.log(box.locked);\n// → true" - } - ], - "include": [ - "code/chapter/08_error.js" - ] - }, - { - "number": 9, - "id": "09_regexp", - "title": "Regular Expressions", - "start_code": "function parseINI(string) {\n // Start with an object to hold the top-level fields\n let result = {};\n let section = result;\n string.split(/\\r?\\n/).forEach(line => {\n let match;\n if (match = line.match(/^(\\w+)=(.*)$/)) {\n section[match[1]] = match[2];\n } else if (match = line.match(/^\\[(.*)\\]$/)) {\n section = result[match[1]] = {};\n } else if (!/^\\s*(;.*)?$/.test(line)) {\n throw new Error(\"Line '\" + line + \"' is not valid.\");\n }\n });\n return result;\n}\n\nconsole.log(parseINI(`\nname=Vasilis\n[address]\ncity=Tessaloniki`));\n", - "exercises": [ - { - "name": "Regexp golf", - "file": "code/solutions/09_1_regexp_golf.js", - "number": 1, - "type": "js", - "code": "// Fill in the regular expressions\n\nverify(/.../,\n [\"my car\", \"bad cats\"],\n [\"camper\", \"high art\"]);\n\nverify(/.../,\n [\"pop culture\", \"mad props\"],\n [\"plop\", \"prrrop\"]);\n\nverify(/.../,\n [\"ferret\", \"ferry\", \"ferrari\"],\n [\"ferrum\", \"transfer A\"]);\n\nverify(/.../,\n [\"how delicious\", \"spacious room\"],\n [\"ruinous\", \"consciousness\"]);\n\nverify(/.../,\n [\"bad punctuation .\"],\n [\"escape the period\"]);\n\nverify(/.../,\n [\"hottentottententen\"],\n [\"no\", \"hotten totten tenten\"]);\n\nverify(/.../,\n [\"red platypus\", \"wobbling nest\"],\n [\"earth bed\", \"learning ape\", \"BEET\"]);\n\n\nfunction verify(regexp, yes, no) {\n // Ignore unfinished exercises\n if (regexp.source == \"...\") return;\n for (let str of yes) if (!regexp.test(str)) {\n console.log(`Failure to match '${str}'`);\n }\n for (let str of no) if (regexp.test(str)) {\n console.log(`Unexpected match for '${str}'`);\n }\n}", - "solution": "// Fill in the regular expressions\n\nverify(/ca[rt]/,\n [\"my car\", \"bad cats\"],\n [\"camper\", \"high art\"]);\n\nverify(/pr?op/,\n [\"pop culture\", \"mad props\"],\n [\"plop\", \"prrrop\"]);\n\nverify(/ferr(et|y|ari)/,\n [\"ferret\", \"ferry\", \"ferrari\"],\n [\"ferrum\", \"transfer A\"]);\n\nverify(/ious\\b/,\n [\"how delicious\", \"spacious room\"],\n [\"ruinous\", \"consciousness\"]);\n\nverify(/\\s[.,:;]/,\n [\"bad punctuation .\"],\n [\"escape the dot\"]);\n\nverify(/\\w{7}/,\n [\"hottentottententen\"],\n [\"no\", \"hotten totten tenten\"]);\n\nverify(/\\b[^\\We]+\\b/i,\n [\"red platypus\", \"wobbling nest\"],\n [\"earth bed\", \"learning ape\", \"BEET\"]);\n\n\nfunction verify(regexp, yes, no) {\n // Ignore unfinished exercises\n if (regexp.source == \"...\") return;\n for (let str of yes) if (!regexp.test(str)) {\n console.log(`Failure to match '${str}'`);\n }\n for (let str of no) if (regexp.test(str)) {\n console.log(`Unexpected match for '${str}'`);\n }\n}" - }, - { - "name": "Quoting style", - "file": "code/solutions/09_2_quoting_style.js", - "number": 2, - "type": "js", - "code": "let text = \"'I'm the cook,' he said, 'it's my job.'\";\n// Change this call.\nconsole.log(text.replace(/A/g, \"B\"));\n// → \"I'm the cook,\" he said, \"it's my job.\"", - "solution": "let text = \"'I'm the cook,' he said, 'it's my job.'\";\n\nconsole.log(text.replace(/(^|\\W)'|'(\\W|$)/g, '$1\"$2'));\n// → \"I'm the cook,\" he said, \"it's my job.\"" - }, - { - "name": "Numbers again", - "file": "code/solutions/09_3_numbers_again.js", - "number": 3, - "type": "js", - "code": "// Fill in this regular expression.\nlet number = /^...$/;\n\n// Tests:\nfor (let str of [\"1\", \"-1\", \"+15\", \"1.55\", \".5\", \"5.\",\n \"1.3e2\", \"1E-4\", \"1e+12\"]) {\n if (!number.test(str)) {\n console.log(`Failed to match '${str}'`);\n }\n}\nfor (let str of [\"1a\", \"+-1\", \"1.2.3\", \"1+1\", \"1e4.5\",\n \".5.\", \"1f5\", \".\"]) {\n if (number.test(str)) {\n console.log(`Incorrectly accepted '${str}'`);\n }\n}", - "solution": "// Fill in this regular expression.\nlet number = /^[+\\-]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][+\\-]?\\d+)?$/;\n\n// Tests:\nfor (let str of [\"1\", \"-1\", \"+15\", \"1.55\", \".5\", \"5.\",\n \"1.3e2\", \"1E-4\", \"1e+12\"]) {\n if (!number.test(str)) {\n console.log(`Failed to match '${str}'`);\n }\n}\nfor (let str of [\"1a\", \"+-1\", \"1.2.3\", \"1+1\", \"1e4.5\",\n \".5.\", \"1f5\", \".\"]) {\n if (number.test(str)) {\n console.log(`Incorrectly accepted '${str}'`);\n }\n}" - } - ], - "include": null - }, - { - "number": 10, - "id": "10_modules", - "title": "Modules", - "start_code": "", - "exercises": [ - { - "name": "Roads module", - "file": "code/solutions/10_2_roads_module.js", - "number": 2, - "type": "js", - "code": "// Add dependencies and exports\n\nconst roads = [\n \"Alice's House-Bob's House\", \"Alice's House-Cabin\",\n \"Alice's House-Post Office\", \"Bob's House-Town Hall\",\n \"Daria's House-Ernie's House\", \"Daria's House-Town Hall\",\n \"Ernie's House-Grete's House\", \"Grete's House-Farm\",\n \"Grete's House-Shop\", \"Marketplace-Farm\",\n \"Marketplace-Post Office\", \"Marketplace-Shop\",\n \"Marketplace-Town Hall\", \"Shop-Town Hall\"\n];", - "solution": "const {buildGraph} = require(\"./graph\");\n\nconst roads = [\n \"Alice's House-Bob's House\", \"Alice's House-Cabin\",\n \"Alice's House-Post Office\", \"Bob's House-Town Hall\",\n \"Daria's House-Ernie's House\", \"Daria's House-Town Hall\",\n \"Ernie's House-Grete's House\", \"Grete's House-Farm\",\n \"Grete's House-Shop\", \"Marketplace-Farm\",\n \"Marketplace-Post Office\", \"Marketplace-Shop\",\n \"Marketplace-Town Hall\", \"Shop-Town Hall\"\n];\n\nexports.roadGraph = buildGraph(roads.map(r => r.split(\"-\")));" - } - ], - "include": [ - "code/packages_chapter_10.js", - "code/chapter/07_robot.js" - ] - }, - { - "number": 11, - "id": "11_async", - "title": "Asynchronous Programming", - "start_code": "findInStorage(bigOak, \"events on 2017-12-21\")\n .then(console.log);\n", - "exercises": [ - { - "name": "Tracking the scalpel", - "file": "code/solutions/11_1_tracking_the_scalpel.js", - "number": 1, - "type": "js", - "code": "async function locateScalpel(nest) {\n // Your code here.\n}\n\nfunction locateScalpel2(nest) {\n // Your code here.\n}\n\nlocateScalpel(bigOak).then(console.log);\n// → Butcher Shop", - "solution": "async function locateScalpel(nest) {\n let current = nest.name;\n for (;;) {\n let next = await anyStorage(nest, current, \"scalpel\");\n if (next == current) return current;\n current = next;\n }\n}\n\nfunction locateScalpel2(nest) {\n function loop(current) {\n return anyStorage(nest, current, \"scalpel\").then(next => {\n if (next == current) return current;\n else return loop(next);\n });\n }\n return loop(nest.name);\n}\n\nlocateScalpel(bigOak).then(console.log);\n// → Butcher's Shop\nlocateScalpel2(bigOak).then(console.log);\n// → Butcher's Shop" - }, - { - "name": "Building Promise.all", - "file": "code/solutions/11_2_building_promiseall.js", - "number": 2, - "type": "js", - "code": "function Promise_all(promises) {\n return new Promise((resolve, reject) => {\n // Your code here.\n });\n}\n\n// Test code.\nPromise_all([]).then(array => {\n console.log(\"This should be []:\", array);\n});\nfunction soon(val) {\n return new Promise(resolve => {\n setTimeout(() => resolve(val), Math.random() * 500);\n });\n}\nPromise_all([soon(1), soon(2), soon(3)]).then(array => {\n console.log(\"This should be [1, 2, 3]:\", array);\n});\nPromise_all([soon(1), Promise.reject(\"X\"), soon(3)])\n .then(array => {\n console.log(\"We should not get here\");\n })\n .catch(error => {\n if (error != \"X\") {\n console.log(\"Unexpected failure:\", error);\n }\n });", - "solution": "function Promise_all(promises) {\n return new Promise((resolve, reject) => {\n let results = [];\n let pending = promises.length;\n for (let i = 0; i < promises.length; i++) {\n promises[i].then(result => {\n results[i] = result;\n pending--;\n if (pending == 0) resolve(results);\n }).catch(reject);\n }\n if (promises.length == 0) resolve(results);\n });\n}\n\n// Test code.\nPromise_all([]).then(array => {\n console.log(\"This should be []:\", array);\n});\nfunction soon(val) {\n return new Promise(resolve => {\n setTimeout(() => resolve(val), Math.random() * 500);\n });\n}\nPromise_all([soon(1), soon(2), soon(3)]).then(array => {\n console.log(\"This should be [1, 2, 3]:\", array);\n});\nPromise_all([soon(1), Promise.reject(\"X\"), soon(3)]).then(array => {\n console.log(\"We should not get here\");\n}).catch(error => {\n if (error != \"X\") {\n console.log(\"Unexpected failure:\", error);\n }\n});" - } - ], - "include": [ - "code/crow-tech.js", - "code/chapter/11_async.js" - ] - }, - { - "number": 12, - "id": "12_language", - "title": "Project: A Programming Language", - "start_code": "run(`\ndo(define(plusOne, fun(a, +(a, 1))),\n print(plusOne(10)))\n`);\n\nrun(`\ndo(define(pow, fun(base, exp,\n if(==(exp, 0),\n 1,\n *(base, pow(base, -(exp, 1)))))),\n print(pow(2, 10)))\n`);\n", - "exercises": [ - { - "name": "Arrays", - "file": "code/solutions/12_1_arrays.js", - "number": 1, - "type": "js", - "code": "// Modify these definitions...\n\ntopScope.array = \"...\";\n\ntopScope.length = \"...\";\n\ntopScope.element = \"...\";\n\nrun(`\ndo(define(sum, fun(array,\n do(define(i, 0),\n define(sum, 0),\n while(<(i, length(array)),\n do(define(sum, +(sum, element(array, i))),\n define(i, +(i, 1)))),\n sum))),\n print(sum(array(1, 2, 3))))\n`);\n// → 6", - "solution": "topScope.array = (...values) => values;\n\ntopScope.length = array => array.length;\n\ntopScope.element = (array, i) => array[i];\n\nrun(`\ndo(define(sum, fun(array,\n do(define(i, 0),\n define(sum, 0),\n while(<(i, length(array)),\n do(define(sum, +(sum, element(array, i))),\n define(i, +(i, 1)))),\n sum))),\n print(sum(array(1, 2, 3))))\n`);\n// → 6" - }, - { - "name": "Comments", - "file": "code/solutions/12_3_comments.js", - "number": 3, - "type": "js", - "code": "// This is the old skipSpace. Modify it...\nfunction skipSpace(string) {\n let first = string.search(/\\S/);\n if (first == -1) return \"\";\n return string.slice(first);\n}\n\nconsole.log(parse(\"# hello\\nx\"));\n// → {type: \"word\", name: \"x\"}\n\nconsole.log(parse(\"a # one\\n # two\\n()\"));\n// → {type: \"apply\",\n// operator: {type: \"word\", name: \"a\"},\n// args: []}", - "solution": "function skipSpace(string) {\n let skippable = string.match(/^(\\s|#.*)*/);\n return string.slice(skippable[0].length);\n}\n\nconsole.log(parse(\"# hello\\nx\"));\n// → {type: \"word\", name: \"x\"}\n\nconsole.log(parse(\"a # one\\n # two\\n()\"));\n// → {type: \"apply\",\n// operator: {type: \"word\", name: \"a\"},\n// args: []}" - }, - { - "name": "Fixing scope", - "file": "code/solutions/12_4_fixing_scope.js", - "number": 4, - "type": "js", - "code": "specialForms.set = (args, scope) => {\n // Your code here.\n};\n\nrun(`\ndo(define(x, 4),\n define(setx, fun(val, set(x, val))),\n setx(50),\n print(x))\n`);\n// → 50\nrun(`set(quux, true)`);\n// → Some kind of ReferenceError", - "solution": "specialForms.set = (args, env) => {\n if (args.length != 2 || args[0].type != \"word\") {\n throw new SyntaxError(\"Bad use of set\");\n }\n let varName = args[0].name;\n let value = evaluate(args[1], env);\n\n for (let scope = env; scope; scope = Object.getPrototypeOf(scope)) {\n if (Object.prototype.hasOwnProperty.call(scope, varName)) {\n scope[varName] = value;\n return value;\n }\n }\n throw new ReferenceError(`Setting undefined variable ${varName}`);\n};\n\nrun(`\ndo(define(x, 4),\n define(setx, fun(val, set(x, val))),\n setx(50),\n print(x))\n`);\n// → 50\nrun(`set(quux, true)`);\n// → Some kind of ReferenceError" - } - ], - "include": [ - "code/chapter/12_language.js" - ] - }, - { - "number": 13, - "id": "13_browser", - "title": "JavaScript and the Browser", - "start_code": "", - "exercises": [], - "include": null - }, - { - "number": 14, - "id": "14_dom", - "title": "The Document Object Model", - "start_code": "\n\n

\n \n

\n\n", - "exercises": [ - { - "name": "Build a table", - "file": "code/solutions/14_1_build_a_table.html", - "number": 1, - "type": "html", - "code": "\n\n

Mountains

\n\n
\n\n", - "solution": "\n\n\n\n

Mountains

\n\n
\n\n" - }, - { - "name": "Elements by tag name", - "file": "code/solutions/14_2_elements_by_tag_name.html", - "number": 2, - "type": "html", - "code": "\n\n

Heading with a span element.

\n

A paragraph with one, two\n spans.

\n\n", - "solution": "\n\n

Heading with a span element.

\n

A paragraph with one, two\n spans.

\n\n" - }, - { - "name": "The cat's hat", - "file": "code/solutions/14_3_the_cats_hat.html", - "number": 3, - "type": "html", - "code": "\n\n\n\n\n\n", - "solution": "\n\n\n\n\n\n\n\n\n\n" - } - ], - "include": null - }, - { - "number": 15, - "id": "15_event", - "title": "Handling Events", - "start_code": "\n\n

Drag the bar to change its width:

\n
\n
\n\n", - "exercises": [ - { - "name": "Balloon", - "file": "code/solutions/15_1_balloon.html", - "number": 1, - "type": "html", - "code": "\n\n

🎈

\n\n", - "solution": "\n\n

🎈

\n\n" - }, - { - "name": "Mouse trail", - "file": "code/solutions/15_2_mouse_trail.html", - "number": 2, - "type": "html", - "code": "\n\n\n\n", - "solution": "\n\n\n\n\n\n" - }, - { - "name": "Tabs", - "file": "code/solutions/15_3_tabs.html", - "number": 3, - "type": "html", - "code": "\n\n\n
Tab one
\n
Tab two
\n
Tab three
\n
\n", - "solution": "\n\n\n
Tab one
\n
Tab two
\n
Tab three
\n
\n" - } - ], - "include": null - }, - { - "number": 16, - "id": "16_game", - "title": "Project: A Platform Game", - "start_code": "\n\n\n\n\n\n\n \n\n", - "exercises": [ - { - "name": "Game over", - "file": "code/solutions/16_1_game_over.html", - "number": 1, - "type": "html", - "code": "\n\n\n\n\n\n\n\n", - "solution": "\n\n\n\n\n\n\n\n" - }, - { - "name": "Pausing the game", - "file": "code/solutions/16_2_pausing_the_game.html", - "number": 2, - "type": "html", - "code": "\n\n\n\n\n\n\n\n", - "solution": "\n\n\n\n\n\n\n\n" - }, - { - "name": "A monster", - "file": "code/solutions/16_3_a_monster.html", - "number": 3, - "type": "html", - "code": "\n\n\n\n\n\n\n\n \n", - "solution": "\n\n\n\n\n\n\n\n\n \n" - } - ], - "include": [ - "code/chapter/16_game.js", - "code/levels.js" - ] - }, - { - "number": 17, - "id": "17_canvas", - "title": "Drawing on Canvas", - "start_code": "\n\n\n\n\n\n \n\n", - "exercises": [ - { - "name": "Shapes", - "file": "code/solutions/17_1_shapes.html", - "number": 1, - "type": "html", - "code": "\n\n\n\n\n\n", - "solution": "\n\n\n\n\n\n" - }, - { - "name": "The pie chart", - "file": "code/solutions/17_2_the_pie_chart.html", - "number": 2, - "type": "html", - "code": "\n\n\n\n\n\n", - "solution": "\n\n\n\n\n\n" - }, - { - "name": "A bouncing ball", - "file": "code/solutions/17_3_a_bouncing_ball.html", - "number": 3, - "type": "html", - "code": "\n\n\n\n\n\n", - "solution": "\n\n\n\n\n\n" - } - ], - "include": [ - "code/chapter/16_game.js", - "code/levels.js", - "code/chapter/17_canvas.js" - ] - }, - { - "number": 18, - "id": "18_http", - "title": "HTTP and Forms", - "start_code": "\n\n\nNotes:
\n\n\n\n", - "exercises": [ - { - "name": "Content negotiation", - "file": "code/solutions/18_1_content_negotiation.js", - "number": 1, - "type": "js", - "code": "// Your code here.", - "solution": "const url = \"https://eloquentjavascript.net/author\";\nconst types = [\"text/plain\",\n \"text/html\",\n \"application/json\",\n \"application/rainbows+unicorns\"];\n\nasync function showTypes() {\n for (let type of types) {\n let resp = await fetch(url, {headers: {accept: type}});\n console.log(`${type}: ${await resp.text()}\\n`);\n }\n}\n\nshowTypes();" - }, - { - "name": "A JavaScript workbench", - "file": "code/solutions/18_2_a_javascript_workbench.html", - "number": 2, - "type": "html", - "code": "\n\n\n\n\n
\n\n",
-        "solution": "\n\n\n\n\n
\n\n"
-      },
-      {
-        "name": "Conway's Game of Life",
-        "file": "code/solutions/18_3_conways_game_of_life.html",
-        "number": 3,
-        "type": "html",
-        "code": "\n\n\n
\n\n\n", - "solution": "\n\n\n
\n\n\n\n" - } - ], - "include": [ - "code/chapter/18_http.js" - ] - }, - { - "number": 19, - "id": "19_paint", - "title": "Project: A Pixel Art Editor", - "start_code": "\n\n\n
\n\n", - "exercises": [ - { - "name": "Keyboard bindings", - "file": "code/solutions/19_1_keyboard_bindings.html", - "number": 1, - "type": "html", - "code": "\n\n\n
\n", - "solution": "\n\n\n
\n" - }, - { - "name": "Efficient drawing", - "file": "code/solutions/19_2_efficient_drawing.html", - "number": 2, - "type": "html", - "code": "\n\n\n
\n", - "solution": "\n\n\n
\n" - }, - { - "name": "Circles", - "file": "code/solutions/19_3_circles.html", - "number": 3, - "type": "html", - "code": "\n\n\n
\n", - "solution": "\n\n\n
\n" - }, - { - "name": "Proper lines", - "file": "code/solutions/19_4_proper_lines.html", - "number": 4, - "type": "html", - "code": "\n\n\n
\n", - "solution": "\n\n\n
\n" - } - ], - "include": [ - "code/chapter/19_paint.js" - ] - }, - { - "number": 20, - "id": "20_node", - "title": "Node.js", - "start_code": "", - "exercises": [ - { - "name": "Search tool", - "file": "code/solutions/20_1_search_tool.js", - "number": 1, - "type": "js", - "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", - "solution": "const {statSync, readdirSync, readFileSync} = require(\"fs\");\n\nlet searchTerm = new RegExp(process.argv[2]);\n\nfor (let arg of process.argv.slice(3)) {\n search(arg);\n}\n\nfunction search(file) {\n let stats = statSync(file);\n if (stats.isDirectory()) {\n for (let f of readdirSync(file)) {\n search(file + \"/\" + f);\n }\n } else if (searchTerm.test(readFileSync(file, \"utf8\"))) {\n console.log(file);\n }\n}\n" - }, - { - "name": "Directory creation", - "file": "code/solutions/20_2_directory_creation.js", - "number": 2, - "type": "js", - "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", - "solution": "// This code won't work on its own, but is also included in the\n// code/file_server.js file, which defines the whole system.\n\nconst {mkdir} = require(\"fs\").promises;\n\nmethods.MKCOL = async function(request) {\n let path = urlPath(request.url);\n let stats;\n try {\n stats = await stat(path);\n } catch (error) {\n if (error.code != \"ENOENT\") throw error;\n await mkdir(path);\n return {status: 204};\n }\n if (stats.isDirectory()) return {status: 204};\n else return {status: 400, body: \"Not a directory\"};\n};\n" - }, - { - "name": "A public space on the web", - "file": "code/solutions/20_3_a_public_space_on_the_web.zip", - "number": 3, - "type": "js", - "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", - "solution": "// This solutions consists of multiple files. Download it\n// though the link below.\n" - } - ], - "include": null - }, - { - "number": 21, - "id": "21_skillsharing", - "title": "Project: Skill-Sharing Website", - "start_code": "", - "exercises": [ - { - "name": "Disk persistence", - "file": "code/solutions/21_1_disk_persistence.js", - "number": 1, - "type": "js", - "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", - "solution": "// This isn't a stand-alone file, only a redefinition of a few\n// fragments from skillsharing/skillsharing_server.js\n\nconst {readFileSync, writeFile} = require(\"fs\");\n\nconst fileName = \"./talks.json\";\n\nfunction loadTalks() {\n let json;\n try {\n json = JSON.parse(readFileSync(fileName, \"utf8\"));\n } catch (e) {\n json = {};\n }\n return Object.assign(Object.create(null), json);\n}\n\nSkillShareServer.prototype.updated = function() {\n this.version++;\n let response = this.talkResponse();\n this.waiting.forEach(resolve => resolve(response));\n this.waiting = [];\n\n writeFile(fileName, JSON.stringify(this.talks), e => {\n if (e) throw e;\n });\n};\n\n// The line that starts the server must be changed to\nnew SkillShareServer(loadTalks()).start(8000);\n" - }, - { - "name": "Comment field resets", - "file": "code/solutions/21_2_comment_field_resets.js", - "number": 2, - "type": "js", - "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", - "solution": "// This isn't a stand-alone file, only a redefinition of the main\n// component from skillsharing/public/skillsharing_client.js\n\nclass Talk {\n constructor(talk, dispatch) {\n this.comments = elt(\"div\");\n this.dom = elt(\n \"section\", {className: \"talk\"},\n elt(\"h2\", null, talk.title, \" \", elt(\"button\", {\n type: \"button\",\n onclick: () => dispatch({type: \"deleteTalk\",\n talk: talk.title})\n }, \"Delete\")),\n elt(\"div\", null, \"by \",\n elt(\"strong\", null, talk.presenter)),\n elt(\"p\", null, talk.summary),\n this.comments,\n elt(\"form\", {\n onsubmit(event) {\n event.preventDefault();\n let form = event.target;\n dispatch({type: \"newComment\",\n talk: talk.title,\n message: form.elements.comment.value});\n form.reset();\n }\n }, elt(\"input\", {type: \"text\", name: \"comment\"}), \" \",\n elt(\"button\", {type: \"submit\"}, \"Add comment\")));\n this.syncState(talk);\n }\n\n syncState(talk) {\n this.talk = talk;\n this.comments.textContent = \"\";\n for (let comment of talk.comments) {\n this.comments.appendChild(renderComment(comment));\n }\n }\n}\n\nclass SkillShareApp {\n constructor(state, dispatch) {\n this.dispatch = dispatch;\n this.talkDOM = elt(\"div\", {className: \"talks\"});\n this.talkMap = Object.create(null);\n this.dom = elt(\"div\", null,\n renderUserField(state.user, dispatch),\n this.talkDOM,\n renderTalkForm(dispatch));\n this.syncState(state);\n }\n\n syncState(state) {\n if (state.talks == this.talks) return;\n this.talks = state.talks;\n\n for (let talk of state.talks) {\n let cmp = this.talkMap[talk.title];\n if (cmp && cmp.talk.author == talk.author &&\n cmp.talk.summary == talk.summary) {\n cmp.syncState(talk);\n } else {\n if (cmp) cmp.dom.remove();\n cmp = new Talk(talk, this.dispatch);\n this.talkMap[talk.title] = cmp;\n this.talkDOM.appendChild(cmp.dom);\n }\n }\n for (let title of Object.keys(this.talkMap)) {\n if (!state.talks.some(talk => talk.title == title)) {\n this.talkMap[title].dom.remove();\n delete this.talkMap[title];\n }\n }\n }\n}\n" - } - ], - "include": null - }, - { - "title": "JavaScript and Performance", - "number": 22, - "start_code": "\n\n\n", - "include": [ - "code/draw_layout.js", - "code/chapter/22_fast.js" - ], - "exercises": [ - { - "name": "Pathfinding", - "file": "code/solutions/22_1_pathfinding.js", - "number": 1, - "type": "js", - "code": "function findPath(a, b) {\n // Your code here...\n}\n\nlet graph = treeGraph(4, 4);\nlet root = graph[0], leaf = graph[graph.length - 1];\nconsole.log(findPath(root, leaf).length);\n// → 4\n\nleaf.connect(root);\nconsole.log(findPath(root, leaf).length);\n// → 2\n", - "solution": "function findPath(a, b) {\n let work = [[a]];\n for (let path of work) {\n let end = path[path.length - 1];\n if (end == b) return path;\n for (let next of end.edges) {\n if (!work.some(path => path[path.length - 1] == next)) {\n work.push(path.concat([next]));\n }\n }\n }\n}\n\nlet graph = treeGraph(4, 4);\nlet root = graph[0], leaf = graph[graph.length - 1];\nconsole.log(findPath(root, leaf).length);\n// → 4\n\nleaf.connect(root);\nconsole.log(findPath(root, leaf).length);\n// → 2\n" - }, - { - "name": "Timing", - "file": "code/solutions/22_2_timing.js", - "number": 2, - "type": "js", - "code": "", - "solution": "function findPath(a, b) {\n let work = [[a]];\n for (let path of work) {\n let end = path[path.length - 1];\n if (end == b) return path;\n for (let next of end.edges) {\n if (!work.some(path => path[path.length - 1] == next)) {\n work.push(path.concat([next]));\n }\n }\n }\n}\n\nfunction time(findPath) {\n let graph = treeGraph(6, 6);\n let startTime = Date.now();\n let result = findPath(graph[0], graph[graph.length - 1]);\n console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`);\n}\ntime(findPath);\n" - }, - { - "name": "Optimizing", - "file": "code/solutions/22_3_optimizing.js", - "number": 3, - "type": "js", - "code": "", - "solution": "function time(findPath) {\n let graph = treeGraph(6, 6);\n let startTime = Date.now();\n let result = findPath(graph[0], graph[graph.length - 1]);\n console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`);\n}\n\nfunction findPath_set(a, b) {\n let work = [[a]];\n let reached = new Set([a]);\n for (let path of work) {\n let end = path[path.length - 1];\n if (end == b) return path;\n for (let next of end.edges) {\n if (!reached.has(next)) {\n reached.add(next);\n work.push(path.concat([next]));\n }\n }\n }\n}\n\ntime(findPath_set);\n\nfunction pathToArray(path) {\n let result = [];\n for (; path; path = path.via) result.unshift(path.at);\n return result;\n}\n\nfunction findPath_list(a, b) {\n let work = [{at: a, via: null}];\n let reached = new Set([a]);\n for (let path of work) {\n if (path.at == b) return pathToArray(path);\n for (let next of path.at.edges) {\n if (!reached.has(next)) {\n reached.add(next);\n work.push({at: next, via: path});\n }\n }\n }\n}\n\ntime(findPath_list);\n" - } - ] - } -]; diff --git a/docs/js/code.js b/docs/js/code.js deleted file mode 100644 index 93eb6a6be..000000000 --- a/docs/js/code.js +++ /dev/null @@ -1,217 +0,0 @@ -addEventListener("load", () => { - let editor = CodeMirror.fromTextArea(document.querySelector("#editor"), { - mode: "javascript", - extraKeys: { - "Ctrl-Enter": runCode, - "Cmd-Enter": runCode - }, - matchBrackets: true, - lineNumbers: true - }) - function guessType(code) { - return /^[\s\w\n:]* { - clearTimeout(reGuess) - reGuess = setTimeout(() => { - if (context.type == null) { - let mode = guessType(editor.getValue()) == "html" ? "text/html" : "javascript" - if (mode != editor.getOption("mode")) - editor.setOption("mode", mode) - } - }, 500) - }) - - function hasIncludes(code, include) { - if (!include) return code - - let re = /(?:\s|)* + + + + +
+ + +

Introduction

+ +
+ +

Creemos que estamos creando el sistema para nuestros propios propósitos. Creemos que lo estamos haciendo a nuestra imagen... Pero la computadora no es realmente como nosotros. Es una proyección de una parte muy pequeña de nosotros: esa porción dedicada a la lógica, el orden, las reglas y la claridad. quote}}

+ +
Ellen Ullman, Close to the Machine: Technophilia and its Discontents
+ +
Picture of a screwdriver and a circuit board
+ +

This is a book about instructing computers. Computers are about as common as screwdrivers today, but they are quite a bit more complex, and making them do what you want them to do isn’t always easy.

+ +

If the task you have for your computer is a common, well-understood one, such as showing you your email or acting like a calculator, you can open the appropriate application and get to work. But for unique or open-ended tasks, there probably is no application.

+ +

That is where programming may come in. Programming is the act of constructing a program—a set of precise instructions telling a computer what to do. Because computers are dumb, pedantic beasts, programming is fundamentally tedious and frustrating.

+ +

Fortunately, if you can get over that fact, and maybe even enjoy the rigor of thinking in terms that dumb machines can deal with, programming can be rewarding. It allows you to do things in seconds that would take forever by hand. It is a way to make your computer tool do things that it couldn’t do before. And it provides a wonderful exercise in abstract thinking.

+ +

Most programming is done with programming languages. A programming language is an artificially constructed language used to instruct computers. It is interesting that the most effective way we’ve found to communicate with a computer borrows so heavily from the way we communicate with each other. Like human languages, computer languages allow words and phrases to be combined in new ways, making it possible to express ever new concepts.

+ +

At one point language-based interfaces, such as the BASIC and DOS prompts of the 1980s and 1990s, were the main method of interacting with computers. They have largely been replaced with visual interfaces, which are easier to learn but offer less freedom. Computer languages are still there, if you know where to look. One such language, JavaScript, is built into every modern web browser and is thus available on almost every device.

+ +

This book will try to make you familiar enough with this language to do useful and amusing things with it.

+ +

On programming

+ +

Besides explaining JavaScript, I will introduce the basic principles of programming. Programming, it turns out, is hard. The fundamental rules are simple and clear, but programs built on top of these rules tend to become complex enough to introduce their own rules and complexity. You’re building your own maze, in a way, and you might just get lost in it.

+ +

There will be times when reading this book feels terribly frustrating. If you are new to programming, there will be a lot of new material to digest. Much of this material will then be combined in ways that require you to make additional connections.

+ +

It is up to you to make the necessary effort. When you are struggling to follow the book, do not jump to any conclusions about your own capabilities. You are fine—you just need to keep at it. Take a break, reread some material, and make sure you read and understand the example programs and exercises. Learning is hard work, but everything you learn is yours and will make subsequent learning easier.

+ +
+ +

When action grows unprofitable, gather information; when information grows unprofitable, sleep.

+ +
Ursula K. Le Guin, The Left Hand of Darkness
+ +
+ +

A program is many things. It is a piece of text typed by a programmer, it is the directing force that makes the computer do what it does, it is data in the computer’s memory, yet it controls the actions performed on this same memory. Analogies that try to compare programs to objects we are familiar with tend to fall short. A superficially fitting one is that of a machine—lots of separate parts tend to be involved, and to make the whole thing tick, we have to consider the ways in which these parts interconnect and contribute to the operation of the whole.

+ +

A computer is a physical machine that acts as a host for these immaterial machines. Computers themselves can do only stupidly straightforward things. The reason they are so useful is that they do these things at an incredibly high speed. A program can ingeniously combine an enormous number of these simple actions to do very complicated things.

+ +

A program is a building of thought. It is costless to build, it is weightless, and it grows easily under our typing hands.

+ +

But without care, a program’s size and complexity will grow out of control, confusing even the person who created it. Keeping programs under control is the main problem of programming. When a program works, it is beautiful. The art of programming is the skill of controlling complexity. The great program is subdued—made simple in its complexity.

+ +

Some programmers believe that this complexity is best managed by using only a small set of well-understood techniques in their programs. They have composed strict rules (“best practices”) prescribing the form programs should have and carefully stay within their safe little zone.

+ +

This is not only boring, it is ineffective. New problems often require new solutions. The field of programming is young and still developing rapidly, and it is varied enough to have room for wildly different approaches. There are many terrible mistakes to make in program design, and you should go ahead and make them so that you understand them. A sense of what a good program looks like is developed in practice, not learned from a list of rules.

+ +

Why language matters

+ +

In the beginning, at the birth of computing, there were no programming languages. Programs looked something like this:

+ +
00110001 00000000 00000000
+00110001 00000001 00000001
+00110011 00000001 00000010
+01010001 00001011 00000010
+00100010 00000010 00001000
+01000011 00000001 00000000
+01000001 00000001 00000001
+00010000 00000010 00000000
+01100010 00000000 00000000
+ +

That is a program to add the numbers from 1 to 10 together and print out the result: 1 + 2 + ... + 10 = 55. It could run on a simple, hypothetical machine. To program early computers, it was necessary to set large arrays of switches in the right position or punch holes in strips of cardboard and feed them to the computer. You can probably imagine how tedious and error-prone this procedure was. Even writing simple programs required much cleverness and discipline. Complex ones were nearly inconceivable.

+ +

Of course, manually entering these arcane patterns of bits (the ones and zeros) did give the programmer a profound sense of being a mighty wizard. And that has to be worth something in terms of job satisfaction.

+ +

Each line of the previous program contains a single instruction. It could be written in English like this:

+ +
    + +
  1. + +

    Store the number 0 in memory location 0.

  2. + +
  3. + +

    Store the number 1 in memory location 1.

  4. + +
  5. + +

    Store the value of memory location 1 in memory location 2.

  6. + +
  7. + +

    Subtract the number 11 from the value in memory location 2.

  8. + +
  9. + +

    If the value in memory location 2 is the number 0, continue with instruction 9.

  10. + +
  11. + +

    Add the value of memory location 1 to memory location 0.

  12. + +
  13. + +

    Add the number 1 to the value of memory location 1.

  14. + +
  15. + +

    Continue with instruction 3.

  16. + +
  17. + +

    Output the value of memory location 0.

  18. + +
+ +

Although that is already more readable than the soup of bits, it is still rather obscure. Using names instead of numbers for the instructions and memory locations helps.

+ +
 Set “total” to 0.
+ Set “count” to 1.
+[loop]
+ Set “compare” to “count”.
+ Subtract 11 from “compare”.
+ If “compare” is zero, continue at [end].
+ Add “count” to “total”.
+ Add 1 to “count”.
+ Continue at [loop].
+[end]
+ Output “total”.
+ +

Can you see how the program works at this point? The first two lines give two memory locations their starting values: total will be used to build up the result of the computation, and count will keep track of the number that we are currently looking at. The lines using compare are probably the weirdest ones. The program wants to see whether count is equal to 11 to decide whether it can stop running. Because our hypothetical machine is rather primitive, it can only test whether a number is zero and make a decision based on that. So it uses the memory location labeled compare to compute the value of count - 11 and makes a decision based on that value. The next two lines add the value of count to the result and increment count by 1 every time the program has decided that count is not 11 yet.

+ +

Here is the same program in JavaScript:

+ +
let total = 0, count = 1;
+while (count <= 10) {
+  total += count;
+  count += 1;
+}
+console.log(total);
+// → 55
+ +

This version gives us a few more improvements. Most important, there is no need to specify the way we want the program to jump back and forth anymore. The while construct takes care of that. It continues executing the block (wrapped in braces) below it as long as the condition it was given holds. That condition is count <= 10, which means “count is less than or equal to 10”. We no longer have to create a temporary value and compare that to zero, which was just an uninteresting detail. Part of the power of programming languages is that they can take care of uninteresting details for us.

+ +

At the end of the program, after the while construct has finished, the console.log operation is used to write out the result.

+ +

Finally, here is what the program could look like if we happened to have the convenient operations range and sum available, which respectively create a collection of numbers within a range and compute the sum of a collection of numbers:

+ +
console.log(sum(range(1, 10)));
+// → 55
+ +

The moral of this story is that the same program can be expressed in both long and short, unreadable and readable ways. The first version of the program was extremely obscure, whereas this last one is almost English: log the sum of the range of numbers from 1 to 10. (We will see in later chapters how to define operations like sum and range.)

+ +

A good programming language helps the programmer by allowing them to talk about the actions that the computer has to perform on a higher level. It helps omit details, provides convenient building blocks (such as while and console.log), allows you to define your own building blocks (such as sum and range), and makes those blocks easy to compose.

+ +

What is JavaScript?

+ +

JavaScript was introduced in 1995 as a way to add programs to web pages in the Netscape Navigator browser. The language has since been adopted by all other major graphical web browsers. It has made modern web applications possible—applications with which you can interact directly without doing a page reload for every action. JavaScript is also used in more traditional websites to provide various forms of interactivity and cleverness.

+ +

It is important to note that JavaScript has almost nothing to do with the programming language named Java. The similar name was inspired by marketing considerations rather than good judgment. When JavaScript was being introduced, the Java language was being heavily marketed and was gaining popularity. Someone thought it was a good idea to try to ride along on this success. Now we are stuck with the name.

+ +

After its adoption outside of Netscape, a standard document was written to describe the way the JavaScript language should work so that the various pieces of software that claimed to support JavaScript were actually talking about the same language. This is called the ECMAScript standard, after the Ecma International organization that did the standardization. In practice, the terms ECMAScript and JavaScript can be used interchangeably—they are two names for the same language.

+ +

There are those who will say terrible things about JavaScript. Many of these things are true. When I was required to write something in JavaScript for the first time, I quickly came to despise it. It would accept almost anything I typed but interpret it in a way that was completely different from what I meant. This had a lot to do with the fact that I did not have a clue what I was doing, of course, but there is a real issue here: JavaScript is ridiculously liberal in what it allows. The idea behind this design was that it would make programming in JavaScript easier for beginners. In actuality, it mostly makes finding problems in your programs harder because the system will not point them out to you.

+ +

This flexibility also has its advantages, though. It leaves space for a lot of techniques that are impossible in more rigid languages, and as you will see (for example in Chapter 10), it can be used to overcome some of JavaScript’s shortcomings. After learning the language properly and working with it for a while, I have learned to actually like JavaScript.

+ +

There have been several versions of JavaScript. ECMAScript version 3 was the widely supported version in the time of JavaScript’s ascent to dominance, roughly between 2000 and 2010. During this time, work was underway on an ambitious version 4, which planned a number of radical improvements and extensions to the language. Changing a living, widely used language in such a radical way turned out to be politically difficult, and work on the version 4 was abandoned in 2008, leading to a much less ambitious version 5, which made only some uncontroversial improvements, coming out in 2009. Then in 2015 version 6 came out, a major update that included some of the ideas planned for version 4. Since then we’ve had new, small updates every year.

+ +

The fact that the language is evolving means that browsers have to constantly keep up, and if you’re using an older browser, it may not support every feature. The language designers are careful to not make any changes that could break existing programs, so new browsers can still run old programs. In this book, I’m using the 2017 version of JavaScript.

+ +

Web browsers are not the only platforms on which JavaScript is used. Some databases, such as MongoDB and CouchDB, use JavaScript as their scripting and query language. Several platforms for desktop and server programming, most notably the Node.js project (the subject of Chapter 20), provide an environment for programming JavaScript outside of the browser.

+ +

Code, and what to do with it

+ +

Code is the text that makes up programs. Most chapters in this book contain quite a lot of code. I believe reading code and writing code are indispensable parts of learning to program. Try to not just glance over the examples—read them attentively and understand them. This may be slow and confusing at first, but I promise that you’ll quickly get the hang of it. The same goes for the exercises. Don’t assume you understand them until you’ve actually written a working solution.

+ +

I recommend you try your solutions to exercises in an actual JavaScript interpreter. That way, you’ll get immediate feedback on whether what you are doing is working, and, I hope, you’ll be tempted to experiment and go beyond the exercises.

+ +

When reading this book in your browser, you can edit (and run) all example programs by clicking them.

+ +

If you want to run the programs defined in this book outside of the book’s website, some care will be required. Many examples stand on their own and should work in any JavaScript environment. But code in later chapters is often written for a specific environment (the browser or Node.js) and can run only there. In addition, many chapters define bigger programs, and the pieces of code that appear in them depend on each other or on external files. The sandbox on the website provides links to Zip files containing all the scripts and data files necessary to run the code for a given chapter.

+ +

Overview of this book

+ +

This book contains roughly three parts. The first 12 chapters discuss the JavaScript language. The next seven chapters are about web browsers and the way JavaScript is used to program them. Finally, two chapters are devoted to Node.js, another environment to program JavaScript in.

+ +

Throughout the book, there are five project chapters, which describe larger example programs to give you a taste of actual programming. In order of appearance, we will work through building a delivery robot, a programming language, a platform game, a pixel paint program, and a dynamic website.

+ +

The language part of the book starts with four chapters that introduce the basic structure of the JavaScript language. They introduce control structures (such as the while word you saw in this introduction), functions (writing your own building blocks), and data structures. After these, you will be able to write basic programs. Next, Chapters 5 and 6 introduce techniques to use functions and objects to write more abstract code and keep complexity under control.

+ +

After a first project chapter, the language part of the book continues with chapters on error handling and bug fixing, regular expressions (an important tool for working with text), modularity (another defense against complexity), and asynchronous programming (dealing with events that take time). The second project chapter concludes the first part of the book.

+ +

The second part, Chapters 13 to 19, describes the tools that browser JavaScript has access to. You’ll learn to display things on the screen (Chapters 14 and 17), respond to user input (Chapter 15), and communicate over the network (Chapter 18). There are again two project chapters in this part.

+ +

After that, Chapter 20 describes Node.js, and Chapter 21 builds a small website using that tool.

+ +

Typographic conventions

+ +

In this book, text written in a monospaced font will represent elements of programs—sometimes they are self-sufficient fragments, and sometimes they just refer to part of a nearby program. Programs (of which you have already seen a few) are written as follows:

+ +
function factorial(n) {
+  if (n == 0) {
+    return 1;
+  } else {
+    return factorial(n - 1) * n;
+  }
+}
+ +

Sometimes, to show the output that a program produces, the expected output is written after it, with two slashes and an arrow in front.

+ +
console.log(factorial(8));
+// → 40320
+ +

Good luck!

+
diff --git a/docs/01_values.html b/docs/01_values.html new file mode 100644 index 000000000..ab9ab5d2a --- /dev/null +++ b/docs/01_values.html @@ -0,0 +1,293 @@ + + + + + Values, Types, and Operators :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 1Values, Types, and Operators

+ +
+ +

Below the surface of the machine, the program moves. Without effort, it expands and contracts. In great harmony, electrons scatter and regroup. The forms on the monitor are but ripples on the water. The essence stays invisibly below.

+ +
Master Yuan-Ma, The Book of Programming
+ +
Picture of a sea of bits
+ +

Inside the computer’s world, there is only data. You can read data, modify data, create new data—but that which isn’t data cannot be mentioned. All this data is stored as long sequences of bits and is thus fundamentally alike.

+ +

Bits are any kind of two-valued things, usually described as zeros and ones. Inside the computer, they take forms such as a high or low electrical charge, a strong or weak signal, or a shiny or dull spot on the surface of a CD. Any piece of discrete information can be reduced to a sequence of zeros and ones and thus represented in bits.

+ +

For example, we can express the number 13 in bits. It works the same way as a decimal number, but instead of 10 different digits, you have only 2, and the weight of each increases by a factor of 2 from right to left. Here are the bits that make up the number 13, with the weights of the digits shown below them:

+ +
   0   0   0   0   1   1   0   1
+ 128  64  32  16   8   4   2   1
+ +

So that’s the binary number 00001101. Its non-zero digits stand for 8, 4, and 1, and add up to 13.

+ +

Values

+ +

Imagine a sea of bits—an ocean of them. A typical modern computer has more than 30 billion bits in its volatile data storage (working memory). Nonvolatile storage (the hard disk or equivalent) tends to have yet a few orders of magnitude more.

+ +

To be able to work with such quantities of bits without getting lost, we must separate them into chunks that represent pieces of information. In a JavaScript environment, those chunks are called values. Though all values are made of bits, they play different roles. Every value has a type that determines its role. Some values are numbers, some values are pieces of text, some values are functions, and so on.

+ +

To create a value, you must merely invoke its name. This is convenient. You don’t have to gather building material for your values or pay for them. You just call for one, and whoosh, you have it. They are not really created from thin air, of course. Every value has to be stored somewhere, and if you want to use a gigantic amount of them at the same time, you might run out of memory. Fortunately, this is a problem only if you need them all simultaneously. As soon as you no longer use a value, it will dissipate, leaving behind its bits to be recycled as building material for the next generation of values.

+ +

This chapter introduces the atomic elements of JavaScript programs, that is, the simple value types and the operators that can act on such values.

+ +

Numbers

+ +

Values of the number type are, unsurprisingly, numeric values. In a JavaScript program, they are written as follows:

+ +
13
+ +

Use that in a program, and it will cause the bit pattern for the number 13 to come into existence inside the computer’s memory.

+ +

JavaScript uses a fixed number of bits, 64 of them, to store a single number value. There are only so many patterns you can make with 64 bits, which means that the number of different numbers that can be represented is limited. With N decimal digits, you can represent 10N numbers. Similarly, given 64 binary digits, you can represent 264 different numbers, which is about 18 quintillion (an 18 with 18 zeros after it). That’s a lot.

+ +

Computer memory used to be much smaller, and people tended to use groups of 8 or 16 bits to represent their numbers. It was easy to accidentally overflow such small numbers—to end up with a number that did not fit into the given number of bits. Today, even computers that fit in your pocket have plenty of memory, so you are free to use 64-bit chunks, and you need to worry about overflow only when dealing with truly astronomical numbers.

+ +

Not all whole numbers less than 18 quintillion fit in a JavaScript number, though. Those bits also store negative numbers, so one bit indicates the sign of the number. A bigger issue is that nonwhole numbers must also be represented. To do this, some of the bits are used to store the position of the decimal point. The actual maximum whole number that can be stored is more in the range of 9 quadrillion (15 zeros)—which is still pleasantly huge.

+ +

Fractional numbers are written by using a dot.

+ +
9.81
+ +

For very big or very small numbers, you may also use scientific notation by adding an e (for exponent), followed by the exponent of the number.

+ +
2.998e8
+ +

That is 2.998 × 108 = 299,800,000.

+ +

Calculations with whole numbers (also called integers) smaller than the aforementioned 9 quadrillion are guaranteed to always be precise. Unfortunately, calculations with fractional numbers are generally not. Just as π (pi) cannot be precisely expressed by a finite number of decimal digits, many numbers lose some precision when only 64 bits are available to store them. This is a shame, but it causes practical problems only in specific situations. The important thing is to be aware of it and treat fractional digital numbers as approximations, not as precise values.

+ +

Arithmetic

+ +

The main thing to do with numbers is arithmetic. Arithmetic operations such as addition or multiplication take two number values and produce a new number from them. Here is what they look like in JavaScript:

+ +
100 + 4 * 11
+ +

The + and * symbols are called operators. The first stands for addition, and the second stands for multiplication. Putting an operator between two values will apply it to those values and produce a new value.

+ +

But does the example mean “add 4 and 100, and multiply the result by 11,” or is the multiplication done before the adding? As you might have guessed, the multiplication happens first. But as in mathematics, you can change this by wrapping the addition in parentheses.

+ +
(100 + 4) * 11
+ +

For subtraction, there is the - operator, and division can be done with the / operator.

+ +

When operators appear together without parentheses, the order in which they are applied is determined by the precedence of the operators. The example shows that multiplication comes before addition. The / operator has the same precedence as *. Likewise for + and -. When multiple operators with the same precedence appear next to each other, as in 1 - 2 + 1, they are applied left to right: (1 - 2) + 1.

+ +

These rules of precedence are not something you should worry about. When in doubt, just add parentheses.

+ +

There is one more arithmetic operator, which you might not immediately recognize. The % symbol is used to represent the remainder operation. X % Y is the remainder of dividing X by Y. For example, 314 % 100 produces 14, and 144 % 12 gives 0. The remainder operator’s precedence is the same as that of multiplication and division. You’ll also often see this operator referred to as modulo.

+ +

Special numbers

+ +

There are three special values in JavaScript that are considered numbers but don’t behave like normal numbers.

+ +

The first two are Infinity and -Infinity, which represent the positive and negative infinities. Infinity - 1 is still Infinity, and so on. Don’t put too much trust in infinity-based computation, though. It isn’t mathematically sound, and it will quickly lead to the next special number: NaN.

+ +

NaN stands for “not a number”, even though it is a value of the number type. You’ll get this result when you, for example, try to calculate 0 / 0 (zero divided by zero), Infinity - Infinity, or any number of other numeric operations that don’t yield a meaningful result.

+ +

Strings

+ +

The next basic data type is the string. Strings are used to represent text. They are written by enclosing their content in quotes.

+ +
`Down on the sea`
+"Lie on the ocean"
+'Float on the ocean'
+ +

You can use single quotes, double quotes, or backticks to mark strings, as long as the quotes at the start and the end of the string match.

+ +

Almost anything can be put between quotes, and JavaScript will make a string value out of it. But a few characters are more difficult. You can imagine how putting quotes between quotes might be hard. Newlines (the characters you get when you press enter) can be included without escaping only when the string is quoted with backticks (`).

+ +

To make it possible to include such characters in a string, the following notation is used: whenever a backslash (\) is found inside quoted text, it indicates that the character after it has a special meaning. This is called escaping the character. A quote that is preceded by a backslash will not end the string but be part of it. When an n character occurs after a backslash, it is interpreted as a newline. Similarly, a t after a backslash means a tab character. Take the following string:

+ +
"This is the first line\nAnd this is the second"
+ +

The actual text contained is this:

+ +
This is the first line
+And this is the second
+ +

There are, of course, situations where you want a backslash in a string to be just a backslash, not a special code. If two backslashes follow each other, they will collapse together, and only one will be left in the resulting string value. This is how the string “A newline character is written like "\n".” can be expressed:

+ +
"A newline character is written like \"\\n\"."
+ +

Strings, too, have to be modeled as a series of bits to be able to exist inside the computer. The way JavaScript does this is based on the Unicode standard. This standard assigns a number to virtually every character you would ever need, including characters from Greek, Arabic, Japanese, Armenian, and so on. If we have a number for every character, a string can be described by a sequence of numbers.

+ +

And that’s what JavaScript does. But there’s a complication: JavaScript’s representation uses 16 bits per string element, which can describe up to 216 different characters. But Unicode defines more characters than that—about twice as many, at this point. So some characters, such as many emoji, take up two “character positions” in JavaScript strings. We’ll come back to this in Chapter 5.

+ +

Strings cannot be divided, multiplied, or subtracted, but the + operator can be used on them. It does not add, but it concatenates—it glues two strings together. The following line will produce the string "concatenate":

+ +
"con" + "cat" + "e" + "nate"
+ +

String values have a number of associated functions (methods) that can be used to perform other operations on them. I’ll say more about these in Chapter 4.

+ +

Strings written with single or double quotes behave very much the same—the only difference is in which type of quote you need to escape inside of them. Backtick-quoted strings, usually called template +literals, can do a few more tricks. Apart from being able to span lines, they can also embed other values.

+ +
`half of 100 is ${100 / 2}`
+ +

When you write something inside ${} in a template literal, its result will be computed, converted to a string, and included at that position. The example produces “half of 100 is 50”.

+ +

Unary operators

+ +

Not all operators are symbols. Some are written as words. One example is the typeof operator, which produces a string value naming the type of the value you give it.

+ +
console.log(typeof 4.5)
+// → number
+console.log(typeof "x")
+// → string
+ +

We will use console.log in example code to indicate that we want to see the result of evaluating something. More about that in the next chapter.

+ +

The other operators shown all operated on two values, but typeof takes only one. Operators that use two values are called binary operators, while those that take one are called unary operators. The minus operator can be used both as a binary operator and as a unary operator.

+ +
console.log(- (10 - 2))
+// → -8
+ +

Boolean values

+ +

It is often useful to have a value that distinguishes between only two possibilities, like “yes” and “no” or “on” and “off”. For this purpose, JavaScript has a Boolean type, which has just two values, true and false, which are written as those words.

+ +

Comparison

+ +

Here is one way to produce Boolean values:

+ +
console.log(3 > 2)
+// → true
+console.log(3 < 2)
+// → false
+ +

The > and < signs are the traditional symbols for “is greater than” and “is less than”, respectively. They are binary operators. Applying them results in a Boolean value that indicates whether they hold true in this case.

+ +

Strings can be compared in the same way.

+ +
console.log("Aardvark" < "Zoroaster")
+// → true
+ +

The way strings are ordered is roughly alphabetic but not really what you’d expect to see in a dictionary: uppercase letters are always “less” than lowercase ones, so "Z" < "a", and nonalphabetic characters (!, -, and so on) are also included in the ordering. When comparing strings, JavaScript goes over the characters from left to right, comparing the Unicode codes one by one.

+ +

Other similar operators are >= (greater than or equal to), <= (less than or equal to), == (equal to), and != (not equal to).

+ +
console.log("Itchy" != "Scratchy")
+// → true
+console.log("Apple" == "Orange")
+// → false
+ +

There is only one value in JavaScript that is not equal to itself, and that is NaN (“not a number”).

+ +
console.log(NaN == NaN)
+// → false
+ +

NaN is supposed to denote the result of a nonsensical computation, and as such, it isn’t equal to the result of any other nonsensical computations.

+ +

Logical operators

+ +

There are also some operations that can be applied to Boolean values themselves. JavaScript supports three logical operators: and, or, and not. These can be used to “reason” about Booleans.

+ +

The && operator represents logical and. It is a binary operator, and its result is true only if both the values given to it are true.

+ +
console.log(true && false)
+// → false
+console.log(true && true)
+// → true
+ +

The || operator denotes logical or. It produces true if either of the values given to it is true.

+ +
console.log(false || true)
+// → true
+console.log(false || false)
+// → false
+ +

Not is written as an exclamation mark (!). It is a unary operator that flips the value given to it—!true produces false, and !false gives true.

+ +

When mixing these Boolean operators with arithmetic and other operators, it is not always obvious when parentheses are needed. In practice, you can usually get by with knowing that of the operators we have seen so far, || has the lowest precedence, then comes &&, then the comparison operators (>, ==, and so on), and then the rest. This order has been chosen such that, in typical expressions like the following one, as few parentheses as possible are necessary:

+ +
1 + 1 == 2 && 10 * 10 > 50
+ +

The last logical operator I will discuss is not unary, not binary, but ternary, operating on three values. It is written with a question mark and a colon, like this:

+ +
console.log(true ? 1 : 2);
+// → 1
+console.log(false ? 1 : 2);
+// → 2
+ +

This one is called the conditional operator (or sometimes just the ternary operator since it is the only such operator in the language). The value on the left of the question mark “picks” which of the other two values will come out. When it is true, it chooses the middle value, and when it is false, it chooses the value on the right.

+ +

Empty values

+ +

There are two special values, written null and undefined, that are used to denote the absence of a meaningful value. They are themselves values, but they carry no information.

+ +

Many operations in the language that don’t produce a meaningful value (you’ll see some later) yield undefined simply because they have to yield some value.

+ +

The difference in meaning between undefined and null is an accident of JavaScript’s design, and it doesn’t matter most of the time. In cases where you actually have to concern yourself with these values, I recommend treating them as mostly interchangeable.

+ +

Automatic type conversion

+ +

In the Introduction, I mentioned that JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions:

+ +
console.log(8 * null)
+// → 0
+console.log("5" - 1)
+// → 4
+console.log("5" + 1)
+// → 51
+console.log("five" * 2)
+// → NaN
+console.log(false == 0)
+// → true
+ +

When an operator is applied to the “wrong” type of value, JavaScript will quietly convert that value to the type it needs, using a set of rules that often aren’t what you want or expect. This is called type coercion. The null in the first expression becomes 0, and the "5" in the second expression becomes 5 (from string to number). Yet in the third expression, + tries string concatenation before numeric addition, so the 1 is converted to "1" (from number to string).

+ +

When something that doesn’t map to a number in an obvious way (such as "five" or undefined) is converted to a number, you get the value NaN. Further arithmetic operations on NaN keep producing NaN, so if you find yourself getting one of those in an unexpected place, look for accidental type conversions.

+ +

When comparing values of the same type using ==, the outcome is easy to predict: you should get true when both values are the same, except in the case of NaN. But when the types differ, JavaScript uses a complicated and confusing set of rules to determine what to do. In most cases, it just tries to convert one of the values to the other value’s type. However, when null or undefined occurs on either side of the operator, it produces true only if both sides are one of null or undefined.

+ +
console.log(null == undefined);
+// → true
+console.log(null == 0);
+// → false
+ +

That behavior is often useful. When you want to test whether a value has a real value instead of null or undefined, you can compare it to null with the == (or !=) operator.

+ +

But what if you want to test whether something refers to the precise value false? Expressions like 0 == false and "" == false are also true because of automatic type conversion. When you do not want any type conversions to happen, there are two additional operators: === and !==. The first tests whether a value is precisely equal to the other, and the second tests whether it is not precisely equal. So "" === false is false as expected.

+ +

I recommend using the three-character comparison operators defensively to prevent unexpected type conversions from tripping you up. But when you’re certain the types on both sides will be the same, there is no problem with using the shorter operators.

+ +

Short-circuiting of logical operators

+ +

The logical operators && and || handle values of different types in a peculiar way. They will convert the value on their left side to Boolean type in order to decide what to do, but depending on the operator and the result of that conversion, they will return either the original left-hand value or the right-hand value.

+ +

The || operator, for example, will return the value to its left when that can be converted to true and will return the value on its right otherwise. This has the expected effect when the values are Boolean and does something analogous for values of other types.

+ +
console.log(null || "user")
+// → user
+console.log("Agnes" || "user")
+// → Agnes
+ +

We can use this functionality as a way to fall back on a default value. If you have a value that might be empty, you can put || after it with a replacement value. If the initial value can be converted to false, you’ll get the replacement instead. The rules for converting strings and numbers to Boolean values state that 0, NaN, and the empty string ("") count as false, while all the other values count as true. So 0 || -1 produces -1, and "" || "!?" yields "!?".

+ +

The && operator works similarly but the other way around. When the value to its left is something that converts to false, it returns that value, and otherwise it returns the value on its right.

+ +

Another important property of these two operators is that the part to their right is evaluated only when necessary. In the case of true || X, no matter what X is—even if it’s a piece of program that does something terrible—the result will be true, and X is never evaluated. The same goes for false && X, which is false and will ignore X. This is called short-circuit evaluation.

+ +

The conditional operator works in a similar way. Of the second and third values, only the one that is selected is evaluated.

+ +

Summary

+ +

We looked at four types of JavaScript values in this chapter: numbers, strings, Booleans, and undefined values.

+ +

Such values are created by typing in their name (true, null) or value (13, "abc"). You can combine and transform values with operators. We saw binary operators for arithmetic (+, -, *, /, and %), string concatenation (+), comparison (==, !=, ===, !==, <, >, <=, >=), and logic (&&, ||), as well as several unary operators (- to negate a number, ! to negate logically, and typeof to find a value’s type) and a ternary operator (?:) to pick one of two values based on a third value.

+ +

This gives you enough information to use JavaScript as a pocket calculator but not much more. The next chapter will start tying these expressions together into basic programs.

+
diff --git a/docs/02_program_structure.html b/docs/02_program_structure.html new file mode 100644 index 000000000..9b2bfd233 --- /dev/null +++ b/docs/02_program_structure.html @@ -0,0 +1,515 @@ + + + + + Program Structure :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 2Program Structure

+ +
+ +

And my heart glows bright red under my filmy, translucent skin and they have to administer 10cc of JavaScript to get me to come back. (I respond well to toxins in the blood.) Man, that stuff will kick the peaches right out your gills!

+ +
_why, Why's (Poignant) Guide to Ruby
+ +
Picture of tentacles holding objects
+ +

In this chapter, we will start to do things that can actually be called programming. We will expand our command of the JavaScript language beyond the nouns and sentence fragments we’ve seen so far, to the point where we can express meaningful prose.

+ +

Expressions and statements

+ +

In Chapter 1, we made values and applied operators to them to get new values. Creating values like this is the main substance of any JavaScript program. But that substance has to be framed in a larger structure to be useful. So that’s what we’ll cover next.

+ +

A fragment of code that produces a value is called an expression. Every value that is written literally (such as 22 or "psychoanalysis") is an expression. An expression between parentheses is also an expression, as is a binary operator applied to two expressions or a unary operator applied to one.

+ +

This shows part of the beauty of a language-based interface. Expressions can contain other expressions in a way similar to how subsentences in human languages are nested—a subsentence can contain its own subsentences, and so on. This allows us to build expressions that describe arbitrarily complex computations.

+ +

If an expression corresponds to a sentence fragment, a JavaScript statement corresponds to a full sentence. A program is a list of statements.

+ +

The simplest kind of statement is an expression with a semicolon after it. This is a program:

+ +
1;
+!false;
+ +

It is a useless program, though. An expression can be content to just produce a value, which can then be used by the enclosing code. A statement stands on its own, so it amounts to something only if it affects the world. It could display something on the screen—that counts as changing the world—or it could change the internal state of the machine in a way that will affect the statements that come after it. These changes are called side effects. The statements in the previous example just produce the values 1 and true and then immediately throw them away. This leaves no impression on the world at all. When you run this program, nothing observable happens.

+ +

In some cases, JavaScript allows you to omit the semicolon at the end of a statement. In other cases, it has to be there, or the next line will be treated as part of the same statement. The rules for when it can be safely omitted are somewhat complex and error-prone. So in this book, every statement that needs a semicolon will always get one. I recommend you do the same, at least until you’ve learned more about the subtleties of missing semicolons.

+ +

Bindings

+ +

How does a program keep an internal state? How does it remember things? We have seen how to produce new values from old values, but this does not change the old values, and the new value has to be immediately used or it will dissipate again. To catch and hold values, JavaScript provides a thing called a binding, or variable:

+ +
let caught = 5 * 5;
+ +

That’s a second kind of statement. The special word (keyword) let indicates that this sentence is going to define a binding. It is followed by the name of the binding and, if we want to immediately give it a value, by an = operator and an expression.

+ +

The previous statement creates a binding called caught and uses it to grab hold of the number that is produced by multiplying 5 by 5.

+ +

After a binding has been defined, its name can be used as an expression. The value of such an expression is the value the binding currently holds. Here’s an example:

+ +
let ten = 10;
+console.log(ten * ten);
+// → 100
+ +

When a binding points at a value, that does not mean it is tied to that value forever. The = operator can be used at any time on existing bindings to disconnect them from their current value and have them point to a new one.

+ +
let mood = "light";
+console.log(mood);
+// → light
+mood = "dark";
+console.log(mood);
+// → dark
+ +

You should imagine bindings as tentacles, rather than boxes. They do not contain values; they grasp them—two bindings can refer to the same value. A program can access only the values that it still has a reference to. When you need to remember something, you grow a tentacle to hold on to it or you reattach one of your existing tentacles to it.

+ +

Let’s look at another example. To remember the number of dollars that Luigi still owes you, you create a binding. And then when he pays back $35, you give this binding a new value.

+ +
let luigisDebt = 140;
+luigisDebt = luigisDebt - 35;
+console.log(luigisDebt);
+// → 105
+ +

When you define a binding without giving it a value, the tentacle has nothing to grasp, so it ends in thin air. If you ask for the value of an empty binding, you’ll get the value undefined.

+ +

A single let statement may define multiple bindings. The definitions must be separated by commas.

+ +
let one = 1, two = 2;
+console.log(one + two);
+// → 3
+ +

The words var and const can also be used to create bindings, in a way similar to let.

+ +
var name = "Ayda";
+const greeting = "Hello ";
+console.log(greeting + name);
+// → Hello Ayda
+ +

The first, var (short for “variable”), is the way bindings were declared in pre-2015 JavaScript. I’ll get back to the precise way it differs from let in the next chapter. For now, remember that it mostly does the same thing, but we’ll rarely use it in this book because it has some confusing properties.

+ +

The word const stands for constant. It defines a constant binding, which points at the same value for as long as it lives. This is useful for bindings that give a name to a value so that you can easily refer to it later.

+ +

Binding names

+ +

Binding names can be any word. Digits can be part of binding names—catch22 is a valid name, for example—but the name must not start with a digit. A binding name may include dollar signs ($) or underscores (_) but no other punctuation or special characters.

+ +

Words with a special meaning, such as let, are keywords, and they may not be used as binding names. There are also a number of words that are “reserved for use” in future versions of JavaScript, which also can’t be used as binding names. The full list of keywords and reserved words is rather long.

+ +
break case catch class const continue debugger default
+delete do else enum export extends false finally for
+function if implements import interface in instanceof let
+new package private protected public return static super
+switch this throw true try typeof var void while with yield
+ +

Don’t worry about memorizing this list. When creating a binding produces an unexpected syntax error, see whether you’re trying to define a reserved word.

+ +

The environment

+ +

The collection of bindings and their values that exist at a given time is called the environment. When a program starts up, this environment is not empty. It always contains bindings that are part of the language standard, and most of the time, it also has bindings that provide ways to interact with the surrounding system. For example, in a browser, there are functions to interact with the currently loaded website and to read mouse and keyboard input.

+ +

Functions

+ +

A lot of the values provided in the default environment have the type function. A function is a piece of program wrapped in a value. Such values can be applied in order to run the wrapped program. For example, in a browser environment, the binding prompt holds a function that shows a little dialog box asking for user input. It is used like this:

+ +
prompt("Enter passcode");
A prompt dialog
+ +

Executing a function is called invoking, calling, or applying it. You can call a function by putting parentheses after an expression that produces a function value. Usually you’ll directly use the name of the binding that holds the function. The values between the parentheses are given to the program inside the function. In the example, the prompt function uses the string that we give it as the text to show in the dialog box. Values given to functions are called arguments. Different functions might need a different number or different types of arguments.

+ +

The prompt function isn’t used much in modern web programming, mostly because you have no control over the way the resulting dialog looks, but can be helpful in toy programs and experiments.

+ +

The console.log function

+ +

In the examples, I used console.log to output values. Most JavaScript systems (including all modern web browsers and Node.js) provide a console.log function that writes out its arguments to some text output device. In browsers, the output lands in the JavaScript +console. This part of the browser interface is hidden by default, but most browsers open it when you press F12 or, on a Mac, command-option-I. If that does not work, search through the menus for an item named Developer Tools or similar.

+ +

When running the examples (or your own code) on the pages of this book, console.log output will be shown after the example, instead of in the browser’s JavaScript console.

+ +
let x = 30;
+console.log("the value of x is", x);
+// → the value of x is 30
+ +

Though binding names cannot contain period characters, console.log does have one. This is because console.log isn’t a simple binding. It is actually an expression that retrieves the log property from the value held by the console binding. We’ll find out exactly what this means in Chapter 4.

+ +

Return values

+ +

Showing a dialog box or writing text to the screen is a side +effect. A lot of functions are useful because of the side effects they produce. Functions may also produce values, in which case they don’t need to have a side effect to be useful. For example, the function Math.max takes any amount of number arguments and gives back the greatest.

+ +
console.log(Math.max(2, 4));
+// → 4
+ +

When a function produces a value, it is said to return that value. Anything that produces a value is an expression in JavaScript, which means function calls can be used within larger expressions. Here a call to Math.min, which is the opposite of Math.max, is used as part of a plus expression:

+ +
console.log(Math.min(2, 4) + 100);
+// → 102
+ +

The next chapter explains how to write your own functions.

+ +

Control flow

+ +

When your program contains more than one statement, the statements are executed as if they are a story, from top to bottom. This example program has two statements. The first one asks the user for a number, and the second, which is executed after the first, shows the square of that number.

+ +
let theNumber = Number(prompt("Pick a number"));
+console.log("Your number is the square root of " +
+            theNumber * theNumber);
+ +

The function Number converts a value to a number. We need that conversion because the result of prompt is a string value, and we want a number. There are similar functions called String and Boolean that convert values to those types.

+ +

Here is the rather trivial schematic representation of straight-line control flow:

Trivial control flow
+ +

Conditional execution

+ +

Not all programs are straight roads. We may, for example, want to create a branching road, where the program takes the proper branch based on the situation at hand. This is called conditional +execution.

Conditional control flow
+ +

Conditional execution is created with the if keyword in JavaScript. In the simple case, we want some code to be executed if, and only if, a certain condition holds. We might, for example, want to show the square of the input only if the input is actually a number.

+ +
let theNumber = Number(prompt("Pick a number"));
+if (!Number.isNaN(theNumber)) {
+  console.log("Your number is the square root of " +
+              theNumber * theNumber);
+}
+ +

With this modification, if you enter “parrot”, no output is shown.

+ +

The if keyword executes or skips a statement depending on the value of a Boolean expression. The deciding expression is written after the keyword, between parentheses, followed by the statement to execute.

+ +

The Number.isNaN function is a standard JavaScript function that returns true only if the argument it is given is NaN. The Number function happens to return NaN when you give it a string that doesn’t represent a valid number. Thus, the condition translates to “unless theNumber is not-a-number, do this”.

+ +

The statement after the if is wrapped in braces ({ and }) in this example. The braces can be used to group any number of statements into a single statement, called a block. You could also have omitted them in this case, since they hold only a single statement, but to avoid having to think about whether they are needed, most JavaScript programmers use them in every wrapped statement like this. We’ll mostly follow that convention in this book, except for the occasional one-liner.

+ +
if (1 + 1 == 2) console.log("It's true");
+// → It's true
+ +

You often won’t just have code that executes when a condition holds true, but also code that handles the other case. This alternate path is represented by the second arrow in the diagram. You can use the else keyword, together with if, to create two separate, alternative execution paths.

+ +
let theNumber = Number(prompt("Pick a number"));
+if (!Number.isNaN(theNumber)) {
+  console.log("Your number is the square root of " +
+              theNumber * theNumber);
+} else {
+  console.log("Hey. Why didn't you give me a number?");
+}
+ +

If you have more than two paths to choose from, you can “chain” multiple if/else pairs together. Here’s an example:

+ +
let num = Number(prompt("Pick a number"));
+
+if (num < 10) {
+  console.log("Small");
+} else if (num < 100) {
+  console.log("Medium");
+} else {
+  console.log("Large");
+}
+ +

The program will first check whether num is less than 10. If it is, it chooses that branch, shows "Small", and is done. If it isn’t, it takes the else branch, which itself contains a second if. If the second condition (< 100) holds, that means the number is between 10 and 100, and "Medium" is shown. If it doesn’t, the second and last else branch is chosen.

+ +

The schema for this program looks something like this:

Nested if control flow
+ +

while and do loops

+ +

Consider a program that outputs all even numbers from 0 to 12. One way to write this is as follows:

+ +
console.log(0);
+console.log(2);
+console.log(4);
+console.log(6);
+console.log(8);
+console.log(10);
+console.log(12);
+ +

That works, but the idea of writing a program is to make something less work, not more. If we needed all even numbers less than 1,000, this approach would be unworkable. What we need is a way to run a piece of code multiple times. This form of control flow is called a loop.

Loop control flow
+ +

Looping control flow allows us to go back to some point in the program where we were before and repeat it with our current program state. If we combine this with a binding that counts, we can do something like this:

+ +
let number = 0;
+while (number <= 12) {
+  console.log(number);
+  number = number + 2;
+}
+// → 0
+// → 2
+//   … etcetera
+ +

A statement starting with the keyword while creates a loop. The word while is followed by an expression in parentheses and then a statement, much like if. The loop keeps entering that statement as long as the expression produces a value that gives true when converted to Boolean.

+ +

The number binding demonstrates the way a binding can track the progress of a program. Every time the loop repeats, number gets a value that is 2 more than its previous value. At the beginning of every repetition, it is compared with the number 12 to decide whether the program’s work is finished.

+ +

As an example that actually does something useful, we can now write a program that calculates and shows the value of 210 (2 to the 10th power). We use two bindings: one to keep track of our result and one to count how often we have multiplied this result by 2. The loop tests whether the second binding has reached 10 yet and, if not, updates both bindings.

+ +
let result = 1;
+let counter = 0;
+while (counter < 10) {
+  result = result * 2;
+  counter = counter + 1;
+}
+console.log(result);
+// → 1024
+ +

The counter could also have started at 1 and checked for <= 10, but for reasons that will become apparent in Chapter 4, it is a good idea to get used to counting from 0.

+ +

A do loop is a control structure similar to a while loop. It differs only on one point: a do loop always executes its body at least once, and it starts testing whether it should stop only after that first execution. To reflect this, the test appears after the body of the loop.

+ +
let yourName;
+do {
+  yourName = prompt("Who are you?");
+} while (!yourName);
+console.log(yourName);
+ +

This program will force you to enter a name. It will ask again and again until it gets something that is not an empty string. Applying the ! operator will convert a value to Boolean type before negating it, and all strings except "" convert to true. This means the loop continues going round until you provide a non-empty name.

+ +

Indenting Code

+ +

In the examples, I’ve been adding spaces in front of statements that are part of some larger statement. These spaces are not required—the computer will accept the program just fine without them. In fact, even the line breaks in programs are optional. You could write a program as a single long line if you felt like it.

+ +

The role of this indentation inside blocks is to make the structure of the code stand out. In code where new blocks are opened inside other blocks, it can become hard to see where one block ends and another begins. With proper indentation, the visual shape of a program corresponds to the shape of the blocks inside it. I like to use two spaces for every open block, but tastes differ—some people use four spaces, and some people use tab characters. The important thing is that each new block adds the same amount of space.

+ +
if (false != true) {
+  console.log("That makes sense.");
+  if (1 < 2) {
+    console.log("No surprise there.");
+  }
+}
+ +

Most code editor programs (including the one in this book) will help by automatically indenting new lines the proper amount.

+ +

for loops

+ +

Many loops follow the pattern shown in the while examples. First a “counter” binding is created to track the progress of the loop. Then comes a while loop, usually with a test expression that checks whether the counter has reached its end value. At the end of the loop body, the counter is updated to track progress.

+ +

Because this pattern is so common, JavaScript and similar languages provide a slightly shorter and more comprehensive form, the for loop.

+ +
for (let number = 0; number <= 12; number = number + 2) {
+  console.log(number);
+}
+// → 0
+// → 2
+//   … etcetera
+ +

This program is exactly equivalent to the earlier even-number-printing example. The only change is that all the statements that are related to the “state” of the loop are grouped together after for.

+ +

The parentheses after a for keyword must contain two semicolons. The part before the first semicolon initializes the loop, usually by defining a binding. The second part is the expression that checks whether the loop must continue. The final part updates the state of the loop after every iteration. In most cases, this is shorter and clearer than a while construct.

+ +

This is the code that computes 210 using for instead of while:

+ +
let result = 1;
+for (let counter = 0; counter < 10; counter = counter + 1) {
+  result = result * 2;
+}
+console.log(result);
+// → 1024
+ +

Breaking Out of a Loop

+ +

Having the looping condition produce false is not the only way a loop can finish. There is a special statement called break that has the effect of immediately jumping out of the enclosing loop.

+ +

This program illustrates the break statement. It finds the first number that is both greater than or equal to 20 and divisible by 7.

+ +
for (let current = 20; ; current = current + 1) {
+  if (current % 7 == 0) {
+    console.log(current);
+    break;
+  }
+}
+// → 21
+ +

Using the remainder (%) operator is an easy way to test whether a number is divisible by another number. If it is, the remainder of their division is zero.

+ +

The for construct in the example does not have a part that checks for the end of the loop. This means that the loop will never stop unless the break statement inside is executed.

+ +

If you were to remove that break statement or you accidentally write an end condition that always produces true, your program would get stuck in an infinite loop. A program stuck in an infinite loop will never finish running, which is usually a bad thing.

+ +

If you create an infinite loop in one of the examples on these pages, you’ll usually be asked whether you want to stop the script after a few seconds. If that fails, you will have to close the tab that you’re working in, or on some browsers close your whole browser, to recover.

+ +

The continue keyword is similar to break, in that it influences the progress of a loop. When continue is encountered in a loop body, control jumps out of the body and continues with the loop’s next iteration.

+ +

Updating bindings succinctly

+ +

Especially when looping, a program often needs to “update” a binding to hold a value based on that binding’s previous value.

+ +
counter = counter + 1;
+ +

JavaScript provides a shortcut for this.

+ +
counter += 1;
+ +

Similar shortcuts work for many other operators, such as result *= 2 to double result or counter -= 1 to count downward.

+ +

This allows us to shorten our counting example a little more.

+ +
for (let number = 0; number <= 12; number += 2) {
+  console.log(number);
+}
+ +

For counter += 1 and counter -= 1, there are even shorter equivalents: counter++ and counter--.

+ +

Dispatching on a value with switch

+ +

It is not uncommon for code to look like this:

+ +
if (x == "value1") action1();
+else if (x == "value2") action2();
+else if (x == "value3") action3();
+else defaultAction();
+ +

There is a construct called switch that is intended to express such a “dispatch” in a more direct way. Unfortunately, the syntax JavaScript uses for this (which it inherited from the C/Java line of programming languages) is somewhat awkward—a chain of if statements may look better. Here is an example:

+ +
switch (prompt("What is the weather like?")) {
+  case "rainy":
+    console.log("Remember to bring an umbrella.");
+    break;
+  case "sunny":
+    console.log("Dress lightly.");
+  case "cloudy":
+    console.log("Go outside.");
+    break;
+  default:
+    console.log("Unknown weather type!");
+    break;
+}
+ +

You may put any number of case labels inside the block opened by switch. The program will start executing at the label that corresponds to the value that switch was given, or at default if no matching value is found. It will continue executing, even across other labels, until it reaches a break statement. In some cases, such as the "sunny" case in the example, this can be used to share some code between cases (it recommends going outside for both sunny and cloudy weather). But be careful—it is easy to forget such a break, which will cause the program to execute code you do not want executed.

+ +

Capitalization

+ +

Binding names may not contain spaces, yet it is often helpful to use multiple words to clearly describe what the binding represents. These are pretty much your choices for writing a binding name with several words in it:

+ +
fuzzylittleturtle
+fuzzy_little_turtle
+FuzzyLittleTurtle
+fuzzyLittleTurtle
+ +

The first style can be hard to read. I rather like the look of the underscores, though that style is a little painful to type. The standard JavaScript functions, and most JavaScript programmers, follow the bottom style—they capitalize every word except the first. It is not hard to get used to little things like that, and code with mixed naming styles can be jarring to read, so we follow this convention.

+ +

In a few cases, such as the Number function, the first letter of a binding is also capitalized. This was done to mark this function as a constructor. What a constructor is will become clear in Chapter 6. For now, the important thing is not to be bothered by this apparent lack of consistency.

+ +

Comments

+ +

Often, raw code does not convey all the information you want a program to convey to human readers, or it conveys it in such a cryptic way that people might not understand it. At other times, you might just want to include some related thoughts as part of your program. This is what comments are for.

+ +

A comment is a piece of text that is part of a program but is completely ignored by the computer. JavaScript has two ways of writing comments. To write a single-line comment, you can use two slash characters (//) and then the comment text after it.

+ +
let accountBalance = calculateBalance(account);
+// It's a green hollow where a river sings
+accountBalance.adjust();
+// Madly catching white tatters in the grass.
+let report = new Report();
+// Where the sun on the proud mountain rings:
+addToReport(accountBalance, report);
+// It's a little valley, foaming like light in a glass.
+ +

A // comment goes only to the end of the line. A section of text between /* and */ will be ignored in its entirety, regardless of whether it contains line breaks. This is useful for adding blocks of information about a file or a chunk of program.

+ +
/*
+  I first found this number scrawled on the back of an old notebook.
+  Since then, it has often dropped by, showing up in phone numbers
+  and the serial numbers of products that I've bought. It obviously
+  likes me, so I've decided to keep it.
+*/
+const myNumber = 11213;
+ +

Summary

+ +

You now know that a program is built out of statements, which themselves sometimes contain more statements. Statements tend to contain expressions, which themselves can be built out of smaller expressions.

+ +

Putting statements after one another gives you a program that is executed from top to bottom. You can introduce disturbances in the flow of control by using conditional (if, else, and switch) and looping (while, do, and for) statements.

+ +

Bindings can be used to file pieces of data under a name, and they are useful for tracking state in your program. The environment is the set of bindings that are defined. JavaScript systems always put a number of useful standard bindings into your environment.

+ +

Functions are special values that encapsulate a piece of program. You can invoke them by writing functionName(argument1, argument2). Such a function call is an expression and may produce a value.

+ +

Exercises

+ +

If you are unsure how to test your solutions to the exercises, refer to the Introduction.

+ +

Each exercise starts with a problem description. Read this description and try to solve the exercise. If you run into problems, consider reading the hints after the exercise. Full solutions to the exercises are not included in this book, but you can find them online at https://eloquentjavascript.net/code. If you want to learn something from the exercises, I recommend looking at the solutions only after you’ve solved the exercise, or at least after you’ve attacked it long and hard enough to have a slight headache.

+ +

Looping a triangle

+ +

Write a loop that makes seven calls to console.log to output the following triangle:

+ +
#
+##
+###
+####
+#####
+######
+#######
+ +

It may be useful to know that you can find the length of a string by writing .length after it.

+ +
let abc = "abc";
+console.log(abc.length);
+// → 3
+ +

Most exercises contain a piece of code that you can modify to solve the exercise. Remember that you can click code blocks to edit them.

+ +
// Your code here.
+ +
+ +

You can start with a program that prints out the numbers 1 to 7, which you can derive by making a few modifications to the even number printing example given earlier in the chapter, where the for loop was introduced.

+ +

Now consider the equivalence between numbers and strings of hash characters. You can go from 1 to 2 by adding 1 (+= 1). You can go from "#" to "##" by adding a character (+= "#"). Thus, your solution can closely follow the number-printing program.

+ +
+ +

FizzBuzz

+ +

Write a program that uses console.log to print all the numbers from 1 to 100, with two exceptions. For numbers divisible by 3, print "Fizz" instead of the number, and for numbers divisible by 5 (and not 3), print "Buzz" instead.

+ +

When you have that working, modify your program to print "FizzBuzz" for numbers that are divisible by both 3 and 5 (and still print "Fizz" or "Buzz" for numbers divisible by only one of those).

+ +

(This is actually an interview question that has been claimed to weed out a significant percentage of programmer candidates. So if you solved it, your labor market value just went up.)

+ +
// Your code here.
+ +
+ +

Going over the numbers is clearly a looping job, and selecting what to print is a matter of conditional execution. Remember the trick of using the remainder (%) operator for checking whether a number is divisible by another number (has a remainder of zero).

+ +

In the first version, there are three possible outcomes for every number, so you’ll have to create an if/else if/else chain.

+ +

The second version of the program has a straightforward solution and a clever one. The simple solution is to add another conditional “branch” to precisely test the given condition. For the clever solution, build up a string containing the word or words to output and print either this word or the number if there is no word, potentially by making good use of the || operator.

+ +
+ +

Chessboard

+ +

Write a program that creates a string that represents an 8×8 grid, using newline characters to separate lines. At each position of the grid there is either a space or a "#" character. The characters should form a chessboard.

+ +

Passing this string to console.log should show something like this:

+ +
 # # # #
+# # # # 
+ # # # #
+# # # # 
+ # # # #
+# # # # 
+ # # # #
+# # # #
+ +

When you have a program that generates this pattern, define a binding size = 8 and change the program so that it works for any size, outputting a grid of the given width and height.

+ +
// Your code here.
+ +
+ +

You can build the string by starting with an empty one ("") and repeatedly adding characters. A newline character is written "\n".

+ +

To work with two dimensions, you will need a loop inside of a loop. Put braces around the bodies of both loops to make it easy to see where they start and end. Try to properly indent these bodies. The order of the loops must follow the order in which we build up the string (line by line, left to right, top to bottom). So the outer loop handles the lines, and the inner loop handles the characters on a line.

+ +

You’ll need two bindings to track your progress. To know whether to put a space or a hash sign at a given position, you could test whether the sum of the two counters is even (% 2).

+ +

Terminating a line by adding a newline character must happen after the line has been built up, so do this after the inner loop but inside the outer loop.

+ +
+
diff --git a/docs/03_functions.html b/docs/03_functions.html new file mode 100644 index 000000000..405fdfed2 --- /dev/null +++ b/docs/03_functions.html @@ -0,0 +1,581 @@ + + + + + Functions :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 3Functions

+ +
+ +

People think that computer science is the art of geniuses but the actual reality is the opposite, just many people doing things that build on each other, like a wall of mini stones.

+ +
Donald Knuth
+ +
Picture of fern leaves with a fractal shape
+ +

Functions are the bread and butter of JavaScript programming. The concept of wrapping a piece of program in a value has many uses. It gives us a way to structure larger programs, to reduce repetition, to associate names with subprograms, and to isolate these subprograms from each other.

+ +

The most obvious application of functions is defining new vocabulary. Creating new words in prose is usually bad style. But in programming, it is indispensable.

+ +

Typical adult English speakers have some 20,000 words in their vocabulary. Few programming languages come with 20,000 commands built in. And the vocabulary that is available tends to be more precisely defined, and thus less flexible, than in human language. Therefore, we usually have to introduce new concepts to avoid repeating ourselves too much.

+ +

Defining a function

+ +

A function definition is a regular binding where the value of the binding is a function. For example, this code defines square to refer to a function that produces the square of a given number:

+ +
const square = function(x) {
+  return x * x;
+};
+
+console.log(square(12));
+// → 144
+ +

A function is created with an expression that starts with the keyword function. Functions have a set of parameters (in this case, only x) and a body, which contains the statements that are to be executed when the function is called. The function body of a function created this way must always be wrapped in braces, even when it consists of only a single statement.

+ +

A function can have multiple parameters or no parameters at all. In the following example, makeNoise does not list any parameter names, whereas power lists two:

+ +
const makeNoise = function() {
+  console.log("Pling!");
+};
+
+makeNoise();
+// → Pling!
+
+const power = function(base, exponent) {
+  let result = 1;
+  for (let count = 0; count < exponent; count++) {
+    result *= base;
+  }
+  return result;
+};
+
+console.log(power(2, 10));
+// → 1024
+ +

Some functions produce a value, such as power and square, and some don’t, such as makeNoise, whose only result is a side effect. A return statement determines the value the function returns. When control comes across such a statement, it immediately jumps out of the current function and gives the returned value to the code that called the function. A return keyword without an expression after it will cause the function to return undefined. Functions that don’t have a return statement at all, such as makeNoise, similarly return undefined.

+ +

Parameters to a function behave like regular bindings, but their initial values are given by the caller of the function, not the code in the function itself.

+ +

Bindings and scopes

+ +

Each binding has a scope, which is the part of the program in which the binding is visible. For bindings defined outside of any function or block, the scope is the whole program—you can refer to such bindings wherever you want. These are called global.

+ +

But bindings created for function parameters or declared inside a function can be referenced only in that function, so they are known as local bindings. Every time the function is called, new instances of these bindings are created. This provides some isolation between functions—each function call acts in its own little world (its local environment) and can often be understood without knowing a lot about what’s going on in the global environment.

+ +

Bindings declared with let and const are in fact local to the block that they are declared in, so if you create one of those inside of a loop, the code before and after the loop cannot “see” it. In pre-2015 JavaScript, only functions created new scopes, so old-style bindings, created with the var keyword, are visible throughout the whole function that they appear in—or throughout the global scope, if they are not in a function.

+ +
let x = 10;
+if (true) {
+  let y = 20;
+  var z = 30;
+  console.log(x + y + z);
+  // → 60
+}
+// y is not visible here
+console.log(x + z);
+// → 40
+ +

Each scope can “look out” into the scope around it, so x is visible inside the block in the example. The exception is when multiple bindings have the same name—in that case, code can see only the innermost one. For example, when the code inside the halve function refers to n, it is seeing its own n, not the global n.

+ +
const halve = function(n) {
+  return n / 2;
+};
+
+let n = 10;
+console.log(halve(100));
+// → 50
+console.log(n);
+// → 10
+ +

Nested scope

+ +

JavaScript distinguishes not just global and local bindings. Blocks and functions can be created inside other blocks and functions, producing multiple degrees of locality.

+ +

For example, this function—which outputs the ingredients needed to make a batch of hummus—has another function inside it:

+ +
const hummus = function(factor) {
+  const ingredient = function(amount, unit, name) {
+    let ingredientAmount = amount * factor;
+    if (ingredientAmount > 1) {
+      unit += "s";
+    }
+    console.log(`${ingredientAmount} ${unit} ${name}`);
+  };
+  ingredient(1, "can", "chickpeas");
+  ingredient(0.25, "cup", "tahini");
+  ingredient(0.25, "cup", "lemon juice");
+  ingredient(1, "clove", "garlic");
+  ingredient(2, "tablespoon", "olive oil");
+  ingredient(0.5, "teaspoon", "cumin");
+};
+ +

The code inside the ingredient function can see the factor binding from the outer function. But its local bindings, such as unit or ingredientAmount, are not visible in the outer function.

+ +

The set of bindings visible inside a block is determined by the place of that block in the program text. Each local scope can also see all the local scopes that contain it, and all scopes can see the global scope. This approach to binding visibility is called lexical scoping.

+ +

Functions as values

+ +

A function binding usually simply acts as a name for a specific piece of the program. Such a binding is defined once and never changed. This makes it easy to confuse the function and its name.

+ +

But the two are different. A function value can do all the things that other values can do—you can use it in arbitrary expressions, not just call it. It is possible to store a function value in a new binding, pass it as an argument to a function, and so on. Similarly, a binding that holds a function is still just a regular binding and can, if not constant, be assigned a new value, like so:

+ +
let launchMissiles = function() {
+  missileSystem.launch("now");
+};
+if (safeMode) {
+  launchMissiles = function() {/* do nothing */};
+}
+ +

In Chapter 5, we will discuss the interesting things that can be done by passing around function values to other functions.

+ +

Declaration notation

+ +

There is a slightly shorter way to create a function binding. When the function keyword is used at the start of a statement, it works differently.

+ +
function square(x) {
+  return x * x;
+}
+ +

This is a function declaration. The statement defines the binding square and points it at the given function. It is slightly easier to write and doesn’t require a semicolon after the function.

+ +

There is one subtlety with this form of function definition.

+ +
console.log("The future says:", future());
+
+function future() {
+  return "You'll never have flying cars";
+}
+ +

The preceding code works, even though the function is defined below the code that uses it. Function declarations are not part of the regular top-to-bottom flow of control. They are conceptually moved to the top of their scope and can be used by all the code in that scope. This is sometimes useful because it offers the freedom to order code in a way that seems meaningful, without worrying about having to define all functions before they are used.

+ +

Arrow functions

+ +

There’s a third notation for functions, which looks very different from the others. Instead of the function keyword, it uses an arrow (=>) made up of an equal sign and a greater-than character (not to be confused with the greater-than-or-equal operator, which is written >=).

+ +
const power = (base, exponent) => {
+  let result = 1;
+  for (let count = 0; count < exponent; count++) {
+    result *= base;
+  }
+  return result;
+};
+ +

The arrow comes after the list of parameters and is followed by the function’s body. It expresses something like “this input (the parameters) produces this result (the body)”.

+ +

When there is only one parameter name, you can omit the parentheses around the parameter list. If the body is a single expression, rather than a block in braces, that expression will be returned from the function. So, these two definitions of square do the same thing:

+ +
const square1 = (x) => { return x * x; };
+const square2 = x => x * x;
+ +

When an arrow function has no parameters at all, its parameter list is just an empty set of parentheses.

+ +
const horn = () => {
+  console.log("Toot");
+};
+ +

There’s no deep reason to have both arrow functions and function expressions in the language. Apart from a minor detail, which we’ll discuss in Chapter 6, they do the same thing. Arrow functions were added in 2015, mostly to make it possible to write small function expressions in a less verbose way. We’ll be using them a lot in Chapter 5.

+ +

The call stack

+ +

The way control flows through functions is somewhat involved. Let’s take a closer look at it. Here is a simple program that makes a few function calls:

+ +
function greet(who) {
+  console.log("Hello " + who);
+}
+greet("Harry");
+console.log("Bye");
+ +

A run through this program goes roughly like this: the call to greet causes control to jump to the start of that function (line 2). The function calls console.log, which takes control, does its job, and then returns control to line 2. There it reaches the end of the greet function, so it returns to the place that called it, which is line 4. The line after that calls console.log again. After that returns, the program reaches its end.

+ +

We could show the flow of control schematically like this:

+ +
not in function
+   in greet
+        in console.log
+   in greet
+not in function
+   in console.log
+not in function
+ +

Because a function has to jump back to the place that called it when it returns, the computer must remember the context from which the call happened. In one case, console.log has to return to the greet function when it is done. In the other case, it returns to the end of the program.

+ +

The place where the computer stores this context is the call +stack. Every time a function is called, the current context is stored on top of this stack. When a function returns, it removes the top context from the stack and uses that context to continue execution.

+ +

Storing this stack requires space in the computer’s memory. When the stack grows too big, the computer will fail with a message like “out of stack space” or “too much recursion”. The following code illustrates this by asking the computer a really hard question that causes an infinite back-and-forth between two functions. Rather, it would be infinite, if the computer had an infinite stack. As it is, we will run out of space, or “blow the stack”.

+ +
function chicken() {
+  return egg();
+}
+function egg() {
+  return chicken();
+}
+console.log(chicken() + " came first.");
+// → ??
+ +

Optional Arguments

+ +

The following code is allowed and executes without any problem:

+ +
function square(x) { return x * x; }
+console.log(square(4, true, "hedgehog"));
+// → 16
+ +

We defined square with only one parameter. Yet when we call it with three, the language doesn’t complain. It ignores the extra arguments and computes the square of the first one.

+ +

JavaScript is extremely broad-minded about the number of arguments you pass to a function. If you pass too many, the extra ones are ignored. If you pass too few, the missing parameters get assigned the value undefined.

+ +

The downside of this is that it is possible—likely, even—that you’ll accidentally pass the wrong number of arguments to functions. And no one will tell you about it.

+ +

The upside is that this behavior can be used to allow a function to be called with different numbers of arguments. For example, this minus function tries to imitate the - operator by acting on either one or two arguments:

+ +
function minus(a, b) {
+  if (b === undefined) return -a;
+  else return a - b;
+}
+
+console.log(minus(10));
+// → -10
+console.log(minus(10, 5));
+// → 5
+ +

If you write an = operator after a parameter, followed by an expression, the value of that expression will replace the argument when it is not given.

+ +

For example, this version of power makes its second argument optional. If you don’t provide it or pass the value undefined, it will default to two, and the function will behave like square.

+ +
function power(base, exponent = 2) {
+  let result = 1;
+  for (let count = 0; count < exponent; count++) {
+    result *= base;
+  }
+  return result;
+}
+
+console.log(power(4));
+// → 16
+console.log(power(2, 6));
+// → 64
+ +

In the next chapter, we will see a way in which a function body can get at the whole list of arguments it was passed. This is helpful because it makes it possible for a function to accept any number of arguments. For example, console.log does this—it outputs all of the values it is given.

+ +
console.log("C", "O", 2);
+// → C O 2
+ +

Closure

+ +

The ability to treat functions as values, combined with the fact that local bindings are re-created every time a function is called, brings up an interesting question. What happens to local bindings when the function call that created them is no longer active?

+ +

The following code shows an example of this. It defines a function, wrapValue, that creates a local binding. It then returns a function that accesses and returns this local binding.

+ +
function wrapValue(n) {
+  let local = n;
+  return () => local;
+}
+
+let wrap1 = wrapValue(1);
+let wrap2 = wrapValue(2);
+console.log(wrap1());
+// → 1
+console.log(wrap2());
+// → 2
+ +

This is allowed and works as you’d hope—both instances of the binding can still be accessed. This situation is a good demonstration of the fact that local bindings are created anew for every call, and different calls can’t trample on one another’s local bindings.

+ +

This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called closure. A function that references bindings from local scopes around it is called a closure. This behavior not only frees you from having to worry about lifetimes of bindings but also makes it possible to use function values in some creative ways.

+ +

With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount.

+ +
function multiplier(factor) {
+  return number => number * factor;
+}
+
+let twice = multiplier(2);
+console.log(twice(5));
+// → 10
+ +

The explicit local binding from the wrapValue example isn’t really needed since a parameter is itself a local binding.

+ +

Thinking about programs like this takes some practice. A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees the environment in which it was created, not the environment in which it is called.

+ +

In the example, multiplier is called and creates an environment in which its factor parameter is bound to 2. The function value it returns, which is stored in twice, remembers this environment. So when that is called, it multiplies its argument by 2.

+ +

Recursion

+ +

It is perfectly okay for a function to call itself, as long as it doesn’t do it so often that it overflows the stack. A function that calls itself is called recursive. Recursion allows some functions to be written in a different style. Take, for example, this alternative implementation of power:

+ +
function power(base, exponent) {
+  if (exponent == 0) {
+    return 1;
+  } else {
+    return base * power(base, exponent - 1);
+  }
+}
+
+console.log(power(2, 3));
+// → 8
+ +

This is rather close to the way mathematicians define exponentiation and arguably describes the concept more clearly than the looping variant. The function calls itself multiple times with ever smaller exponents to achieve the repeated multiplication.

+ +

But this implementation has one problem: in typical JavaScript implementations, it’s about three times slower than the looping version. Running through a simple loop is generally cheaper than calling a function multiple times.

+ +

The dilemma of speed versus elegance is an interesting one. You can see it as a kind of continuum between human-friendliness and machine-friendliness. Almost any program can be made faster by making it bigger and more convoluted. The programmer has to decide on an appropriate balance.

+ +

In the case of the power function, the inelegant (looping) version is still fairly simple and easy to read. It doesn’t make much sense to replace it with the recursive version. Often, though, a program deals with such complex concepts that giving up some efficiency in order to make the program more straightforward is helpful.

+ +

Worrying about efficiency can be a distraction. It’s yet another factor that complicates program design, and when you’re doing something that’s already difficult, that extra thing to worry about can be paralyzing.

+ +

Therefore, always start by writing something that’s correct and easy to understand. If you’re worried that it’s too slow—which it usually isn’t since most code simply isn’t executed often enough to take any significant amount of time—you can measure afterward and improve it if necessary.

+ +

Recursion is not always just an inefficient alternative to looping. Some problems really are easier to solve with recursion than with loops. Most often these are problems that require exploring or processing several “branches”, each of which might branch out again into even more branches.

+ +

Consider this puzzle: by starting from the number 1 and repeatedly either adding 5 or multiplying by 3, an infinite set of numbers can be produced. How would you write a function that, given a number, tries to find a sequence of such additions and multiplications that produces that number?

+ +

For example, the number 13 could be reached by first multiplying by 3 and then adding 5 twice, whereas the number 15 cannot be reached at all.

+ +

Here is a recursive solution:

+ +
function findSolution(target) {
+  function find(current, history) {
+    if (current == target) {
+      return history;
+    } else if (current > target) {
+      return null;
+    } else {
+      return find(current + 5, `(${history} + 5)`) ||
+             find(current * 3, `(${history} * 3)`);
+    }
+  }
+  return find(1, "1");
+}
+
+console.log(findSolution(24));
+// → (((1 * 3) + 5) * 3)
+ +

Note that this program doesn’t necessarily find the shortest sequence of operations. It is satisfied when it finds any sequence at all.

+ +

It is okay if you don’t see how it works right away. Let’s work through it, since it makes for a great exercise in recursive thinking.

+ +

The inner function find does the actual recursing. It takes two arguments: the current number and a string that records how we reached this number. If it finds a solution, it returns a string that shows how to get to the target. If no solution can be found starting from this number, it returns null.

+ +

To do this, the function performs one of three actions. If the current number is the target number, the current history is a way to reach that target, so it is returned. If the current number is greater than the target, there’s no sense in further exploring this branch because both adding and multiplying will only make the number bigger, so it returns null. Finally, if we’re still below the target number, the function tries both possible paths that start from the current number by calling itself twice, once for addition and once for multiplication. If the first call returns something that is not null, it is returned. Otherwise, the second call is returned, regardless of whether it produces a string or null.

+ +

To better understand how this function produces the effect we’re looking for, let’s look at all the calls to find that are made when searching for a solution for the number 13.

+ +
find(1, "1")
+  find(6, "(1 + 5)")
+    find(11, "((1 + 5) + 5)")
+      find(16, "(((1 + 5) + 5) + 5)")
+        too big
+      find(33, "(((1 + 5) + 5) * 3)")
+        too big
+    find(18, "((1 + 5) * 3)")
+      too big
+  find(3, "(1 * 3)")
+    find(8, "((1 * 3) + 5)")
+      find(13, "(((1 * 3) + 5) + 5)")
+        found!
+ +

The indentation indicates the depth of the call stack. The first time find is called, it starts by calling itself to explore the solution that starts with (1 + 5). That call will further recurse to explore every continued solution that yields a number less than or equal to the target number. Since it doesn’t find one that hits the target, it returns null back to the first call. There the || operator causes the call that explores (1 * 3) to happen. This search has more luck—its first recursive call, through yet another recursive call, hits upon the target number. That innermost call returns a string, and each of the || operators in the intermediate calls passes that string along, ultimately returning the solution.

+ +

Growing functions

+ +

There are two more or less natural ways for functions to be introduced into programs.

+ +

The first is that you find yourself writing similar code multiple times. You’d prefer not to do that. Having more code means more space for mistakes to hide and more material to read for people trying to understand the program. So you take the repeated functionality, find a good name for it, and put it into a function.

+ +

The second way is that you find you need some functionality that you haven’t written yet and that sounds like it deserves its own function. You’ll start by naming the function, and then you’ll write its body. You might even start writing code that uses the function before you actually define the function itself.

+ +

How difficult it is to find a good name for a function is a good indication of how clear a concept it is that you’re trying to wrap. Let’s go through an example.

+ +

We want to write a program that prints two numbers: the numbers of cows and chickens on a farm, with the words Cows and Chickens after them and zeros padded before both numbers so that they are always three digits long.

+ +
007 Cows
+011 Chickens
+ +

This asks for a function of two arguments—the number of cows and the number of chickens. Let’s get coding.

+ +
function printFarmInventory(cows, chickens) {
+  let cowString = String(cows);
+  while (cowString.length < 3) {
+    cowString = "0" + cowString;
+  }
+  console.log(`${cowString} Cows`);
+  let chickenString = String(chickens);
+  while (chickenString.length < 3) {
+    chickenString = "0" + chickenString;
+  }
+  console.log(`${chickenString} Chickens`);
+}
+printFarmInventory(7, 11);
+ +

Writing .length after a string expression will give us the length of that string. Thus, the while loops keep adding zeros in front of the number strings until they are at least three characters long.

+ +

Mission accomplished! But just as we are about to send the farmer the code (along with a hefty invoice), she calls and tells us she’s also started keeping pigs, and couldn’t we please extend the software to also print pigs?

+ +

We sure can. But just as we’re in the process of copying and pasting those four lines one more time, we stop and reconsider. There has to be a better way. Here’s a first attempt:

+ +
function printZeroPaddedWithLabel(number, label) {
+  let numberString = String(number);
+  while (numberString.length < 3) {
+    numberString = "0" + numberString;
+  }
+  console.log(`${numberString} ${label}`);
+}
+
+function printFarmInventory(cows, chickens, pigs) {
+  printZeroPaddedWithLabel(cows, "Cows");
+  printZeroPaddedWithLabel(chickens, "Chickens");
+  printZeroPaddedWithLabel(pigs, "Pigs");
+}
+
+printFarmInventory(7, 11, 3);
+ +

It works! But that name, printZeroPaddedWithLabel, is a little awkward. It conflates three things—printing, zero-padding, and adding a label—into a single function.

+ +

Instead of lifting out the repeated part of our program wholesale, let’s try to pick out a single concept.

+ +
function zeroPad(number, width) {
+  let string = String(number);
+  while (string.length < width) {
+    string = "0" + string;
+  }
+  return string;
+}
+
+function printFarmInventory(cows, chickens, pigs) {
+  console.log(`${zeroPad(cows, 3)} Cows`);
+  console.log(`${zeroPad(chickens, 3)} Chickens`);
+  console.log(`${zeroPad(pigs, 3)} Pigs`);
+}
+
+printFarmInventory(7, 16, 3);
+ +

A function with a nice, obvious name like zeroPad makes it easier for someone who reads the code to figure out what it does. And such a function is useful in more situations than just this specific program. For example, you could use it to help print nicely aligned tables of numbers.

+ +

How smart and versatile should our function be? We could write anything, from a terribly simple function that can only pad a number to be three characters wide to a complicated generalized number-formatting system that handles fractional numbers, negative numbers, alignment of decimal dots, padding with different characters, and so on.

+ +

A useful principle is to not add cleverness unless you are absolutely sure you’re going to need it. It can be tempting to write general “frameworks” for every bit of functionality you come across. Resist that urge. You won’t get any real work done—you’ll just be writing code that you never use.

+ +

Functions and side effects

+ +

Functions can be roughly divided into those that are called for their side effects and those that are called for their return value. (Though it is definitely also possible to both have side effects and return a value.)

+ +

The first helper function in the farm example, printZeroPaddedWithLabel, is called for its side effect: it prints a line. The second version, zeroPad, is called for its return value. It is no coincidence that the second is useful in more situations than the first. Functions that create values are easier to combine in new ways than functions that directly perform side effects.

+ +

A pure function is a specific kind of value-producing function that not only has no side effects but also doesn’t rely on side effects from other code—for example, it doesn’t read global bindings whose value might change. A pure function has the pleasant property that, when called with the same arguments, it always produces the same value (and doesn’t do anything else). A call to such a function can be substituted by its return value without changing the meaning of the code. When you are not sure that a pure function is working correctly, you can test it by simply calling it and know that if it works in that context, it will work in any context. Nonpure functions tend to require more scaffolding to test.

+ +

Still, there’s no need to feel bad when writing functions that are not pure or to wage a holy war to purge them from your code. Side effects are often useful. There’d be no way to write a pure version of console.log, for example, and console.log is good to have. Some operations are also easier to express in an efficient way when we use side effects, so computing speed can be a reason to avoid purity.

+ +

Summary

+ +

This chapter taught you how to write your own functions. The function keyword, when used as an expression, can create a function value. When used as a statement, it can be used to declare a binding and give it a function as its value. Arrow functions are yet another way to create functions.

+ +
// Define f to hold a function value
+const f = function(a) {
+  console.log(a + 2);
+};
+
+// Declare g to be a function
+function g(a, b) {
+  return a * b * 3.5;
+}
+
+// A less verbose function value
+let h = a => a % 3;
+ +

A key aspect in understanding functions is understanding scopes. Each block creates a new scope. Parameters and bindings declared in a given scope are local and not visible from the outside. Bindings declared with var behave differently—they end up in the nearest function scope or the global scope.

+ +

Separating the tasks your program performs into different functions is helpful. You won’t have to repeat yourself as much, and functions can help organize a program by grouping code into pieces that do specific things.

+ +

Exercises

+ +

Minimum

+ +

The previous chapter introduced the standard function Math.min that returns its smallest argument. We can build something like that now. Write a function min that takes two arguments and returns their minimum.

+ +
// Your code here.
+
+console.log(min(0, 10));
+// → 0
+console.log(min(0, -10));
+// → -10
+ +
+ +

If you have trouble putting braces and parentheses in the right place to get a valid function definition, start by copying one of the examples in this chapter and modifying it.

+ +

A function may contain multiple return statements.

+ +
+ +

Recursion

+ +

We’ve seen that % (the remainder operator) can be used to test whether a number is even or odd by using % 2 to see whether it’s divisible by two. Here’s another way to define whether a positive whole number is even or odd:

+ +
    + +
  • + +

    Zero is even.

  • + +
  • + +

    One is odd.

  • + +
  • + +

    For any other number N, its evenness is the same as N - 2.

+ +

Define a recursive function isEven corresponding to this description. The function should accept a single parameter (a positive, whole number) and return a Boolean.

+ +

Test it on 50 and 75. See how it behaves on -1. Why? Can you think of a way to fix this?

+ +
// Your code here.
+
+console.log(isEven(50));
+// → true
+console.log(isEven(75));
+// → false
+console.log(isEven(-1));
+// → ??
+ +
+ +

Your function will likely look somewhat similar to the inner find function in the recursive findSolution example in this chapter, with an if/else if/else chain that tests which of the three cases applies. The final else, corresponding to the third case, makes the recursive call. Each of the branches should contain a return statement or in some other way arrange for a specific value to be returned.

+ +

When given a negative number, the function will recurse again and again, passing itself an ever more negative number, thus getting further and further away from returning a result. It will eventually run out of stack space and abort.

+ +
+ +

Bean counting

+ +

You can get the Nth character, or letter, from a string by writing "string"[N]. The returned value will be a string containing only one character (for example, "b"). The first character has position 0, which causes the last one to be found at position string.length - 1. In other words, a two-character string has length 2, and its characters have positions 0 and 1.

+ +

Write a function countBs that takes a string as its only argument and returns a number that indicates how many uppercase “B” characters there are in the string.

+ +

Next, write a function called countChar that behaves like countBs, except it takes a second argument that indicates the character that is to be counted (rather than counting only uppercase “B” characters). Rewrite countBs to make use of this new function.

+ +
// Your code here.
+
+console.log(countBs("BBC"));
+// → 2
+console.log(countChar("kakkerlak", "k"));
+// → 4
+ +
+ +

Your function will need a loop that looks at every character in the string. It can run an index from zero to one below its length (< string.length). If the character at the current position is the same as the one the function is looking for, it adds 1 to a counter variable. Once the loop has finished, the counter can be returned.

+ +

Take care to make all the bindings used in the function local to the function by properly declaring them with the let or const keyword.

+ +
+
diff --git a/docs/04_data.html b/docs/04_data.html new file mode 100644 index 000000000..2d2e07ffc --- /dev/null +++ b/docs/04_data.html @@ -0,0 +1,794 @@ + + + + + Data Structures: Objects and Arrays :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 4Data Structures: Objects and Arrays

+ +
+ +

On two occasions I have been asked, ‘Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?’ [...] I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.

+ +
Charles Babbage, Passages from the Life of a Philosopher (1864)
+ +
Picture of a weresquirrel
+ +

Numbers, Booleans, and strings are the atoms that data structures are built from. Many types of information require more than one atom, though. Objects allow us to group values—including other objects—to build more complex structures.

+ +

The programs we have built so far have been limited by the fact that they were operating only on simple data types. This chapter will introduce basic data structures. By the end of it, you’ll know enough to start writing useful programs.

+ +

The chapter will work through a more or less realistic programming example, introducing concepts as they apply to the problem at hand. The example code will often build on functions and bindings that were introduced earlier in the text.

+ +

The weresquirrel

+ +

Every now and then, usually between 8 p.m. and 10 p.m., Jacques finds himself transforming into a small furry rodent with a bushy tail.

+ +

On one hand, Jacques is quite glad that he doesn’t have classic lycanthropy. Turning into a squirrel does cause fewer problems than turning into a wolf. Instead of having to worry about accidentally eating the neighbor (that would be awkward), he worries about being eaten by the neighbor’s cat. After two occasions where he woke up on a precariously thin branch in the crown of an oak, naked and disoriented, he has taken to locking the doors and windows of his room at night and putting a few walnuts on the floor to keep himself busy.

+ +

That takes care of the cat and tree problems. But Jacques would prefer to get rid of his condition entirely. The irregular occurrences of the transformation make him suspect that they might be triggered by something. For a while, he believed that it happened only on days when he had been near oak trees. But avoiding oak trees did not stop the problem.

+ +

Switching to a more scientific approach, Jacques has started keeping a daily log of everything he does on a given day and whether he changed form. With this data he hopes to narrow down the conditions that trigger the transformations.

+ +

The first thing he needs is a data structure to store this information.

+ +

Data sets

+ +

To work with a chunk of digital data, we’ll first have to find a way to represent it in our machine’s memory. Say, for example, that we want to represent a collection of the numbers 2, 3, 5, 7, and 11.

+ +

We could get creative with strings—after all, strings can have any length, so we can put a lot of data into them—and use "2 3 5 7 11" as our representation. But this is awkward. You’d have to somehow extract the digits and convert them back to numbers to access them.

+ +

Fortunately, JavaScript provides a data type specifically for storing sequences of values. It is called an array and is written as a list of values between square brackets, separated by commas.

+ +
let listOfNumbers = [2, 3, 5, 7, 11];
+console.log(listOfNumbers[2]);
+// → 5
+console.log(listOfNumbers[0]);
+// → 2
+console.log(listOfNumbers[2 - 1]);
+// → 3
+ +

The notation for getting at the elements inside an array also uses square brackets. A pair of square brackets immediately after an expression, with another expression inside of them, will look up the element in the left-hand expression that corresponds to the index given by the expression in the brackets.

+ +

The first index of an array is zero, not one. So the first element is retrieved with listOfNumbers[0]. Zero-based counting has a long tradition in technology and in certain ways makes a lot of sense, but it takes some getting used to. Think of the index as the amount of items to skip, counting from the start of the array.

+ +

Properties

+ +

We’ve seen a few suspicious-looking expressions like myString.length (to get the length of a string) and Math.max (the maximum function) in past chapters. These are expressions that access a property of some value. In the first case, we access the length property of the value in myString. In the second, we access the property named max in the Math object (which is a collection of mathematics-related constants and functions).

+ +

Almost all JavaScript values have properties. The exceptions are null and undefined. If you try to access a property on one of these nonvalues, you get an error.

+ +
null.length;
+// → TypeError: null has no properties
+ +

The two main ways to access properties in JavaScript are with a dot and with square brackets. Both value.x and value[x] access a property on value—but not necessarily the same property. The difference is in how x is interpreted. When using a dot, the word after the dot is the literal name of the property. When using square brackets, the expression between the brackets is evaluated to get the property name. Whereas value.x fetches the property of value named “x”, value[x] tries to evaluate the expression x and uses the result, converted to a string, as the property name.

+ +

So if you know that the property you are interested in is called color, you say value.color. If you want to extract the property named by the value held in the binding i, you say value[i]. Property names are strings. They can be any string, but the dot notation works only with names that look like valid binding names. So if you want to access a property named 2 or John Doe, you must use square brackets: value[2] or value["John Doe"].

+ +

The elements in an array are stored as the array’s properties, using numbers as property names. Because you can’t use the dot notation with numbers and usually want to use a binding that holds the index anyway, you have to use the bracket notation to get at them.

+ +

The length property of an array tells us how many elements it has. This property name is a valid binding name, and we know its name in advance, so to find the length of an array, you typically write array.length because that’s easier to write than array["length"].

+ +

Methods

+ +

Both string and array objects contain, in addition to the length property, a number of properties that hold function values.

+ +
let doh = "Doh";
+console.log(typeof doh.toUpperCase);
+// → function
+console.log(doh.toUpperCase());
+// → DOH
+ +

Every string has a toUpperCase property. When called, it will return a copy of the string in which all letters have been converted to uppercase. There is also toLowerCase, going the other way.

+ +

Interestingly, even though the call to toUpperCase does not pass any arguments, the function somehow has access to the string "Doh", the value whose property we called. How this works is described in Chapter 6.

+ +

Properties that contain functions are generally called methods of the value they belong to, as in “toUpperCase is a method of a string”.

+ +

This example demonstrates two methods you can use to manipulate arrays:

+ +
let sequence = [1, 2, 3];
+sequence.push(4);
+sequence.push(5);
+console.log(sequence);
+// → [1, 2, 3, 4, 5]
+console.log(sequence.pop());
+// → 5
+console.log(sequence);
+// → [1, 2, 3, 4]
+ +

The push method adds values to the end of an array, and the pop method does the opposite, removing the last value in the array and returning it.

+ +

These somewhat silly names are the traditional terms for operations on a stack. A stack, in programming, is a data structure that allows you to push values into it and pop them out again in the opposite order so that the thing that was added last is removed first. These are common in programming—you might remember the function call +stack from the previous chapter, which is an instance of the same idea.

+ +

Objects

+ +

Back to the weresquirrel. A set of daily log entries can be represented as an array. But the entries do not consist of just a number or a string—each entry needs to store a list of activities and a Boolean value that indicates whether Jacques turned into a squirrel or not. Ideally, we would like to group these together into a single value and then put those grouped values into an array of log entries.

+ +

Values of the type object are arbitrary collections of properties. One way to create an object is by using braces as an expression.

+ +
let day1 = {
+  squirrel: false,
+  events: ["work", "touched tree", "pizza", "running"]
+};
+console.log(day1.squirrel);
+// → false
+console.log(day1.wolf);
+// → undefined
+day1.wolf = false;
+console.log(day1.wolf);
+// → false
+ +

Inside the braces, there is a list of properties separated by commas. Each property has a name followed by a colon and a value. When an object is written over multiple lines, indenting it like in the example helps with readability. Properties whose names aren’t valid binding names or valid numbers have to be quoted.

+ +
let descriptions = {
+  work: "Went to work",
+  "touched tree": "Touched a tree"
+};
+ +

This means that braces have two meanings in JavaScript. At the start of a statement, they start a block of statements. In any other position, they describe an object. Fortunately, it is rarely useful to start a statement with an object in braces, so the ambiguity between these two is not much of a problem.

+ +

Reading a property that doesn’t exist will give you the value undefined.

+ +

It is possible to assign a value to a property expression with the = operator. This will replace the property’s value if it already existed or create a new property on the object if it didn’t.

+ +

To briefly return to our tentacle model of bindings—property bindings are similar. They grasp values, but other bindings and properties might be holding onto those same values. You may think of objects as octopuses with any number of tentacles, each of which has a name tattooed on it.

+ +

The delete operator cuts off a tentacle from such an octopus. It is a unary operator that, when applied to an object property, will remove the named property from the object. This is not a common thing to do, but it is possible.

+ +
let anObject = {left: 1, right: 2};
+console.log(anObject.left);
+// → 1
+delete anObject.left;
+console.log(anObject.left);
+// → undefined
+console.log("left" in anObject);
+// → false
+console.log("right" in anObject);
+// → true
+ +

The binary in operator, when applied to a string and an object, tells you whether that object has a property with that name. The difference between setting a property to undefined and actually deleting it is that, in the first case, the object still has the property (it just doesn’t have a very interesting value), whereas in the second case the property is no longer present and in will return false.

+ +

To find out what properties an object has, you can use the Object.keys function. You give it an object, and it returns an array of strings—the object’s property names.

+ +
console.log(Object.keys({x: 0, y: 0, z: 2}));
+// → ["x", "y", "z"]
+ +

There’s an Object.assign function that copies all properties from one object into another.

+ +
let objectA = {a: 1, b: 2};
+Object.assign(objectA, {b: 3, c: 4});
+console.log(objectA);
+// → {a: 1, b: 3, c: 4}
+ +

Arrays, then, are just a kind of object specialized for storing sequences of things. If you evaluate typeof [], it produces "object". You can see them as long, flat octopuses with all their tentacles in a neat row, labeled with numbers.

+ +

We will represent the journal that Jacques keeps as an array of objects.

+ +
let journal = [
+  {events: ["work", "touched tree", "pizza",
+            "running", "television"],
+   squirrel: false},
+  {events: ["work", "ice cream", "cauliflower",
+            "lasagna", "touched tree", "brushed teeth"],
+   squirrel: false},
+  {events: ["weekend", "cycling", "break", "peanuts",
+            "beer"],
+   squirrel: true},
+  /* and so on... */
+];
+ +

Mutability

+ +

We will get to actual programming real soon now. First there’s one more piece of theory to understand.

+ +

We saw that object values can be modified. The types of values discussed in earlier chapters, such as numbers, strings, and Booleans, are all immutable—it is impossible to change values of those types. You can combine them and derive new values from them, but when you take a specific string value, that value will always remain the same. The text inside it cannot be changed. If you have a string that contains "cat", it is not possible for other code to change a character in your string to make it spell "rat".

+ +

Objects work differently. You can change their properties, causing a single object value to have different content at different times.

+ +

When we have two numbers, 120 and 120, we can consider them precisely the same number, whether or not they refer to the same physical bits. With objects, there is a difference between having two references to the same object and having two different objects that contain the same properties. Consider the following code:

+ +
let object1 = {value: 10};
+let object2 = object1;
+let object3 = {value: 10};
+
+console.log(object1 == object2);
+// → true
+console.log(object1 == object3);
+// → false
+
+object1.value = 15;
+console.log(object2.value);
+// → 15
+console.log(object3.value);
+// → 10
+ +

The object1 and object2 bindings grasp the same object, which is why changing object1 also changes the value of object2. They are said to have the same identity. The binding object3 points to a different object, which initially contains the same properties as object1 but lives a separate life.

+ +

Bindings can also be changeable or constant, but this is separate from the way their values behave. Even though number values don’t change, you can use a let binding to keep track of a changing number by changing the value the binding points at. Similarly, though a const binding to an object can itself not be changed and will continue to point at the same object, the contents of that object might change.

+ +
const score = {visitors: 0, home: 0};
+// This is okay
+score.visitors = 1;
+// This isn't allowed
+score = {visitors: 1, home: 1};
+ +

When you compare objects with JavaScript’s == operator, it compares by identity: it will produce true only if both objects are precisely the same value. Comparing different objects will return false, even if they have identical properties. There is no “deep” comparison operation built into JavaScript, which compares objects by contents, but it is possible to write it yourself (which is one of the exercises at the end of this chapter).

+ +

The lycanthrope’s log

+ +

So, Jacques starts up his JavaScript interpreter and sets up the environment he needs to keep his journal.

+ +
let journal = [];
+
+function addEntry(events, squirrel) {
+  journal.push({events, squirrel});
+}
+ +

Note that the object added to the journal looks a little odd. Instead of declaring properties like events: events, it just gives a property name. This is shorthand that means the same thing—if a property name in brace notation isn’t followed by a value, its value is taken from the binding with the same name.

+ +

So then, every evening at 10 p.m.—or sometimes the next morning, after climbing down from the top shelf of his bookcase—Jacques records the day.

+ +
addEntry(["work", "touched tree", "pizza", "running",
+          "television"], false);
+addEntry(["work", "ice cream", "cauliflower", "lasagna",
+          "touched tree", "brushed teeth"], false);
+addEntry(["weekend", "cycling", "break", "peanuts",
+          "beer"], true);
+ +

Once he has enough data points, he intends to use statistics to find out which of these events may be related to the squirrelifications.

+ +

Correlation is a measure of dependence between statistical variables. A statistical variable is not quite the same as a programming variable. In statistics you typically have a set of measurements, and each variable is measured for every measurement. Correlation between variables is usually expressed as a value that ranges from -1 to 1. Zero correlation means the variables are not related. A correlation of one indicates that the two are perfectly related—if you know one, you also know the other. Negative one also means that the variables are perfectly related but that they are opposites—when one is true, the other is false.

+ +

To compute the measure of correlation between two Boolean variables, we can use the phi coefficient (ϕ). This is a formula whose input is a frequency table containing the number of times the different combinations of the variables were observed. The output of the formula is a number between -1 and 1 that describes the correlation.

+ +

We could take the event of eating pizza and put that in a frequency table like this, where each number indicates the amount of times that combination occurred in our measurements:

Eating pizza versus turning into a squirrel
+ +

If we call that table n, we can compute ϕ using the following formula:

+ + + +
ϕ = +
n11n00 − + n10n01
+
+ n1•n0•n•1n•0 +
+
+
+ + +

(If at this point you’re putting the book down to focus on a terrible flashback to 10th grade math class—hold on! I do not intend to torture you with endless pages of cryptic notation—it’s just this one formula for now. And even with this one, all we do is turn it into JavaScript.)

+ +

The notation n01 indicates the number of measurements where the first variable (squirrelness) is false (0) and the second variable (pizza) is true (1). In the pizza table, n01 is 9.

+ +

The value n1• refers to the sum of all measurements where the first variable is true, which is 5 in the example table. Likewise, n•0 refers to the sum of the measurements where the second variable is false.

+ +

So for the pizza table, the part above the division line (the dividend) would be 1×76−4×9 = 40, and the part below it (the divisor) would be the square root of 5×85×10×80, or √340000. This comes out to ϕ ≈ 0.069, which is tiny. Eating pizza does not appear to have influence on the transformations.

+ +

Computing correlation

+ +

We can represent a two-by-two table in JavaScript with a four-element array ([76, 9, 4, 1]). We could also use other representations, such as an array containing two two-element arrays ([[76, 9], [4, 1]]) or an object with property names like "11" and "01", but the flat array is simple and makes the expressions that access the table pleasantly short. We’ll interpret the indices to the array as two-bit binary numbers, where the leftmost (most significant) digit refers to the squirrel variable and the rightmost (least significant) digit refers to the event variable. For example, the binary number 10 refers to the case where Jacques did turn into a squirrel, but the event (say, “pizza”) didn’t occur. This happened four times. And since binary 10 is 2 in decimal notation, we will store this number at index 2 of the array.

+ +

This is the function that computes the ϕ coefficient from such an array:

+ +
function phi(table) {
+  return (table[3] * table[0] - table[2] * table[1]) /
+    Math.sqrt((table[2] + table[3]) *
+              (table[0] + table[1]) *
+              (table[1] + table[3]) *
+              (table[0] + table[2]));
+}
+
+console.log(phi([76, 9, 4, 1]));
+// → 0.068599434
+ +

This is a direct translation of the ϕ formula into JavaScript. Math.sqrt is the square root function, as provided by the Math object in a standard JavaScript environment. We have to add two fields from the table to get fields like n1• because the sums of rows or columns are not stored directly in our data structure.

+ +

Jacques kept his journal for three months. The resulting data set is available in the coding sandbox for this chapter, where it is stored in the JOURNAL binding and in a downloadable file.

+ +

To extract a two-by-two table for a specific event from the journal, we must loop over all the entries and tally how many times the event occurs in relation to squirrel transformations.

+ +
function tableFor(event, journal) {
+  let table = [0, 0, 0, 0];
+  for (let i = 0; i < journal.length; i++) {
+    let entry = journal[i], index = 0;
+    if (entry.events.includes(event)) index += 1;
+    if (entry.squirrel) index += 2;
+    table[index] += 1;
+  }
+  return table;
+}
+
+console.log(tableFor("pizza", JOURNAL));
+// → [76, 9, 4, 1]
+ +

Arrays have an includes method that checks whether a given value exists in the array. The function uses that to determine whether the event name it is interested in is part of the event list for a given day.

+ +

The body of the loop in tableFor figures out which box in the table each journal entry falls into by checking whether the entry contains the specific event it’s interested in and whether the event happens alongside a squirrel incident. The loop then adds one to the correct box in the table.

+ +

We now have the tools we need to compute individual correlations. The only step remaining is to find a correlation for every type of event that was recorded and see whether anything stands out.

+ +

Array loops

+ +

In the tableFor function, there’s a loop like this:

+ +
for (let i = 0; i < JOURNAL.length; i++) {
+  let entry = JOURNAL[i];
+  // Do something with entry
+}
+ +

This kind of loop is common in classical JavaScript—going over arrays one element at a time is something that comes up a lot, and to do that you’d run a counter over the length of the array and pick out each element in turn.

+ +

There is a simpler way to write such loops in modern JavaScript.

+ +
for (let entry of JOURNAL) {
+  console.log(`${entry.events.length} events.`);
+}
+ +

When a for loop looks like this, with the word of after a variable definition, it will loop over the elements of the value given after of. This works not only for arrays but also for strings and some other data structures. We’ll discuss how it works in Chapter 6.

+ +

The final analysis

+ +

We need to compute a correlation for every type of event that occurs in the data set. To do that, we first need to find every type of event.

+ +
function journalEvents(journal) {
+  let events = [];
+  for (let entry of journal) {
+    for (let event of entry.events) {
+      if (!events.includes(event)) {
+        events.push(event);
+      }
+    }
+  }
+  return events;
+}
+
+console.log(journalEvents(JOURNAL));
+// → ["carrot", "exercise", "weekend", "bread", …]
+ +

By going over all the events and adding those that aren’t already in there to the events array, the function collects every type of event.

+ +

Using that, we can see all the correlations.

+ +
for (let event of journalEvents(JOURNAL)) {
+  console.log(event + ":", phi(tableFor(event, JOURNAL)));
+}
+// → carrot:   0.0140970969
+// → exercise: 0.0685994341
+// → weekend:  0.1371988681
+// → bread:   -0.0757554019
+// → pudding: -0.0648203724
+// and so on...
+ +

Most correlations seem to lie close to zero. Eating carrots, bread, or pudding apparently does not trigger squirrel-lycanthropy. It does seem to occur somewhat more often on weekends. Let’s filter the results to show only correlations greater than 0.1 or less than -0.1.

+ +
for (let event of journalEvents(JOURNAL)) {
+  let correlation = phi(tableFor(event, JOURNAL));
+  if (correlation > 0.1 || correlation < -0.1) {
+    console.log(event + ":", correlation);
+  }
+}
+// → weekend:        0.1371988681
+// → brushed teeth: -0.3805211953
+// → candy:          0.1296407447
+// → work:          -0.1371988681
+// → spaghetti:      0.2425356250
+// → reading:        0.1106828054
+// → peanuts:        0.5902679812
+ +

Aha! There are two factors with a correlation that’s clearly stronger than the others. Eating peanuts has a strong positive effect on the chance of turning into a squirrel, whereas brushing his teeth has a significant negative effect.

+ +

Interesting. Let’s try something.

+ +
for (let entry of JOURNAL) {
+  if (entry.events.includes("peanuts") &&
+     !entry.events.includes("brushed teeth")) {
+    entry.events.push("peanut teeth");
+  }
+}
+console.log(phi(tableFor("peanut teeth", JOURNAL)));
+// → 1
+ +

That’s a strong result. The phenomenon occurs precisely when Jacques eats peanuts and fails to brush his teeth. If only he weren’t such a slob about dental hygiene, he’d have never even noticed his affliction.

+ +

Knowing this, Jacques stops eating peanuts altogether and finds that his transformations don’t come back.

+ +

For a few years, things go great for Jacques. But at some point he loses his job. Because he lives in a nasty country where having no job means having no medical services, he is forced to take employment with a circus where he performs as The Incredible Squirrelman, stuffing his mouth with peanut butter before every show.

+ +

One day, fed up with this pitiful existence, Jacques fails to change back into his human form, hops through a crack in the circus tent, and vanishes into the forest. He is never seen again.

+ +

Further arrayology

+ +

Before finishing the chapter, I want to introduce you to a few more object-related concepts. I’ll start by introducing some generally useful array methods.

+ +

We saw push and pop, which add and remove elements at the end of an array, earlier in this chapter. The corresponding methods for adding and removing things at the start of an array are called unshift and shift.

+ +
let todoList = [];
+function remember(task) {
+  todoList.push(task);
+}
+function getTask() {
+  return todoList.shift();
+}
+function rememberUrgently(task) {
+  todoList.unshift(task);
+}
+ +

That program manages a queue of tasks. You add tasks to the end of the queue by calling remember("groceries"), and when you’re ready to do something, you call getTask() to get (and remove) the front item from the queue. The rememberUrgently function also adds a task but adds it to the front instead of the back of the queue.

+ +

To search for a specific value, arrays provide an indexOf method. The method searches through the array from the start to the end and returns the index at which the requested value was found—or -1 if it wasn’t found. To search from the end instead of the start, there’s a similar method called lastIndexOf.

+ +
console.log([1, 2, 3, 2, 1].indexOf(2));
+// → 1
+console.log([1, 2, 3, 2, 1].lastIndexOf(2));
+// → 3
+ +

Both indexOf and lastIndexOf take an optional second argument that indicates where to start searching.

+ +

Another fundamental array method is slice, which takes start and end indices and returns an array that has only the elements between them. The start index is inclusive, the end index exclusive.

+ +
console.log([0, 1, 2, 3, 4].slice(2, 4));
+// → [2, 3]
+console.log([0, 1, 2, 3, 4].slice(2));
+// → [2, 3, 4]
+ +

When the end index is not given, slice will take all of the elements after the start index. You can also omit the start index to copy the entire array.

+ +

The concat method can be used to glue arrays together to create a new array, similar to what the + operator does for strings.

+ +

The following example shows both concat and slice in action. It takes an array and an index, and it returns a new array that is a copy of the original array with the element at the given index removed.

+ +
function remove(array, index) {
+  return array.slice(0, index)
+    .concat(array.slice(index + 1));
+}
+console.log(remove(["a", "b", "c", "d", "e"], 2));
+// → ["a", "b", "d", "e"]
+ +

If you pass concat an argument that is not an array, that value will be added to the new array as if it were a one-element array.

+ +

Strings and their properties

+ +

We can read properties like length and toUpperCase from string values. But if you try to add a new property, it doesn’t stick.

+ +
let kim = "Kim";
+kim.age = 88;
+console.log(kim.age);
+// → undefined
+ +

Values of type string, number, and Boolean are not objects, and though the language doesn’t complain if you try to set new properties on them, it doesn’t actually store those properties. As mentioned earlier, such values are immutable and cannot be changed.

+ +

But these types do have built-in properties. Every string value has a number of methods. Some very useful ones are slice and indexOf, which resemble the array methods of the same name.

+ +
console.log("coconuts".slice(4, 7));
+// → nut
+console.log("coconut".indexOf("u"));
+// → 5
+ +

One difference is that a string’s indexOf can search for a string containing more than one character, whereas the corresponding array method looks only for a single element.

+ +
console.log("one two three".indexOf("ee"));
+// → 11
+ +

The trim method removes whitespace (spaces, newlines, tabs, and similar characters) from the start and end of a string.

+ +
console.log("  okay \n ".trim());
+// → okay
+ +

The zeroPad function from the previous chapter also exists as a method. It is called padStart and takes the desired length and padding character as arguments.

+ +
console.log(String(6).padStart(3, "0"));
+// → 006
+ +

You can split a string on every occurrence of another string with split and join it again with join.

+ +
let sentence = "Secretarybirds specialize in stomping";
+let words = sentence.split(" ");
+console.log(words);
+// → ["Secretarybirds", "specialize", "in", "stomping"]
+console.log(words.join(". "));
+// → Secretarybirds. specialize. in. stomping
+ +

A string can be repeated with the repeat method, which creates a new string containing multiple copies of the original string, glued together.

+ +
console.log("LA".repeat(3));
+// → LALALA
+ +

We have already seen the string type’s length property. Accessing the individual characters in a string looks like accessing array elements (with a caveat that we’ll discuss in Chapter 5).

+ +
let string = "abc";
+console.log(string.length);
+// → 3
+console.log(string[1]);
+// → b
+ +

Rest parameters

+ +

It can be useful for a function to accept any number of arguments. For example, Math.max computes the maximum of all the arguments it is given.

+ +

To write such a function, you put three dots before the function’s last parameter, like this:

+ +
function max(...numbers) {
+  let result = -Infinity;
+  for (let number of numbers) {
+    if (number > result) result = number;
+  }
+  return result;
+}
+console.log(max(4, 1, 9, -2));
+// → 9
+ +

When such a function is called, the rest parameter is bound to an array containing all further arguments. If there are other parameters before it, their values aren’t part of that array. When, as in max, it is the only parameter, it will hold all arguments.

+ +

You can use a similar three-dot notation to call a function with an array of arguments.

+ +
let numbers = [5, 1, 7];
+console.log(max(...numbers));
+// → 7
+ +

This “spreads” out the array into the function call, passing its elements as separate arguments. It is possible to include an array like that along with other arguments, as in max(9, ...numbers, 2).

+ +

Square bracket array notation similarly allows the triple-dot operator to spread another array into the new array.

+ +
let words = ["never", "fully"];
+console.log(["will", ...words, "understand"]);
+// → ["will", "never", "fully", "understand"]
+ +

The Math object

+ +

As we’ve seen, Math is a grab bag of number-related utility functions, such as Math.max (maximum), Math.min (minimum), and Math.sqrt (square root).

+ +

The Math object is used as a container to group a bunch of related functionality. There is only one Math object, and it is almost never useful as a value. Rather, it provides a namespace so that all these functions and values do not have to be global bindings.

+ +

Having too many global bindings “pollutes” the namespace. The more names have been taken, the more likely you are to accidentally overwrite the value of some existing binding. For example, it’s not unlikely to want to name something max in one of your programs. Since JavaScript’s built-in max function is tucked safely inside the Math object, we don’t have to worry about overwriting it.

+ +

Many languages will stop you, or at least warn you, when you are defining a binding with a name that is already taken. JavaScript does this for bindings you declared with let or const but—perversely—not for standard bindings nor for bindings declared with var or function.

+ +

Back to the Math object. If you need to do trigonometry, Math can help. It contains cos (cosine), sin (sine), and tan (tangent), as well as their inverse functions, acos, asin, and atan, respectively. The number π (pi)—or at least the closest approximation that fits in a JavaScript number—is available as Math.PI. There is an old programming tradition of writing the names of constant values in all caps.

+ +
function randomPointOnCircle(radius) {
+  let angle = Math.random() * 2 * Math.PI;
+  return {x: radius * Math.cos(angle),
+          y: radius * Math.sin(angle)};
+}
+console.log(randomPointOnCircle(2));
+// → {x: 0.3667, y: 1.966}
+ +

If sines and cosines are not something you are familiar with, don’t worry. When they are used in this book, in Chapter 14, I’ll explain them.

+ +

The previous example used Math.random. This is a function that returns a new pseudorandom number between zero (inclusive) and one (exclusive) every time you call it.

+ +
console.log(Math.random());
+// → 0.36993729369714856
+console.log(Math.random());
+// → 0.727367032552138
+console.log(Math.random());
+// → 0.40180766698904335
+ +

Though computers are deterministic machines—they always react the same way if given the same input—it is possible to have them produce numbers that appear random. To do that, the machine keeps some hidden value, and whenever you ask for a new random number, it performs complicated computations on this hidden value to create a new value. It stores a new value and returns some number derived from it. That way, it can produce ever new, hard-to-predict numbers in a way that seems random.

+ +

If we want a whole random number instead of a fractional one, we can use Math.floor (which rounds down to the nearest whole number) on the result of Math.random.

+ +
console.log(Math.floor(Math.random() * 10));
+// → 2
+ +

Multiplying the random number by 10 gives us a number greater than or equal to 0 and below 10. Since Math.floor rounds down, this expression will produce, with equal chance, any number from 0 through 9.

+ +

There are also the functions Math.ceil (for “ceiling”, which rounds up to a whole number), Math.round (to the nearest whole number), and Math.abs, which takes the absolute value of a number, meaning it negates negative values but leaves positive ones as they are.

+ +

Destructuring

+ +

Let’s go back to the phi function for a moment.

+ +
function phi(table) {
+  return (table[3] * table[0] - table[2] * table[1]) /
+    Math.sqrt((table[2] + table[3]) *
+              (table[0] + table[1]) *
+              (table[1] + table[3]) *
+              (table[0] + table[2]));
+}
+ +

One of the reasons this function is awkward to read is that we have a binding pointing at our array, but we’d much prefer to have bindings for the elements of the array, that is, let n00 = table[0] and so on. Fortunately, there is a succinct way to do this in JavaScript.

+ +
function phi([n00, n01, n10, n11]) {
+  return (n11 * n00 - n10 * n01) /
+    Math.sqrt((n10 + n11) * (n00 + n01) *
+              (n01 + n11) * (n00 + n10));
+}
+ +

This also works for bindings created with let, var, or const. If you know the value you are binding is an array, you can use square brackets to “look inside” of the value, binding its contents.

+ +

A similar trick works for objects, using braces instead of square brackets.

+ +
let {name} = {name: "Faraji", age: 23};
+console.log(name);
+// → Faraji
+ +

Note that if you try to destructure null or undefined, you get an error, much as you would if you directly try to access a property of those values.

+ +

JSON

+ +

Because properties only grasp their value, rather than contain it, objects and arrays are stored in the computer’s memory as sequences of bits holding the addresses—the place in memory—of their contents. So an array with another array inside of it consists of (at least) one memory region for the inner array, and another for the outer array, containing (among other things) a binary number that represents the position of the inner array.

+ +

If you want to save data in a file for later or send it to another computer over the network, you have to somehow convert these tangles of memory addresses to a description that can be stored or sent. You could send over your entire computer memory along with the address of the value you’re interested in, I suppose, but that doesn’t seem like the best approach.

+ +

What we can do is serialize the data. That means it is converted into a flat description. A popular serialization format is called JSON (pronounced “Jason”), which stands for JavaScript Object Notation. It is widely used as a data storage and communication format on the Web, even in languages other than JavaScript.

+ +

JSON looks similar to JavaScript’s way of writing arrays and objects, with a few restrictions. All property names have to be surrounded by double quotes, and only simple data expressions are allowed—no function calls, bindings, or anything that involves actual computation. Comments are not allowed in JSON.

+ +

A journal entry might look like this when represented as JSON data:

+ +
{
+  "squirrel": false,
+  "events": ["work", "touched tree", "pizza", "running"]
+}
+ +

JavaScript gives us the functions JSON.stringify and JSON.parse to convert data to and from this format. The first takes a JavaScript value and returns a JSON-encoded string. The second takes such a string and converts it to the value it encodes.

+ +
let string = JSON.stringify({squirrel: false,
+                             events: ["weekend"]});
+console.log(string);
+// → {"squirrel":false,"events":["weekend"]}
+console.log(JSON.parse(string).events);
+// → ["weekend"]
+ +

Summary

+ +

Objects and arrays (which are a specific kind of object) provide ways to group several values into a single value. Conceptually, this allows us to put a bunch of related things in a bag and run around with the bag, instead of wrapping our arms around all of the individual things and trying to hold on to them separately.

+ +

Most values in JavaScript have properties, the exceptions being null and undefined. Properties are accessed using value.prop or value["prop"]. Objects tend to use names for their properties and store more or less a fixed set of them. Arrays, on the other hand, usually contain varying amounts of conceptually identical values and use numbers (starting from 0) as the names of their properties.

+ +

There are some named properties in arrays, such as length and a number of methods. Methods are functions that live in properties and (usually) act on the value they are a property of.

+ +

You can iterate over arrays using a special kind of for loop—for (let element of array).

+ +

Exercises

+ +

The sum of a range

+ +

The introduction of this book alluded to the following as a nice way to compute the sum of a range of numbers:

+ +
console.log(sum(range(1, 10)));
+ +

Write a range function that takes two arguments, start and end, and returns an array containing all the numbers from start up to (and including) end.

+ +

Next, write a sum function that takes an array of numbers and returns the sum of these numbers. Run the example program and see whether it does indeed return 55.

+ +

As a bonus assignment, modify your range function to take an optional third argument that indicates the “step” value used when building the array. If no step is given, the elements go up by increments of one, corresponding to the old behavior. The function call range(1, 10, 2) should return [1, 3, 5, 7, 9]. Make sure it also works with negative step values so that range(5, 2, -1) produces [5, 4, 3, 2].

+ +
// Your code here.
+
+console.log(range(1, 10));
+// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+console.log(range(5, 2, -1));
+// → [5, 4, 3, 2]
+console.log(sum(range(1, 10)));
+// → 55
+ +
+ +

Building up an array is most easily done by first initializing a binding to [] (a fresh, empty array) and repeatedly calling its push method to add a value. Don’t forget to return the array at the end of the function.

+ +

Since the end boundary is inclusive, you’ll need to use the <= operator rather than < to check for the end of your loop.

+ +

The step parameter can be an optional parameter that defaults (using the = operator) to 1.

+ +

Having range understand negative step values is probably best done by writing two separate loops—one for counting up and one for counting down—because the comparison that checks whether the loop is finished needs to be >= rather than <= when counting downward.

+ +

It might also be worthwhile to use a different default step, namely, -1, when the end of the range is smaller than the start. That way, range(5, 2) returns something meaningful, rather than getting stuck in an infinite loop. It is possible to refer to previous parameters in the default value of a parameter.

+ +
+ +

Reversing an array

+ +

Arrays have a reverse method that changes the array by inverting the order in which its elements appear. For this exercise, write two functions, reverseArray and reverseArrayInPlace. The first, reverseArray, takes an array as argument and produces a new array that has the same elements in the inverse order. The second, reverseArrayInPlace, does what the reverse method does: it modifies the array given as argument by reversing its elements. Neither may use the standard reverse method.

+ +

Thinking back to the notes about side effects and pure functions in the previous chapter, which variant do you expect to be useful in more situations? Which one runs faster?

+ +
// Your code here.
+
+console.log(reverseArray(["A", "B", "C"]));
+// → ["C", "B", "A"];
+let arrayValue = [1, 2, 3, 4, 5];
+reverseArrayInPlace(arrayValue);
+console.log(arrayValue);
+// → [5, 4, 3, 2, 1]
+ +
+ +

There are two obvious ways to implement reverseArray. The first is to simply go over the input array from front to back and use the unshift method on the new array to insert each element at its start. The second is to loop over the input array backwards and use the push method. Iterating over an array backwards requires a (somewhat awkward) for specification, like (let i = array.length - 1; i >= 0; i--).

+ +

Reversing the array in place is harder. You have to be careful not to overwrite elements that you will later need. Using reverseArray or otherwise copying the whole array (array.slice(0) is a good way to copy an array) works but is cheating.

+ +

The trick is to swap the first and last elements, then the second and second-to-last, and so on. You can do this by looping over half the length of the array (use Math.floor to round down—you don’t need to touch the middle element in an array with an odd number of elements) and swapping the element at position i with the one at position array.length - 1 - i. You can use a local binding to briefly hold on to one of the elements, overwrite that one with its mirror image, and then put the value from the local binding in the place where the mirror image used to be.

+ +
+ +

A list

+ +

Objects, as generic blobs of values, can be used to build all sorts of data structures. A common data structure is the list (not to be confused with array). A list is a nested set of objects, with the first object holding a reference to the second, the second to the third, and so on.

+ +
let list = {
+  value: 1,
+  rest: {
+    value: 2,
+    rest: {
+      value: 3,
+      rest: null
+    }
+  }
+};
+ +

The resulting objects form a chain, like this:

A linked list
+ +

A nice thing about lists is that they can share parts of their structure. For example, if I create two new values {value: 0, rest: list} and {value: -1, rest: list} (with list referring to the binding defined earlier), they are both independent lists, but they share the structure that makes up their last three elements. The original list is also still a valid three-element list.

+ +

Write a function arrayToList that builds up a list structure like the one shown when given [1, 2, 3] as argument. Also write a listToArray function that produces an array from a list. Then add a helper function prepend, which takes an element and a list and creates a new list that adds the element to the front of the input list, and nth, which takes a list and a number and returns the element at the given position in the list (with zero referring to the first element) or undefined when there is no such element.

+ +

If you haven’t already, also write a recursive version of nth.

+ +
// Your code here.
+
+console.log(arrayToList([10, 20]));
+// → {value: 10, rest: {value: 20, rest: null}}
+console.log(listToArray(arrayToList([10, 20, 30])));
+// → [10, 20, 30]
+console.log(prepend(10, prepend(20, null)));
+// → {value: 10, rest: {value: 20, rest: null}}
+console.log(nth(arrayToList([10, 20, 30]), 1));
+// → 20
+ +
+ +

Building up a list is easier when done back to front. So arrayToList could iterate over the array backwards (see the previous exercise) and, for each element, add an object to the list. You can use a local binding to hold the part of the list that was built so far and use an assignment like list = {value: X, rest: list} to add an element.

+ +

To run over a list (in listToArray and nth), a for loop specification like this can be used:

+ +
for (let node = list; node; node = node.rest) {}
+ +

Can you see how that works? Every iteration of the loop, node points to the current sublist, and the body can read its value property to get the current element. At the end of an iteration, node moves to the next sublist. When that is null, we have reached the end of the list, and the loop is finished.

+ +

The recursive version of nth will, similarly, look at an ever smaller part of the “tail” of the list and at the same time count down the index until it reaches zero, at which point it can return the value property of the node it is looking at. To get the zeroth element of a list, you simply take the value property of its head node. To get element N + 1, you take the Nth element of the list that’s in this list’s rest property.

+ +
+ +

Deep comparison

+ +

The == operator compares objects by identity. But sometimes you’d prefer to compare the values of their actual properties.

+ +

Write a function deepEqual that takes two values and returns true only if they are the same value or are objects with the same properties, where the values of the properties are equal when compared with a recursive call to deepEqual.

+ +

To find out whether values should be compared directly (use the === operator for that) or have their properties compared, you can use the typeof operator. If it produces "object" for both values, you should do a deep comparison. But you have to take one silly exception into account: because of a historical accident, typeof null also produces "object".

+ +

The Object.keys function will be useful when you need to go over the properties of objects to compare them.

+ +
// Your code here.
+
+let obj = {here: {is: "an"}, object: 2};
+console.log(deepEqual(obj, obj));
+// → true
+console.log(deepEqual(obj, {here: 1, object: 2}));
+// → false
+console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
+// → true
+ +
+ +

Your test for whether you are dealing with a real object will look something like typeof x == "object" && x != null. Be careful to compare properties only when both arguments are objects. In all other cases you can just immediately return the result of applying ===.

+ +

Use Object.keys to go over the properties. You need to test whether both objects have the same set of property names and whether those properties have identical values. One way to do that is to ensure that both objects have the same number of properties (the lengths of the property lists are the same). And then, when looping over one of the object’s properties to compare them, always first make sure the other actually has a property by that name. If they have the same number of properties and all properties in one also exist in the other, they have the same set of property names.

+ +

Returning the correct value from the function is best done by immediately returning false when a mismatch is found and returning true at the end of the function.

+ +
+
diff --git a/docs/05_higher_order.html b/docs/05_higher_order.html new file mode 100644 index 000000000..966bd9b43 --- /dev/null +++ b/docs/05_higher_order.html @@ -0,0 +1,511 @@ + + + + + Higher-Order Functions :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 5Higher-Order Functions

+ +
+ +

Tzu-li and Tzu-ssu were boasting about the size of their latest programs. ‘Two-hundred thousand lines,’ said Tzu-li, ‘not counting comments!’ Tzu-ssu responded, ‘Pssh, mine is almost a million lines already.’ Master Yuan-Ma said, ‘My best program has five hundred lines.’ Hearing this, Tzu-li and Tzu-ssu were enlightened.

+ +
Master Yuan-Ma, The Book of Programming
+ +
+ +
+ +

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.

+ +
C.A.R. Hoare, 1980 ACM Turing Award Lecture
+ +
Letters from different scripts
+ +

A large program is a costly program, and not just because of the time it takes to build. Size almost always involves complexity, and complexity confuses programmers. Confused programmers, in turn, introduce mistakes (bugs) into programs. A large program then provides a lot of space for these bugs to hide, making them hard to find.

+ +

Let’s briefly go back to the final two example programs in the introduction. The first is self-contained and six lines long.

+ +
let total = 0, count = 1;
+while (count <= 10) {
+  total += count;
+  count += 1;
+}
+console.log(total);
+ +

The second relies on two external functions and is one line long.

+ +
console.log(sum(range(1, 10)));
+ +

Which one is more likely to contain a bug?

+ +

If we count the size of the definitions of sum and range, the second program is also big—even bigger than the first. But still, I’d argue that it is more likely to be correct.

+ +

It is more likely to be correct because the solution is expressed in a vocabulary that corresponds to the problem being solved. Summing a range of numbers isn’t about loops and counters. It is about ranges and sums.

+ +

The definitions of this vocabulary (the functions sum and range) will still involve loops, counters, and other incidental details. But because they are expressing simpler concepts than the program as a whole, they are easier to get right.

+ +

Abstraction

+ +

In the context of programming, these kinds of vocabularies are usually called abstractions. Abstractions hide details and give us the ability to talk about problems at a higher (or more abstract) level.

+ +

As an analogy, compare these two recipes for pea soup. The first one goes like this:

+ +
+ +

Put 1 cup of dried peas per person into a container. Add water until the peas are well covered. Leave the peas in water for at least 12 hours. Take the peas out of the water and put them in a cooking pan. Add 4 cups of water per person. Cover the pan and keep the peas simmering for two hours. Take half an onion per person. Cut it into pieces with a knife. Add it to the peas. Take a stalk of celery per person. Cut it into pieces with a knife. Add it to the peas. Take a carrot per person. Cut it into pieces. With a knife! Add it to the peas. Cook for 10 more minutes.

+ +
+ +

And this is the second recipe:

+ +
+ +

Per person: 1 cup dried split peas, half a chopped onion, a stalk of celery, and a carrot.

+ +

Soak peas for 12 hours. Simmer for 2 hours in 4 cups of water (per person). Chop and add vegetables. Cook for 10 more minutes.

+ +
+ +

The second is shorter and easier to interpret. But you do need to understand a few more cooking-related words such as soak, simmer, chop, and, I guess, vegetable.

+ +

When programming, we can’t rely on all the words we need to be waiting for us in the dictionary. Thus, we might fall into the pattern of the first recipe—work out the precise steps the computer has to perform, one by one, blind to the higher-level concepts that they express.

+ +

It is a useful skill, in programming, to notice when you are working at too low a level of abstraction.

+ +

Abstracting repetition

+ +

Plain functions, as we’ve seen them so far, are a good way to build abstractions. But sometimes they fall short.

+ +

It is common for a program to do something a given number of times. You can write a for loop for that, like this:

+ +
for (let i = 0; i < 10; i++) {
+  console.log(i);
+}
+ +

Can we abstract “doing something N times” as a function? Well, it’s easy to write a function that calls console.log N times.

+ +
function repeatLog(n) {
+  for (let i = 0; i < n; i++) {
+    console.log(i);
+  }
+}
+ +

But what if we want to do something other than logging the numbers? Since “doing something” can be represented as a function and functions are just values, we can pass our action as a function value.

+ +
function repeat(n, action) {
+  for (let i = 0; i < n; i++) {
+    action(i);
+  }
+}
+
+repeat(3, console.log);
+// → 0
+// → 1
+// → 2
+ +

We don’t have to pass a predefined function to repeat. Often, it is easier to create a function value on the spot instead.

+ +
let labels = [];
+repeat(5, i => {
+  labels.push(`Unit ${i + 1}`);
+});
+console.log(labels);
+// → ["Unit 1", "Unit 2", "Unit 3", "Unit 4", "Unit 5"]
+ +

This is structured a little like a for loop—it first describes the kind of loop and then provides a body. However, the body is now written as a function value, which is wrapped in the parentheses of the call to repeat. This is why it has to be closed with the closing brace and closing parenthesis. In cases like this example, where the body is a single small expression, you could also omit the braces and write the loop on a single line.

+ +

Higher-order functions

+ +

Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions. Since we have already seen that functions are regular values, there is nothing particularly remarkable about the fact that such functions exist. The term comes from mathematics, where the distinction between functions and other values is taken more seriously.

+ +

Higher-order functions allow us to abstract over actions, not just values. They come in several forms. For example, we can have functions that create new functions.

+ +
function greaterThan(n) {
+  return m => m > n;
+}
+let greaterThan10 = greaterThan(10);
+console.log(greaterThan10(11));
+// → true
+ +

And we can have functions that change other functions.

+ +
function noisy(f) {
+  return (...args) => {
+    console.log("calling with", args);
+    let result = f(...args);
+    console.log("called with", args, ", returned", result);
+    return result;
+  };
+}
+noisy(Math.min)(3, 2, 1);
+// → calling with [3, 2, 1]
+// → called with [3, 2, 1] , returned 1
+ +

We can even write functions that provide new types of control +flow.

+ +
function unless(test, then) {
+  if (!test) then();
+}
+
+repeat(3, n => {
+  unless(n % 2 == 1, () => {
+    console.log(n, "is even");
+  });
+});
+// → 0 is even
+// → 2 is even
+ +

There is a built-in array method, forEach, that provides something like a for/of loop as a higher-order function.

+ +
["A", "B"].forEach(l => console.log(l));
+// → A
+// → B
+ +

Script data set

+ +

One area where higher-order functions shine is data processing. To process data, we’ll need some actual data. This chapter will use a data set about scripts—writing systems such as Latin, Cyrillic, or Arabic.

+ +

Remember Unicode from Chapter 1, the system that assigns a number to each character in written language? Most of these characters are associated with a specific script. The standard contains 140 different scripts—81 are still in use today, and 59 are historic.

+ +

Though I can fluently read only Latin characters, I appreciate the fact that people are writing texts in at least 80 other writing systems, many of which I wouldn’t even recognize. For example, here’s a sample of Tamil handwriting:

Tamil handwriting
+ +

The example data set contains some pieces of information about the 140 scripts defined in Unicode. It is available in the coding sandbox for this chapter as the SCRIPTS binding. The binding contains an array of objects, each of which describes a script.

+ +
{
+  name: "Coptic",
+  ranges: [[994, 1008], [11392, 11508], [11513, 11520]],
+  direction: "ltr",
+  year: -200,
+  living: false,
+  link: "https://en.wikipedia.org/wiki/Coptic_alphabet"
+}
+ +

Such an object tells us the name of the script, the Unicode ranges assigned to it, the direction in which it is written, the (approximate) origin time, whether it is still in use, and a link to more information. The direction may be "ltr" for left to right, "rtl" for right to left (the way Arabic and Hebrew text are written), or "ttb" for top to bottom (as with Mongolian writing).

+ +

The ranges property contains an array of Unicode character ranges, each of which is a two-element array containing a lower bound and an upper bound. Any character codes within these ranges are assigned to the script. The lower bound is inclusive (code 994 is a Coptic character), and the upper bound is non-inclusive (code 1008 isn’t).

+ +

Filtering arrays

+ +

To find the scripts in the data set that are still in use, the following function might be helpful. It filters out the elements in an array that don’t pass a test.

+ +
function filter(array, test) {
+  let passed = [];
+  for (let element of array) {
+    if (test(element)) {
+      passed.push(element);
+    }
+  }
+  return passed;
+}
+
+console.log(filter(SCRIPTS, script => script.living));
+// → [{name: "Adlam", …}, …]
+ +

The function uses the argument named test, a function value, to fill a “gap” in the computation—the process of deciding which elements to collect.

+ +

Note how the filter function, rather than deleting elements from the existing array, builds up a new array with only the elements that pass the test. This function is pure. It does not modify the array it is given.

+ +

Like forEach, filter is a standard array method. The example defined the function only to show what it does internally. From now on, we’ll use it like this instead:

+ +
console.log(SCRIPTS.filter(s => s.direction == "ttb"));
+// → [{name: "Mongolian", …}, …]
+ +

Transforming with map

+ +

Say we have an array of objects representing scripts, produced by filtering the SCRIPTS array somehow. But we want an array of names, which is easier to inspect.

+ +

The map method transforms an array by applying a function to all of its elements and building a new array from the returned values. The new array will have the same length as the input array, but its content will have been mapped to a new form by the function.

+ +
function map(array, transform) {
+  let mapped = [];
+  for (let element of array) {
+    mapped.push(transform(element));
+  }
+  return mapped;
+}
+
+let rtlScripts = SCRIPTS.filter(s => s.direction == "rtl");
+console.log(map(rtlScripts, s => s.name));
+// → ["Adlam", "Arabic", "Imperial Aramaic", …]
+ +

Like forEach and filter, map is a standard array method.

+ +

Summarizing with reduce

+ +

Another common thing to do with arrays is to compute a single value from them. Our recurring example, summing a collection of numbers, is an instance of this. Another example is finding the script with the most characters.

+ +

The higher-order operation that represents this pattern is called reduce (sometimes also called fold). It builds a value by repeatedly taking a single element from the array and combining it with the current value. When summing numbers, you’d start with the number zero and, for each element, add that to the sum.

+ +

The parameters to reduce are, apart from the array, a combining function and a start value. This function is a little less straightforward than filter and map, so take a close look at it:

+ +
function reduce(array, combine, start) {
+  let current = start;
+  for (let element of array) {
+    current = combine(current, element);
+  }
+  return current;
+}
+
+console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0));
+// → 10
+ +

The standard array method reduce, which of course corresponds to this function, has an added convenience. If your array contains at least one element, you are allowed to leave off the start argument. The method will take the first element of the array as its start value and start reducing at the second element.

+ +
console.log([1, 2, 3, 4].reduce((a, b) => a + b));
+// → 10
+ +

To use reduce (twice) to find the script with the most characters, we can write something like this:

+ +
function characterCount(script) {
+  return script.ranges.reduce((count, [from, to]) => {
+    return count + (to - from);
+  }, 0);
+}
+
+console.log(SCRIPTS.reduce((a, b) => {
+  return characterCount(a) < characterCount(b) ? b : a;
+}));
+// → {name: "Han", …}
+ +

The characterCount function reduces the ranges assigned to a script by summing their sizes. Note the use of destructuring in the parameter list of the reducer function. The second call to reduce then uses this to find the largest script by repeatedly comparing two scripts and returning the larger one.

+ +

The Han script has more than 89,000 characters assigned to it in the Unicode standard, making it by far the biggest writing system in the data set. Han is a script (sometimes) used for Chinese, Japanese, and Korean text. Those languages share a lot of characters, though they tend to write them differently. The (U.S.-based) Unicode Consortium decided to treat them as a single writing system to save character codes. This is called Han unification and still makes some people very angry.

+ +

Composability

+ +

Consider how we would have written the previous example (finding the biggest script) without higher-order functions. The code is not that much worse.

+ +
let biggest = null;
+for (let script of SCRIPTS) {
+  if (biggest == null ||
+      characterCount(biggest) < characterCount(script)) {
+    biggest = script;
+  }
+}
+console.log(biggest);
+// → {name: "Han", …}
+ +

There are a few more bindings, and the program is four lines longer. But it is still very readable.

+ +

Higher-order functions start to shine when you need to compose operations. As an example, let’s write code that finds the average year of origin for living and dead scripts in the data set.

+ +
function average(array) {
+  return array.reduce((a, b) => a + b) / array.length;
+}
+
+console.log(Math.round(average(
+  SCRIPTS.filter(s => s.living).map(s => s.year))));
+// → 1165
+console.log(Math.round(average(
+  SCRIPTS.filter(s => !s.living).map(s => s.year))));
+// → 204
+ +

So the dead scripts in Unicode are, on average, older than the living ones. This is not a terribly meaningful or surprising statistic. But I hope you’ll agree that the code used to compute it isn’t hard to read. You can see it as a pipeline: we start with all scripts, filter out the living (or dead) ones, take the years from those, average them, and round the result.

+ +

You could definitely also write this computation as one big loop.

+ +
let total = 0, count = 0;
+for (let script of SCRIPTS) {
+  if (script.living) {
+    total += script.year;
+    count += 1;
+  }
+}
+console.log(Math.round(total / count));
+// → 1165
+ +

But it is harder to see what was being computed and how. And because intermediate results aren’t represented as coherent values, it’d be a lot more work to extract something like average into a separate function.

+ +

In terms of what the computer is actually doing, these two approaches are also quite different. The first will build up new arrays when running filter and map, whereas the second computes only some numbers, doing less work. You can usually afford the readable approach, but if you’re processing huge arrays, and doing so many times, the less abstract style might be worth the extra speed.

+ +

Strings and character codes

+ +

One use of the data set would be figuring out what script a piece of text is using. Let’s go through a program that does this.

+ +

Remember that each script has an array of character code ranges associated with it. So given a character code, we could use a function like this to find the corresponding script (if any):

+ +
function characterScript(code) {
+  for (let script of SCRIPTS) {
+    if (script.ranges.some(([from, to]) => {
+      return code >= from && code < to;
+    })) {
+      return script;
+    }
+  }
+  return null;
+}
+
+console.log(characterScript(121));
+// → {name: "Latin", …}
+ +

The some method is another higher-order function. It takes a test function and tells you whether that function returns true for any of the elements in the array.

+ +

But how do we get the character codes in a string?

+ +

In Chapter 1 I mentioned that JavaScript strings are encoded as a sequence of 16-bit numbers. These are called code +units. A Unicode character code was initially supposed to fit within such a unit (which gives you a little over 65,000 characters). When it became clear that wasn’t going to be enough, many people balked at the need to use more memory per character. To address these concerns, UTF-16, the format used by JavaScript strings, was invented. It describes most common characters using a single 16-bit code unit but uses a pair of two such units for others.

+ +

UTF-16 is generally considered a bad idea today. It seems almost intentionally designed to invite mistakes. It’s easy to write programs that pretend code units and characters are the same thing. And if your language doesn’t use two-unit characters, that will appear to work just fine. But as soon as someone tries to use such a program with some less common Chinese characters, it breaks. Fortunately, with the advent of emoji, everybody has started using two-unit characters, and the burden of dealing with such problems is more fairly distributed.

+ +

Unfortunately, obvious operations on JavaScript strings, such as getting their length through the length property and accessing their content using square brackets, deal only with code units.

+ +
// Two emoji characters, horse and shoe
+let horseShoe = "🐴👟";
+console.log(horseShoe.length);
+// → 4
+console.log(horseShoe[0]);
+// → (Invalid half-character)
+console.log(horseShoe.charCodeAt(0));
+// → 55357 (Code of the half-character)
+console.log(horseShoe.codePointAt(0));
+// → 128052 (Actual code for horse emoji)
+ +

JavaScript’s charCodeAt method gives you a code unit, not a full character code. The codePointAt method, added later, does give a full Unicode character. So we could use that to get characters from a string. But the argument passed to codePointAt is still an index into the sequence of code units. So to run over all characters in a string, we’d still need to deal with the question of whether a character takes up one or two code units.

+ +

In the previous chapter, I mentioned that a for/of loop can also be used on strings. Like codePointAt, this type of loop was introduced at a time where people were acutely aware of the problems with UTF-16. When you use it to loop over a string, it gives you real characters, not code units.

+ +
let roseDragon = "🌹🐉";
+for (let char of roseDragon) {
+  console.log(char);
+}
+// → 🌹
+// → 🐉
+ +

If you have a character (which will be a string of one or two code units), you can use codePointAt(0) to get its code.

+ +

Recognizing text

+ +

We have a characterScript function and a way to correctly loop over characters. The next step is to count the characters that belong to each script. The following counting abstraction will be useful there:

+ +
function countBy(items, groupName) {
+  let counts = [];
+  for (let item of items) {
+    let name = groupName(item);
+    let known = counts.findIndex(c => c.name == name);
+    if (known == -1) {
+      counts.push({name, count: 1});
+    } else {
+      counts[known].count++;
+    }
+  }
+  return counts;
+}
+
+console.log(countBy([1, 2, 3, 4, 5], n => n > 2));
+// → [{name: false, count: 2}, {name: true, count: 3}]
+ +

The countBy function expects a collection (anything that we can loop over with for/of) and a function that computes a group name for a given element. It returns an array of objects, each of which names a group and tells you the number of elements that were found in that group.

+ +

It uses another array method—findIndex. This method is somewhat like indexOf, but instead of looking for a specific value, it finds the first value for which the given function returns true. Like indexOf, it returns -1 when no such element is found.

+ +

Using countBy, we can write the function that tells us which scripts are used in a piece of text.

+ +
function textScripts(text) {
+  let scripts = countBy(text, char => {
+    let script = characterScript(char.codePointAt(0));
+    return script ? script.name : "none";
+  }).filter(({name}) => name != "none");
+
+  let total = scripts.reduce((n, {count}) => n + count, 0);
+  if (total == 0) return "No scripts found";
+
+  return scripts.map(({name, count}) => {
+    return `${Math.round(count * 100 / total)}% ${name}`;
+  }).join(", ");
+}
+
+console.log(textScripts('英国的狗说"woof", 俄罗斯的狗说"тяв"'));
+// → 61% Han, 22% Latin, 17% Cyrillic
+ +

The function first counts the characters by name, using characterScript to assign them a name and falling back to the string "none" for characters that aren’t part of any script. The filter call drops the entry for "none" from the resulting array since we aren’t interested in those characters.

+ +

To be able to compute percentages, we first need the total number of characters that belong to a script, which we can compute with reduce. If no such characters are found, the function returns a specific string. Otherwise, it transforms the counting entries into readable strings with map and then combines them with join.

+ +

Summary

+ +

Being able to pass function values to other functions is a deeply useful aspect of JavaScript. It allows us to write functions that model computations with “gaps” in them. The code that calls these functions can fill in the gaps by providing function values.

+ +

Arrays provide a number of useful higher-order methods. You can use forEach to loop over the elements in an array. The filter method returns a new array containing only the elements that pass the predicate function. Transforming an array by putting each element through a function is done with map. You can use reduce to combine all the elements in an array into a single value. The some method tests whether any element matches a given predicate function. And findIndex finds the position of the first element that matches a predicate.

+ +

Exercises

+ +

Flattening

+ +

Use the reduce method in combination with the concat method to “flatten” an array of arrays into a single array that has all the elements of the original arrays.

+ +
let arrays = [[1, 2, 3], [4, 5], [6]];
+// Your code here.
+// → [1, 2, 3, 4, 5, 6]
+ +

Your own loop

+ +

Write a higher-order function loop that provides something like a for loop statement. It takes a value, a test function, an update function, and a body function. Each iteration, it first runs the test function on the current loop value and stops if that returns false. Then it calls the body function, giving it the current value. Finally, it calls the update function to create a new value and starts from the beginning.

+ +

When defining the function, you can use a regular loop to do the actual looping.

+ +
// Your code here.
+
+loop(3, n => n > 0, n => n - 1, console.log);
+// → 3
+// → 2
+// → 1
+ +

Everything

+ +

Analogous to the some method, arrays also have an every method. This one returns true when the given function returns true for every element in the array. In a way, some is a version of the || operator that acts on arrays, and every is like the && operator.

+ +

Implement every as a function that takes an array and a predicate function as parameters. Write two versions, one using a loop and one using the some method.

+ +
function every(array, test) {
+  // Your code here.
+}
+
+console.log(every([1, 3, 5], n => n < 10));
+// → true
+console.log(every([2, 4, 16], n => n < 10));
+// → false
+console.log(every([], n => n < 10));
+// → true
+ +
+ +

Like the && operator, the every method can stop evaluating further elements as soon as it has found one that doesn’t match. So the loop-based version can jump out of the loop—with break or return—as soon as it runs into an element for which the predicate function returns false. If the loop runs to its end without finding such an element, we know that all elements matched and we should return true.

+ +

To build every on top of some, we can apply De Morgan’s +laws, which state that a && b equals !(!a || !b). This can be generalized to arrays, where all elements in the array match if there is no element in the array that does not match.

+ +
+ +

Dominant writing direction

+ +

Write a function that computes the dominant writing direction in a string of text. Remember that each script object has a direction property that can be "ltr" (left to right), "rtl" (right to left), or "ttb" (top to bottom).

+ +

The dominant direction is the direction of a majority of the characters that have a script associated with them. The characterScript and countBy functions defined earlier in the chapter are probably useful here.

+ +
function dominantDirection(text) {
+  // Your code here.
+}
+
+console.log(dominantDirection("Hello!"));
+// → ltr
+console.log(dominantDirection("Hey, مساء الخير"));
+// → rtl
+ +
+ +

Your solution might look a lot like the first half of the textScripts example. You again have to count characters by a criterion based on characterScript and then filter out the part of the result that refers to uninteresting (script-less) characters.

+ +

Finding the direction with the highest character count can be done with reduce. If it’s not clear how, refer to the example earlier in the chapter, where reduce was used to find the script with the most characters.

+ +
+
diff --git a/docs/06_object.html b/docs/06_object.html new file mode 100644 index 000000000..6d63632f3 --- /dev/null +++ b/docs/06_object.html @@ -0,0 +1,660 @@ + + + + + The Secret Life of Objects :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 6The Secret Life of Objects

+ +
+ +

An abstract data type is realized by writing a special kind of program […] which defines the type in terms of the operations which can be performed on it.

+ +
Barbara Liskov, Programming with Abstract Data Types
+ +
Picture of a rabbit with its proto-rabbit
+ +

Chapter 4 introduced JavaScript’s objects. In programming culture, we have a thing called object-oriented programming, a set of techniques that use objects (and related concepts) as the central principle of program organization.

+ +

Though no one really agrees on its precise definition, object-oriented programming has shaped the design of many programming languages, including JavaScript. This chapter will describe the way these ideas can be applied in JavaScript.

+ +

Encapsulation

+ +

The core idea in object-oriented programming is to divide programs into smaller pieces and make each piece responsible for managing its own state.

+ +

This way, some knowledge about the way a piece of the program works can be kept local to that piece. Someone working on the rest of the program does not have to remember or even be aware of that knowledge. Whenever these local details change, only the code directly around it needs to be updated.

+ +

Different pieces of such a program interact with each other through interfaces, limited sets of functions or bindings that provide useful functionality at a more abstract level, hiding their precise implementation.

+ +

Such program pieces are modeled using objects. Their interface consists of a specific set of methods and properties. Properties that are part of the interface are called public. The others, which outside code should not be touching, are called private.

+ +

Many languages provide a way to distinguish public and private properties and prevent outside code from accessing the private ones altogether. JavaScript, once again taking the minimalist approach, does not—not yet at least. There is work underway to add this to the language.

+ +

Even though the language doesn’t have this distinction built in, JavaScript programmers are successfully using this idea. Typically, the available interface is described in documentation or comments. It is also common to put an underscore (_) character at the start of property names to indicate that those properties are private.

+ +

Separating interface from implementation is a great idea. It is usually called encapsulation.

+ +

Methods

+ +

Methods are nothing more than properties that hold function values. This is a simple method:

+ +
let rabbit = {};
+rabbit.speak = function(line) {
+  console.log(`The rabbit says '${line}'`);
+};
+
+rabbit.speak("I'm alive.");
+// → The rabbit says 'I'm alive.'
+ +

Usually a method needs to do something with the object it was called on. When a function is called as a method—looked up as a property and immediately called, as in object.method()—the binding called this in its body automatically points at the object that it was called on.

+ +
function speak(line) {
+  console.log(`The ${this.type} rabbit says '${line}'`);
+}
+let whiteRabbit = {type: "white", speak};
+let hungryRabbit = {type: "hungry", speak};
+
+whiteRabbit.speak("Oh my ears and whiskers, " +
+                  "how late it's getting!");
+// → The white rabbit says 'Oh my ears and whiskers, how
+//   late it's getting!'
+hungryRabbit.speak("I could use a carrot right now.");
+// → The hungry rabbit says 'I could use a carrot right now.'
+ +

You can think of this as an extra parameter that is passed in a different way. If you want to pass it explicitly, you can use a function’s call method, which takes the this value as its first argument and treats further arguments as normal parameters.

+ +
speak.call(hungryRabbit, "Burp!");
+// → The hungry rabbit says 'Burp!'
+ +

Since each function has its own this binding, whose value depends on the way it is called, you cannot refer to the this of the wrapping scope in a regular function defined with the function keyword.

+ +

Arrow functions are different—they do not bind their own this but can see the this binding of the scope around them. Thus, you can do something like the following code, which references this from inside a local function:

+ +
function normalize() {
+  console.log(this.coords.map(n => n / this.length));
+}
+normalize.call({coords: [0, 2, 3], length: 5});
+// → [0, 0.4, 0.6]
+ +

If I had written the argument to map using the function keyword, the code wouldn’t work.

+ +

Prototypes

+ +

Watch closely.

+ +
let empty = {};
+console.log(empty.toString);
+// → function toString(){…}
+console.log(empty.toString());
+// → [object Object]
+ +

I pulled a property out of an empty object. Magic!

+ +

Well, not really. I have simply been withholding information about the way JavaScript objects work. In addition to their set of properties, most objects also have a prototype. A prototype is another object that is used as a fallback source of properties. When an object gets a request for a property that it does not have, its prototype will be searched for the property, then the prototype’s prototype, and so on.

+ +

So who is the prototype of that empty object? It is the great ancestral prototype, the entity behind almost all objects, Object.prototype.

+ +
console.log(Object.getPrototypeOf({}) ==
+            Object.prototype);
+// → true
+console.log(Object.getPrototypeOf(Object.prototype));
+// → null
+ +

As you guess, Object.getPrototypeOf returns the prototype of an object.

+ +

The prototype relations of JavaScript objects form a tree-shaped structure, and at the root of this structure sits Object.prototype. It provides a few methods that show up in all objects, such as toString, which converts an object to a string representation.

+ +

Many objects don’t directly have Object.prototype as their prototype but instead have another object that provides a different set of default properties. Functions derive from Function.prototype, and arrays derive from Array.prototype.

+ +
console.log(Object.getPrototypeOf(Math.max) ==
+            Function.prototype);
+// → true
+console.log(Object.getPrototypeOf([]) ==
+            Array.prototype);
+// → true
+ +

Such a prototype object will itself have a prototype, often Object.prototype, so that it still indirectly provides methods like toString.

+ +

You can use Object.create to create an object with a specific prototype.

+ +
let protoRabbit = {
+  speak(line) {
+    console.log(`The ${this.type} rabbit says '${line}'`);
+  }
+};
+let killerRabbit = Object.create(protoRabbit);
+killerRabbit.type = "killer";
+killerRabbit.speak("SKREEEE!");
+// → The killer rabbit says 'SKREEEE!'
+ +

A property like speak(line) in an object expression is a shorthand way of defining a method. It creates a property called speak and gives it a function as its value.

+ +

The “proto” rabbit acts as a container for the properties that are shared by all rabbits. An individual rabbit object, like the killer rabbit, contains properties that apply only to itself—in this case its type—and derives shared properties from its prototype.

+ +

Classes

+ +

JavaScript’s prototype system can be interpreted as a somewhat informal take on an object-oriented concept called classes. A class defines the shape of a type of object—what methods and properties it has. Such an object is called an instance of the class.

+ +

Prototypes are useful for defining properties for which all instances of a class share the same value, such as methods. Properties that differ per instance, such as our rabbits’ type property, need to be stored directly in the objects themselves.

+ +

So to create an instance of a given class, you have to make an object that derives from the proper prototype, but you also have to make sure it, itself, has the properties that instances of this class are supposed to have. This is what a constructor function does.

+ +
function makeRabbit(type) {
+  let rabbit = Object.create(protoRabbit);
+  rabbit.type = type;
+  return rabbit;
+}
+ +

JavaScript provides a way to make defining this type of function easier. If you put the keyword new in front of a function call, the function is treated as a constructor. This means that an object with the right prototype is automatically created, bound to this in the function, and returned at the end of the function.

+ +

The prototype object used when constructing objects is found by taking the prototype property of the constructor function.

+ +
function Rabbit(type) {
+  this.type = type;
+}
+Rabbit.prototype.speak = function(line) {
+  console.log(`The ${this.type} rabbit says '${line}'`);
+};
+
+let weirdRabbit = new Rabbit("weird");
+ +

Constructors (all functions, in fact) automatically get a property named prototype, which by default holds a plain, empty object that derives from Object.prototype. You can overwrite it with a new object if you want. Or you can add properties to the existing object, as the example does.

+ +

By convention, the names of constructors are capitalized so that they can easily be distinguished from other functions.

+ +

It is important to understand the distinction between the way a prototype is associated with a constructor (through its prototype property) and the way objects have a prototype (which can be found with Object.getPrototypeOf). The actual prototype of a constructor is Function.prototype since constructors are functions. Its prototype property holds the prototype used for instances created through it.

+ +
console.log(Object.getPrototypeOf(Rabbit) ==
+            Function.prototype);
+// → true
+console.log(Object.getPrototypeOf(weirdRabbit) ==
+            Rabbit.prototype);
+// → true
+ +

Class notation

+ +

So JavaScript classes are constructor functions with a prototype property. That is how they work, and until 2015, that was how you had to write them. These days, we have a less awkward notation.

+ +
class Rabbit {
+  constructor(type) {
+    this.type = type;
+  }
+  speak(line) {
+    console.log(`The ${this.type} rabbit says '${line}'`);
+  }
+}
+
+let killerRabbit = new Rabbit("killer");
+let blackRabbit = new Rabbit("black");
+ +

The class keyword starts a class declaration, which allows us to define a constructor and a set of methods all in a single place. Any number of methods may be written inside the declaration’s braces. The one named constructor is treated specially. It provides the actual constructor function, which will be bound to the name Rabbit. The others are packaged into that constructor’s prototype. Thus, the earlier class declaration is equivalent to the constructor definition from the previous section. It just looks nicer.

+ +

Class declarations currently allow only methods—properties that hold functions—to be added to the prototype. This can be somewhat inconvenient when you want to save a non-function value in there. The next version of the language will probably improve this. For now, you can create such properties by directly manipulating the prototype after you’ve defined the class.

+ +

Like function, class can be used both in statements and in expressions. When used as an expression, it doesn’t define a binding but just produces the constructor as a value. You are allowed to omit the class name in a class expression.

+ +
let object = new class { getWord() { return "hello"; } };
+console.log(object.getWord());
+// → hello
+ +

Overriding derived properties

+ +

When you add a property to an object, whether it is present in the prototype or not, the property is added to the object itself. If there was already a property with the same name in the prototype, this property will no longer affect the object, as it is now hidden behind the object’s own property.

+ +
Rabbit.prototype.teeth = "small";
+console.log(killerRabbit.teeth);
+// → small
+killerRabbit.teeth = "long, sharp, and bloody";
+console.log(killerRabbit.teeth);
+// → long, sharp, and bloody
+console.log(blackRabbit.teeth);
+// → small
+console.log(Rabbit.prototype.teeth);
+// → small
+ +

The following diagram sketches the situation after this code has run. The Rabbit and Object prototypes lie behind killerRabbit as a kind of backdrop, where properties that are not found in the object itself can be looked up.

Rabbit object prototype schema
+ +

Overriding properties that exist in a prototype can be a useful thing to do. As the rabbit teeth example shows, overriding can be used to express exceptional properties in instances of a more generic class of objects, while letting the nonexceptional objects take a standard value from their prototype.

+ +

Overriding is also used to give the standard function and array prototypes a different toString method than the basic object prototype.

+ +
console.log(Array.prototype.toString ==
+            Object.prototype.toString);
+// → false
+console.log([1, 2].toString());
+// → 1,2
+ +

Calling toString on an array gives a result similar to calling .join(",") on it—it puts commas between the values in the array. Directly calling Object.prototype.toString with an array produces a different string. That function doesn’t know about arrays, so it simply puts the word object and the name of the type between square brackets.

+ +
console.log(Object.prototype.toString.call([1, 2]));
+// → [object Array]
+ +

Maps

+ +

We saw the word map used in the previous chapter for an operation that transforms a data structure by applying a function to its elements. Confusing as it is, in programming the same word is also used for a related but rather different thing.

+ +

A map (noun) is a data structure that associates values (the keys) with other values. For example, you might want to map names to ages. It is possible to use objects for this.

+ +
let ages = {
+  Boris: 39,
+  Liang: 22,
+  Júlia: 62
+};
+
+console.log(`Júlia is ${ages["Júlia"]}`);
+// → Júlia is 62
+console.log("Is Jack's age known?", "Jack" in ages);
+// → Is Jack's age known? false
+console.log("Is toString's age known?", "toString" in ages);
+// → Is toString's age known? true
+ +

Here, the object’s property names are the people’s names, and the property values are their ages. But we certainly didn’t list anybody named toString in our map. Yet, because plain objects derive from Object.prototype, it looks like the property is there.

+ +

As such, using plain objects as maps is dangerous. There are several possible ways to avoid this problem. First, it is possible to create objects with no prototype. If you pass null to Object.create, the resulting object will not derive from Object.prototype and can safely be used as a map.

+ +
console.log("toString" in Object.create(null));
+// → false
+ +

Object property names must be strings. If you need a map whose keys can’t easily be converted to strings—such as objects—you cannot use an object as your map.

+ +

Fortunately, JavaScript comes with a class called Map that is written for this exact purpose. It stores a mapping and allows any type of keys.

+ +
let ages = new Map();
+ages.set("Boris", 39);
+ages.set("Liang", 22);
+ages.set("Júlia", 62);
+
+console.log(`Júlia is ${ages.get("Júlia")}`);
+// → Júlia is 62
+console.log("Is Jack's age known?", ages.has("Jack"));
+// → Is Jack's age known? false
+console.log(ages.has("toString"));
+// → false
+ +

The methods set, get, and has are part of the interface of the Map object. Writing a data structure that can quickly update and search a large set of values isn’t easy, but we don’t have to worry about that. Someone else did it for us, and we can go through this simple interface to use their work.

+ +

If you do have a plain object that you need to treat as a map for some reason, it is useful to know that Object.keys returns only an object’s own keys, not those in the prototype. As an alternative to the in operator, you can use the hasOwnProperty method, which ignores the object’s prototype.

+ +
console.log({x: 1}.hasOwnProperty("x"));
+// → true
+console.log({x: 1}.hasOwnProperty("toString"));
+// → false
+ +

Polymorphism

+ +

When you call the String function (which converts a value to a string) on an object, it will call the toString method on that object to try to create a meaningful string from it. I mentioned that some of the standard prototypes define their own version of toString so they can create a string that contains more useful information than "[object Object]". You can also do that yourself.

+ +
Rabbit.prototype.toString = function() {
+  return `a ${this.type} rabbit`;
+};
+
+console.log(String(blackRabbit));
+// → a black rabbit
+ +

This is a simple instance of a powerful idea. When a piece of code is written to work with objects that have a certain interface—in this case, a toString method—any kind of object that happens to support this interface can be plugged into the code, and it will just work.

+ +

This technique is called polymorphism. Polymorphic code can work with values of different shapes, as long as they support the interface it expects.

+ +

I mentioned in Chapter 4 that a for/of loop can loop over several kinds of data structures. This is another case of polymorphism—such loops expect the data structure to expose a specific interface, which arrays and strings do. And we can also add this interface to your own objects! But before we can do that, we need to know what symbols are.

+ +

Symbols

+ +

It is possible for multiple interfaces to use the same property name for different things. For example, I could define an interface in which the toString method is supposed to convert the object into a piece of yarn. It would not be possible for an object to conform to both that interface and the standard use of toString.

+ +

That would be a bad idea, and this problem isn’t that common. Most JavaScript programmers simply don’t think about it. But the language designers, whose job it is to think about this stuff, have provided us with a solution anyway.

+ +

When I claimed that property names are strings, that wasn’t entirely accurate. They usually are, but they can also be symbols. Symbols are values created with the Symbol function. Unlike strings, newly created symbols are unique—you cannot create the same symbol twice.

+ +
let sym = Symbol("name");
+console.log(sym == Symbol("name"));
+// → false
+Rabbit.prototype[sym] = 55;
+console.log(blackRabbit[sym]);
+// → 55
+ +

The string you pass to Symbol is included when you convert it to a string and can make it easier to recognize a symbol when, for example, showing it in the console. But it has no meaning beyond that—multiple symbols may have the same name.

+ +

Being both unique and usable as property names makes symbols suitable for defining interfaces that can peacefully live alongside other properties, no matter what their names are.

+ +
const toStringSymbol = Symbol("toString");
+Array.prototype[toStringSymbol] = function() {
+  return `${this.length} cm of blue yarn`;
+};
+
+console.log([1, 2].toString());
+// → 1,2
+console.log([1, 2][toStringSymbol]());
+// → 2 cm of blue yarn
+ +

It is possible to include symbol properties in object expressions and classes by using square brackets around the property name. That causes the property name to be evaluated, much like the square bracket property access notation, which allows us to refer to a binding that holds the symbol.

+ +
let stringObject = {
+  [toStringSymbol]() { return "a jute rope"; }
+};
+console.log(stringObject[toStringSymbol]());
+// → a jute rope
+ +

The iterator interface

+ +

The object given to a for/of loop is expected to be iterable. This means it has a method named with the Symbol.iterator symbol (a symbol value defined by the language, stored as a property of the Symbol function).

+ +

When called, that method should return an object that provides a second interface, iterator. This is the actual thing that iterates. It has a next method that returns the next result. That result should be an object with a value property that provides the next value, if there is one, and a done property, which should be true when there are no more results and false otherwise.

+ +

Note that the next, value, and done property names are plain strings, not symbols. Only Symbol.iterator, which is likely to be added to a lot of different objects, is an actual symbol.

+ +

We can directly use this interface ourselves.

+ +
let okIterator = "OK"[Symbol.iterator]();
+console.log(okIterator.next());
+// → {value: "O", done: false}
+console.log(okIterator.next());
+// → {value: "K", done: false}
+console.log(okIterator.next());
+// → {value: undefined, done: true}
+ +

Let’s implement an iterable data structure. We’ll build a matrix class, acting as a two-dimensional array.

+ +
class Matrix {
+  constructor(width, height, element = (x, y) => undefined) {
+    this.width = width;
+    this.height = height;
+    this.content = [];
+
+    for (let y = 0; y < height; y++) {
+      for (let x = 0; x < width; x++) {
+        this.content[y * width + x] = element(x, y);
+      }
+    }
+  }
+
+  get(x, y) {
+    return this.content[y * this.width + x];
+  }
+  set(x, y, value) {
+    this.content[y * this.width + x] = value;
+  }
+}
+ +

The class stores its content in a single array of width × height elements. The elements are stored row by row, so, for example, the third element in the fifth row is (using zero-based indexing) stored at position 4 × width + 2.

+ +

The constructor function takes a width, a height, and an optional element function that will be used to fill in the initial values. There are get and set methods to retrieve and update elements in the matrix.

+ +

When looping over a matrix, you are usually interested in the position of the elements as well as the elements themselves, so we’ll have our iterator produce objects with x, y, and value properties.

+ +
class MatrixIterator {
+  constructor(matrix) {
+    this.x = 0;
+    this.y = 0;
+    this.matrix = matrix;
+  }
+
+  next() {
+    if (this.y == this.matrix.height) return {done: true};
+
+    let value = {x: this.x,
+                 y: this.y,
+                 value: this.matrix.get(this.x, this.y)};
+    this.x++;
+    if (this.x == this.matrix.width) {
+      this.x = 0;
+      this.y++;
+    }
+    return {value, done: false};
+  }
+}
+ +

The class tracks the progress of iterating over a matrix in its x and y properties. The next method starts by checking whether the bottom of the matrix has been reached. If it hasn’t, it first creates the object holding the current value and then updates its position, moving to the next row if necessary.

+ +

Let’s set up the Matrix class to be iterable. Throughout this book, I’ll occasionally use after-the-fact prototype manipulation to add methods to classes so that the individual pieces of code remain small and self-contained. In a regular program, where there is no need to split the code into small pieces, you’d declare these methods directly in the class instead.

+ +
Matrix.prototype[Symbol.iterator] = function() {
+  return new MatrixIterator(this);
+};
+ +

We can now loop over a matrix with for/of.

+ +
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
+for (let {x, y, value} of matrix) {
+  console.log(x, y, value);
+}
+// → 0 0 value 0,0
+// → 1 0 value 1,0
+// → 0 1 value 0,1
+// → 1 1 value 1,1
+ +

Getters, setters, and statics

+ +

Interfaces often consist mostly of methods, but it is also okay to include properties that hold non-function values. For example, Map objects have a size property that tells you how many keys are stored in them.

+ +

It is not even necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called getters, and they are defined by writing get in front of the method name in an object expression or class declaration.

+ +
let varyingSize = {
+  get size() {
+    return Math.floor(Math.random() * 100);
+  }
+};
+
+console.log(varyingSize.size);
+// → 73
+console.log(varyingSize.size);
+// → 49
+ +

Whenever someone reads from this object’s size property, the associated method is called. You can do a similar thing when a property is written to, using a setter.

+ +
class Temperature {
+  constructor(celsius) {
+    this.celsius = celsius;
+  }
+  get fahrenheit() {
+    return this.celsius * 1.8 + 32;
+  }
+  set fahrenheit(value) {
+    this.celsius = (value - 32) / 1.8;
+  }
+
+  static fromFahrenheit(value) {
+    return new Temperature((value - 32) / 1.8);
+  }
+}
+
+let temp = new Temperature(22);
+console.log(temp.fahrenheit);
+// → 71.6
+temp.fahrenheit = 86;
+console.log(temp.celsius);
+// → 30
+ +

The Temperature class allows you to read and write the temperature in either degrees Celsius or degrees Fahrenheit, but internally it stores only Celsius and automatically converts to and from Celsius in the fahrenheit getter and setter.

+ +

Sometimes you want to attach some properties directly to your constructor function, rather than to the prototype. Such methods won’t have access to a class instance but can, for example, be used to provide additional ways to create instances.

+ +

Inside a class declaration, methods that have static written before their name are stored on the constructor. So the Temperature class allows you to write Temperature.fromFahrenheit(100) to create a temperature using degrees Fahrenheit.

+ +

Inheritance

+ +

Some matrices are known to be symmetric. If you mirror a symmetric matrix around its top-left-to-bottom-right diagonal, it stays the same. In other words, the value stored at x,y is always the same as that at y,x.

+ +

Imagine we need a data structure like Matrix but one that enforces the fact that the matrix is and remains symmetrical. We could write it from scratch, but that would involve repeating some code very similar to what we already wrote.

+ +

JavaScript’s prototype system makes it possible to create a new class, much like the old class, but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the set method.

+ +

In object-oriented programming terms, this is called inheritance. The new class inherits properties and behavior from the old class.

+ +
class SymmetricMatrix extends Matrix {
+  constructor(size, element = (x, y) => undefined) {
+    super(size, size, (x, y) => {
+      if (x < y) return element(y, x);
+      else return element(x, y);
+    });
+  }
+
+  set(x, y, value) {
+    super.set(x, y, value);
+    if (x != y) {
+      super.set(y, x, value);
+    }
+  }
+}
+
+let matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`);
+console.log(matrix.get(2, 3));
+// → 3,2
+ +

The use of the word extends indicates that this class shouldn’t be directly based on the default Object prototype but on some other class. This is called the superclass. The derived class is the subclass.

+ +

To initialize a SymmetricMatrix instance, the constructor calls its superclass’s constructor through the super keyword. This is necessary because if this new object is to behave (roughly) like a Matrix, it is going to need the instance properties that matrices have. To ensure the matrix is symmetrical, the constructor wraps the element function to swap the coordinates for values below the diagonal.

+ +

The set method again uses super but this time not to call the constructor but to call a specific method from the superclass’s set of methods. We are redefining set but do want to use the original behavior. Because this.set refers to the new set method, calling that wouldn’t work. Inside class methods, super provides a way to call methods as they were defined in the superclass.

+ +

Inheritance allows us to build slightly different data types from existing data types with relatively little work. It is a fundamental part of the object-oriented tradition, alongside encapsulation and polymorphism. But while the latter two are now generally regarded as wonderful ideas, inheritance is more controversial.

+ +

Whereas encapsulation and polymorphism can be used to separate pieces of code from each other, reducing the tangledness of the overall program, inheritance fundamentally ties classes together, creating more tangle. When inheriting from a class, you usually have to know more about how it works than when simply using it. Inheritance can be a useful tool, and I use it now and then in my own programs, but it shouldn’t be the first tool you reach for, and you probably shouldn’t actively go looking for opportunities to construct class hierarchies (family trees of classes).

+ +

The instanceof operator

+ +

It is occasionally useful to know whether an object was derived from a specific class. For this, JavaScript provides a binary operator called instanceof.

+ +
console.log(
+  new SymmetricMatrix(2) instanceof SymmetricMatrix);
+// → true
+console.log(new SymmetricMatrix(2) instanceof Matrix);
+// → true
+console.log(new Matrix(2, 2) instanceof SymmetricMatrix);
+// → false
+console.log([1] instanceof Array);
+// → true
+ +

The operator will see through inherited types, so a SymmetricMatrix is an instance of Matrix. The operator can also be applied to standard constructors like Array. Almost every object is an instance of Object.

+ +

Summary

+ +

So objects do more than just hold their own properties. They have prototypes, which are other objects. They’ll act as if they have properties they don’t have as long as their prototype has that property. Simple objects have Object.prototype as their prototype.

+ +

Constructors, which are functions whose names usually start with a capital letter, can be used with the new operator to create new objects. The new object’s prototype will be the object found in the prototype property of the constructor. You can make good use of this by putting the properties that all values of a given type share into their prototype. There’s a class notation that provides a clear way to define a constructor and its prototype.

+ +

You can define getters and setters to secretly call methods every time an object’s property is accessed. Static methods are methods stored in a class’s constructor, rather than its prototype.

+ +

The instanceof operator can, given an object and a constructor, tell you whether that object is an instance of that constructor.

+ +

One useful thing to do with objects is to specify an interface for them and tell everybody that they are supposed to talk to your object only through that interface. The rest of the details that make up your object are now encapsulated, hidden behind the interface.

+ +

More than one type may implement the same interface. Code written to use an interface automatically knows how to work with any number of different objects that provide the interface. This is called polymorphism.

+ +

When implementing multiple classes that differ in only some details, it can be helpful to write the new classes as subclasses of an existing class, inheriting part of its behavior.

+ +

Exercises

+ +

A vector type

+ +

Write a class Vec that represents a vector in two-dimensional space. It takes x and y parameters (numbers), which it should save to properties of the same name.

+ +

Give the Vec prototype two methods, plus and minus, that take another vector as a parameter and return a new vector that has the sum or difference of the two vectors’ (this and the parameter) x and y values.

+ +

Add a getter property length to the prototype that computes the length of the vector—that is, the distance of the point (x, y) from the origin (0, 0).

+ +
// Your code here.
+
+console.log(new Vec(1, 2).plus(new Vec(2, 3)));
+// → Vec{x: 3, y: 5}
+console.log(new Vec(1, 2).minus(new Vec(2, 3)));
+// → Vec{x: -1, y: -1}
+console.log(new Vec(3, 4).length);
+// → 5
+ +
+ +

Look back to the Rabbit class example if you’re unsure how class declarations look.

+ +

Adding a getter property to the constructor can be done by putting the word get before the method name. To compute the distance from (0, 0) to (x, y), you can use the Pythagorean theorem, which says that the square of the distance we are looking for is equal to the square of the x-coordinate plus the square of the y-coordinate. Thus, √(x2 + y2) is the number you want, and Math.sqrt is the way you compute a square root in JavaScript.

+ +
+ +

Groups

+ +

The standard JavaScript environment provides another data structure called Set. Like an instance of Map, a set holds a collection of values. Unlike Map, it does not associate other values with those—it just tracks which values are part of the set. A value can be part of a set only once—adding it again doesn’t have any effect.

+ +

Write a class called Group (since Set is already taken). Like Set, it has add, delete, and has methods. Its constructor creates an empty group, add adds a value to the group (but only if it isn’t already a member), delete removes its argument from the group (if it was a member), and has returns a Boolean value indicating whether its argument is a member of the group.

+ +

Use the === operator, or something equivalent such as indexOf, to determine whether two values are the same.

+ +

Give the class a static from method that takes an iterable object as argument and creates a group that contains all the values produced by iterating over it.

+ +
class Group {
+  // Your code here.
+}
+
+let group = Group.from([10, 20]);
+console.log(group.has(10));
+// → true
+console.log(group.has(30));
+// → false
+group.add(10);
+group.delete(10);
+console.log(group.has(10));
+// → false
+ +
+ +

The easiest way to do this is to store an array of group members in an instance property. The includes or indexOf methods can be used to check whether a given value is in the array.

+ +

Your class’s constructor can set the member collection to an empty array. When add is called, it must check whether the given value is in the array or add it, for example with push, otherwise.

+ +

Deleting an element from an array, in delete, is less straightforward, but you can use filter to create a new array without the value. Don’t forget to overwrite the property holding the members with the newly filtered version of the array.

+ +

The from method can use a for/of loop to get the values out of the iterable object and call add to put them into a newly created group.

+ +
+ +

Iterable groups

+ +

Make the Group class from the previous exercise iterable. Refer to the section about the iterator interface earlier in the chapter if you aren’t clear on the exact form of the interface anymore.

+ +

If you used an array to represent the group’s members, don’t just return the iterator created by calling the Symbol.iterator method on the array. That would work, but it defeats the purpose of this exercise.

+ +

It is okay if your iterator behaves strangely when the group is modified during iteration.

+ +
// Your code here (and the code from the previous exercise)
+
+for (let value of Group.from(["a", "b", "c"])) {
+  console.log(value);
+}
+// → a
+// → b
+// → c
+ +
+ +

It is probably worthwhile to define a new class GroupIterator. Iterator instances should have a property that tracks the current position in the group. Every time next is called, it checks whether it is done and, if not, moves past the current value and returns it.

+ +

The Group class itself gets a method named by Symbol.iterator that, when called, returns a new instance of the iterator class for that group.

+ +
+ +

Borrowing a method

+ +

Earlier in the chapter I mentioned that an object’s hasOwnProperty can be used as a more robust alternative to the in operator when you want to ignore the prototype’s properties. But what if your map needs to include the word "hasOwnProperty"? You won’t be able to call that method anymore because the object’s own property hides the method value.

+ +

Can you think of a way to call hasOwnProperty on an object that has its own property by that name?

+ +
let map = {one: true, two: true, hasOwnProperty: true};
+
+// Fix this call
+console.log(map.hasOwnProperty("one"));
+// → true
+ +
+ +

Remember that methods that exist on plain objects come from Object.prototype.

+ +

Also remember that you can call a function with a specific this binding by using its call method.

+ +
+
diff --git a/docs/07_robot.html b/docs/07_robot.html new file mode 100644 index 000000000..115bc414b --- /dev/null +++ b/docs/07_robot.html @@ -0,0 +1,383 @@ + + + + + Project: A Robot :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 7Project: A Robot

+ +
+ +

[...] the question of whether Machines Can Think [...] is about as relevant as the question of whether Submarines Can Swim.

+ +
Edsger Dijkstra, The Threats to Computing Science
+ +
Picture of a package-delivery robot
+ +

In “project” chapters, I’ll stop pummeling you with new theory for a brief moment, and instead we’ll work through a program together. Theory is necessary to learn to program, but reading and understanding actual programs is just as important.

+ +

Our project in this chapter is to build an automaton, a little program that performs a task in a virtual world. Our automaton will be a mail-delivery robot picking up and dropping off parcels.

+ +

Meadowfield

+ +

The village of Meadowfield isn’t very big. It consists of 11 places with 14 roads between them. It can be described with this array of roads:

+ +
const roads = [
+  "Alice's House-Bob's House",   "Alice's House-Cabin",
+  "Alice's House-Post Office",   "Bob's House-Town Hall",
+  "Daria's House-Ernie's House", "Daria's House-Town Hall",
+  "Ernie's House-Grete's House", "Grete's House-Farm",
+  "Grete's House-Shop",          "Marketplace-Farm",
+  "Marketplace-Post Office",     "Marketplace-Shop",
+  "Marketplace-Town Hall",       "Shop-Town Hall"
+];
The village of Meadowfield
+ +

The network of roads in the village forms a graph. A graph is a collection of points (places in the village) with lines between them (roads). This graph will be the world that our robot moves through.

+ +

The array of strings isn’t very easy to work with. What we’re interested in is the destinations that we can reach from a given place. Let’s convert the list of roads to a data structure that, for each place, tells us what can be reached from there.

+ +
function buildGraph(edges) {
+  let graph = Object.create(null);
+  function addEdge(from, to) {
+    if (graph[from] == null) {
+      graph[from] = [to];
+    } else {
+      graph[from].push(to);
+    }
+  }
+  for (let [from, to] of edges.map(r => r.split("-"))) {
+    addEdge(from, to);
+    addEdge(to, from);
+  }
+  return graph;
+}
+
+const roadGraph = buildGraph(roads);
+ +

Given an array of edges, buildGraph creates a map object that, for each node, stores an array of connected nodes.

+ +

It uses the split method to go from the road strings, which have the form "Start-End", to two-element arrays containing the start and end as separate strings.

+ +

The task

+ +

Our robot will be moving around the village. There are parcels in various places, each addressed to some other place. The robot picks up parcels when it comes to them and delivers them when it arrives at their destinations.

+ +

The automaton must decide, at each point, where to go next. It has finished its task when all parcels have been delivered.

+ +

To be able to simulate this process, we must define a virtual world that can describe it. This model tells us where the robot is and where the parcels are. When the robot has decided to move somewhere, we need to update the model to reflect the new situation.

+ +

If you’re thinking in terms of object-oriented programming, your first impulse might be to start defining objects for the various elements in the world: a class for the robot, one for a parcel, maybe one for places. These could then hold properties that describe their current state, such as the pile of parcels at a location, which we could change when updating the world.

+ +

This is wrong.

+ +

At least, it usually is. The fact that something sounds like an object does not automatically mean that it should be an object in your program. Reflexively writing classes for every concept in your application tends to leave you with a collection of interconnected objects that each have their own internal, changing state. Such programs are often hard to understand and thus easy to break.

+ +

Instead, let’s condense the village’s state down to the minimal set of values that define it. There’s the robot’s current location and the collection of undelivered parcels, each of which has a current location and a destination address. That’s it.

+ +

And while we’re at it, let’s make it so that we don’t change this state when the robot moves but rather compute a new state for the situation after the move.

+ +
class VillageState {
+  constructor(place, parcels) {
+    this.place = place;
+    this.parcels = parcels;
+  }
+
+  move(destination) {
+    if (!roadGraph[this.place].includes(destination)) {
+      return this;
+    } else {
+      let parcels = this.parcels.map(p => {
+        if (p.place != this.place) return p;
+        return {place: destination, address: p.address};
+      }).filter(p => p.place != p.address);
+      return new VillageState(destination, parcels);
+    }
+  }
+}
+ +

The move method is where the action happens. It first checks whether there is a road going from the current place to the destination, and if not, it returns the old state since this is not a valid move.

+ +

Then it creates a new state with the destination as the robot’s new place. But it also needs to create a new set of parcels—parcels that the robot is carrying (that are at the robot’s current place) need to be moved along to the new place. And parcels that are addressed to the new place need to be delivered—that is, they need to be removed from the set of undelivered parcels. The call to map takes care of the moving, and the call to filter does the delivering.

+ +

Parcel objects aren’t changed when they are moved but re-created. The move method gives us a new village state but leaves the old one entirely intact.

+ +
let first = new VillageState(
+  "Post Office",
+  [{place: "Post Office", address: "Alice's House"}]
+);
+let next = first.move("Alice's House");
+
+console.log(next.place);
+// → Alice's House
+console.log(next.parcels);
+// → []
+console.log(first.place);
+// → Post Office
+ +

The move causes the parcel to be delivered, and this is reflected in the next state. But the initial state still describes the situation where the robot is at the post office and the parcel is undelivered.

+ +

Persistent data

+ +

Data structures that don’t change are called immutable or persistent. They behave a lot like strings and numbers in that they are who they are and stay that way, rather than containing different things at different times.

+ +

In JavaScript, just about everything can be changed, so working with values that are supposed to be persistent requires some restraint. There is a function called Object.freeze that changes an object so that writing to its properties is ignored. You could use that to make sure your objects aren’t changed, if you want to be careful. Freezing does require the computer to do some extra work, and having updates ignored is just about as likely to confuse someone as having them do the wrong thing. So I usually prefer to just tell people that a given object shouldn’t be messed with and hope they remember it.

+ +
let object = Object.freeze({value: 5});
+object.value = 10;
+console.log(object.value);
+// → 5
+ +

Why am I going out of my way to not change objects when the language is obviously expecting me to?

+ +

Because it helps me understand my programs. This is about complexity management again. When the objects in my system are fixed, stable things, I can consider operations on them in isolation—moving to Alice’s house from a given start state always produces the same new state. When objects change over time, that adds a whole new dimension of complexity to this kind of reasoning.

+ +

For a small system like the one we are building in this chapter, we could handle that bit of extra complexity. But the most important limit on what kind of systems we can build is how much we can understand. Anything that makes your code easier to understand makes it possible to build a more ambitious system.

+ +

Unfortunately, although understanding a system built on persistent data structures is easier, designing one, especially when your programming language isn’t helping, can be a little harder. We’ll look for opportunities to use persistent data structures in this book, but we’ll also be using changeable ones.

+ +

Simulation

+ +

A delivery robot looks at the world and decides in which direction it wants to move. As such, we could say that a robot is a function that takes a VillageState object and returns the name of a nearby place.

+ +

Because we want robots to be able to remember things, so that they can make and execute plans, we also pass them their memory and allow them to return a new memory. Thus, the thing a robot returns is an object containing both the direction it wants to move in and a memory value that will be given back to it the next time it is called.

+ +
function runRobot(state, robot, memory) {
+  for (let turn = 0;; turn++) {
+    if (state.parcels.length == 0) {
+      console.log(`Done in ${turn} turns`);
+      break;
+    }
+    let action = robot(state, memory);
+    state = state.move(action.direction);
+    memory = action.memory;
+    console.log(`Moved to ${action.direction}`);
+  }
+}
+ +

Consider what a robot has to do to “solve” a given state. It must pick up all parcels by visiting every location that has a parcel and deliver them by visiting every location that a parcel is addressed to, but only after picking up the parcel.

+ +

What is the dumbest strategy that could possibly work? The robot could just walk in a random direction every turn. That means, with great likelihood, it will eventually run into all parcels and then also at some point reach the place where they should be delivered.

+ +

Here’s what that could look like:

+ +
function randomPick(array) {
+  let choice = Math.floor(Math.random() * array.length);
+  return array[choice];
+}
+
+function randomRobot(state) {
+  return {direction: randomPick(roadGraph[state.place])};
+}
+ +

Remember that Math.random() returns a number between zero and one—but always below one. Multiplying such a number by the length of an array and then applying Math.floor to it gives us a random index for the array.

+ +

Since this robot does not need to remember anything, it ignores its second argument (remember that JavaScript functions can be called with extra arguments without ill effects) and omits the memory property in its returned object.

+ +

To put this sophisticated robot to work, we’ll first need a way to create a new state with some parcels. A static method (written here by directly adding a property to the constructor) is a good place to put that functionality.

+ +
VillageState.random = function(parcelCount = 5) {
+  let parcels = [];
+  for (let i = 0; i < parcelCount; i++) {
+    let address = randomPick(Object.keys(roadGraph));
+    let place;
+    do {
+      place = randomPick(Object.keys(roadGraph));
+    } while (place == address);
+    parcels.push({place, address});
+  }
+  return new VillageState("Post Office", parcels);
+};
+ +

We don’t want any parcels that are sent from the same place that they are addressed to. For this reason, the do loop keeps picking new places when it gets one that’s equal to the address.

+ +

Let’s start up a virtual world.

+ +
runRobot(VillageState.random(), randomRobot);
+// → Moved to Marketplace
+// → Moved to Town Hall
+// → …
+// → Done in 63 turns
+ +

It takes the robot a lot of turns to deliver the parcels because it isn’t planning ahead very well. We’ll address that soon.

+ +

For a more pleasant perspective on the simulation, you can use the runRobotAnimation function that’s available in this chapter’s programming environment. This runs the simulation, but instead of outputting text, it shows you the robot moving around the village map.

+ +
runRobotAnimation(VillageState.random(), randomRobot);
+ +

The way runRobotAnimation is implemented will remain a mystery for now, but after you’ve read the later chapters of this book, which discuss JavaScript integration in web browsers, you’ll be able to guess how it works.

+ +

The mail truck’s route

+ +

We should be able to do a lot better than the random robot. An easy improvement would be to take a hint from the way real-world mail delivery works. If we find a route that passes all places in the village, the robot could run that route twice, at which point it is guaranteed to be done. Here is one such route (starting from the post office):

+ +
const mailRoute = [
+  "Alice's House", "Cabin", "Alice's House", "Bob's House",
+  "Town Hall", "Daria's House", "Ernie's House",
+  "Grete's House", "Shop", "Grete's House", "Farm",
+  "Marketplace", "Post Office"
+];
+ +

To implement the route-following robot, we’ll need to make use of robot memory. The robot keeps the rest of its route in its memory and drops the first element every turn.

+ +
function routeRobot(state, memory) {
+  if (memory.length == 0) {
+    memory = mailRoute;
+  }
+  return {direction: memory[0], memory: memory.slice(1)};
+}
+ +

This robot is a lot faster already. It’ll take a maximum of 26 turns (twice the 13-step route) but usually less.

+ +
runRobotAnimation(VillageState.random(), routeRobot, []);
+ +

Pathfinding

+ +

Still, I wouldn’t really call blindly following a fixed route intelligent behavior. The robot could work more efficiently if it adjusted its behavior to the actual work that needs to be done.

+ +

To do that, it has to be able to deliberately move toward a given parcel or toward the location where a parcel has to be delivered. Doing that, even when the goal is more than one move away, will require some kind of route-finding function.

+ +

The problem of finding a route through a graph is a typical search problem. We can tell whether a given solution (a route) is a valid solution, but we can’t directly compute the solution the way we could for 2 + 2. Instead, we have to keep creating potential solutions until we find one that works.

+ +

The number of possible routes through a graph is infinite. But when searching for a route from A to B, we are interested only in the ones that start at A. We also don’t care about routes that visit the same place twice—those are definitely not the most efficient route anywhere. So that cuts down on the number of routes that the route finder has to consider.

+ +

In fact, we are mostly interested in the shortest route. So we want to make sure we look at short routes before we look at longer ones. A good approach would be to “grow” routes from the starting point, exploring every reachable place that hasn’t been visited yet, until a route reaches the goal. That way, we’ll only explore routes that are potentially interesting, and we’ll find the shortest route (or one of the shortest routes, if there are more than one) to the goal.

+ +

Here is a function that does this:

+ +
function findRoute(graph, from, to) {
+  let work = [{at: from, route: []}];
+  for (let i = 0; i < work.length; i++) {
+    let {at, route} = work[i];
+    for (let place of graph[at]) {
+      if (place == to) return route.concat(place);
+      if (!work.some(w => w.at == place)) {
+        work.push({at: place, route: route.concat(place)});
+      }
+    }
+  }
+}
+ +

The exploring has to be done in the right order—the places that were reached first have to be explored first. We can’t immediately explore a place as soon as we reach it because that would mean places reached from there would also be explored immediately, and so on, even though there may be other, shorter paths that haven’t yet been explored.

+ +

Therefore, the function keeps a work list. This is an array of places that should be explored next, along with the route that got us there. It starts with just the start position and an empty route.

+ +

The search then operates by taking the next item in the list and exploring that, which means all roads going from that place are looked at. If one of them is the goal, a finished route can be returned. Otherwise, if we haven’t looked at this place before, a new item is added to the list. If we have looked at it before, since we are looking at short routes first, we’ve found either a longer route to that place or one precisely as long as the existing one, and we don’t need to explore it.

+ +

You can visually imagine this as a web of known routes crawling out from the start location, growing evenly on all sides (but never tangling back into itself). As soon as the first thread reaches the goal location, that thread is traced back to the start, giving us our route.

+ +

Our code doesn’t handle the situation where there are no more work items on the work list because we know that our graph is connected, meaning that every location can be reached from all other locations. We’ll always be able to find a route between two points, and the search can’t fail.

+ +
function goalOrientedRobot({place, parcels}, route) {
+  if (route.length == 0) {
+    let parcel = parcels[0];
+    if (parcel.place != place) {
+      route = findRoute(roadGraph, place, parcel.place);
+    } else {
+      route = findRoute(roadGraph, place, parcel.address);
+    }
+  }
+  return {direction: route[0], memory: route.slice(1)};
+}
+ +

This robot uses its memory value as a list of directions to move in, just like the route-following robot. Whenever that list is empty, it has to figure out what to do next. It takes the first undelivered parcel in the set and, if that parcel hasn’t been picked up yet, plots a route toward it. If the parcel has been picked up, it still needs to be delivered, so the robot creates a route toward the delivery address instead.

+ +

Let’s see how it does.

+ +
runRobotAnimation(VillageState.random(),
+                  goalOrientedRobot, []);
+ +

This robot usually finishes the task of delivering 5 parcels in about 16 turns. That’s slightly better than routeRobot but still definitely not optimal.

+ +

Exercises

+ +

Measuring a robot

+ +

It’s hard to objectively compare robots by just letting them solve a few scenarios. Maybe one robot just happened to get easier tasks or the kind of tasks that it is good at, whereas the other didn’t.

+ +

Write a function compareRobots that takes two robots (and their starting memory). It should generate 100 tasks and let each of the robots solve each of these tasks. When done, it should output the average number of steps each robot took per task.

+ +

For the sake of fairness, make sure you give each task to both robots, rather than generating different tasks per robot.

+ +
function compareRobots(robot1, memory1, robot2, memory2) {
+  // Your code here
+}
+
+compareRobots(routeRobot, [], goalOrientedRobot, []);
+ +
+ +

You’ll have to write a variant of the runRobot function that, instead of logging the events to the console, returns the number of steps the robot took to complete the task.

+ +

Your measurement function can then, in a loop, generate new states and count the steps each of the robots takes. When it has generated enough measurements, it can use console.log to output the average for each robot, which is the total number of steps taken divided by the number of measurements.

+ +
+ +

Robot efficiency

+ +

Can you write a robot that finishes the delivery task faster than goalOrientedRobot? If you observe that robot’s behavior, what obviously stupid things does it do? How could those be improved?

+ +

If you solved the previous exercise, you might want to use your compareRobots function to verify whether you improved the robot.

+ +
// Your code here
+
+runRobotAnimation(VillageState.random(), yourRobot, memory);
+ +
+ +

The main limitation of goalOrientedRobot is that it considers only one parcel at a time. It will often walk back and forth across the village because the parcel it happens to be looking at happens to be at the other side of the map, even if there are others much closer.

+ +

One possible solution would be to compute routes for all packages and then take the shortest one. Even better results can be obtained, if there are multiple shortest routes, by preferring the ones that go to pick up a package instead of delivering a package.

+ +
+ +

Persistent group

+ +

Most data structures provided in a standard JavaScript environment aren’t very well suited for persistent use. Arrays have slice and concat methods, which allow us to easily create new arrays without damaging the old one. But Set, for example, has no methods for creating a new set with an item added or removed.

+ +

Write a new class PGroup, similar to the Group class from Chapter 6, which stores a set of values. Like Group, it has add, delete, and has methods.

+ +

Its add method, however, should return a new PGroup instance with the given member added and leave the old one unchanged. Similarly, delete creates a new instance without a given member.

+ +

The class should work for values of any type, not just strings. It does not have to be efficient when used with large amounts of values.

+ +

The constructor shouldn’t be part of the class’s interface (though you’ll definitely want to use it internally). Instead, there is an empty instance, PGroup.empty, that can be used as a starting value.

+ +

Why do you need only one PGroup.empty value, rather than having a function that creates a new, empty map every time?

+ +
class PGroup {
+  // Your code here
+}
+
+let a = PGroup.empty.add("a");
+let ab = a.add("b");
+let b = ab.delete("a");
+
+console.log(b.has("b"));
+// → true
+console.log(a.has("b"));
+// → false
+console.log(b.has("a"));
+// → false
+ +
+ +

The most convenient way to represent the set of member values is still as an array since arrays are easy to copy.

+ +

When a value is added to the group, you can create a new group with a copy of the original array that has the value added (for example, using concat). When a value is deleted, you filter it from the array.

+ +

The class’s constructor can take such an array as argument and store it as the instance’s (only) property. This array is never updated.

+ +

To add a property (empty) to a constructor that is not a method, you have to add it to the constructor after the class definition, as a regular property.

+ +

You need only one empty instance because all empty groups are the same and instances of the class don’t change. You can create many different groups from that single empty group without affecting it.

+ +
+
diff --git a/docs/08_error.html b/docs/08_error.html new file mode 100644 index 000000000..109e7120b --- /dev/null +++ b/docs/08_error.html @@ -0,0 +1,485 @@ + + + + + Bugs and Errors :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 8Bugs and Errors

+ +
+ +

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

+ +
Brian Kernighan and P.J. Plauger, The Elements of Programming Style
+ +
Picture of a collection of bugs
+ +

Flaws in computer programs are usually called bugs. It makes programmers feel good to imagine them as little things that just happen to crawl into our work. In reality, of course, we put them there ourselves.

+ +

If a program is crystallized thought, you can roughly categorize bugs into those caused by the thoughts being confused and those caused by mistakes introduced while converting a thought to code. The former type is generally harder to diagnose and fix than the latter.

+ +

Language

+ +

Many mistakes could be pointed out to us automatically by the computer, if it knew enough about what we’re trying to do. But here JavaScript’s looseness is a hindrance. Its concept of bindings and properties is vague enough that it will rarely catch typos before actually running the program. And even then, it allows you to do some clearly nonsensical things without complaint, such as computing true * "monkey".

+ +

There are some things that JavaScript does complain about. Writing a program that does not follow the language’s grammar will immediately make the computer complain. Other things, such as calling something that’s not a function or looking up a property on an undefined value, will cause an error to be reported when the program tries to perform the action.

+ +

But often, your nonsense computation will merely produce NaN (not a number) or an undefined value, while the program happily continues, convinced that it’s doing something meaningful. The mistake will manifest itself only later, after the bogus value has traveled through several functions. It might not trigger an error at all but silently cause the program’s output to be wrong. Finding the source of such problems can be difficult.

+ +

The process of finding mistakes—bugs—in programs is called debugging.

+ +

Strict mode

+ +

JavaScript can be made a little stricter by enabling strict mode. This is done by putting the string "use strict" at the top of a file or a function body. Here’s an example:

+ +
function canYouSpotTheProblem() {
+  "use strict";
+  for (counter = 0; counter < 10; counter++) {
+    console.log("Happy happy");
+  }
+}
+
+canYouSpotTheProblem();
+// → ReferenceError: counter is not defined
+ +

Normally, when you forget to put let in front of your binding, as with counter in the example, JavaScript quietly creates a global binding and uses that. In strict mode, an error is reported instead. This is very helpful. It should be noted, though, that this doesn’t work when the binding in question already exists as a global binding. In that case, the loop will still quietly overwrite the value of the binding.

+ +

Another change in strict mode is that the this binding holds the value undefined in functions that are not called as methods. When making such a call outside of strict mode, this refers to the global scope object, which is an object whose properties are the global bindings. So if you accidentally call a method or constructor incorrectly in strict mode, JavaScript will produce an error as soon as it tries to read something from this, rather than happily writing to the global scope.

+ +

For example, consider the following code, which calls a constructor function without the new keyword so that its this will not refer to a newly constructed object:

+ +
function Person(name) { this.name = name; }
+let ferdinand = Person("Ferdinand"); // oops
+console.log(name);
+// → Ferdinand
+ +

So the bogus call to Person succeeded but returned an undefined value and created the global binding name. In strict mode, the result is different.

+ +
"use strict";
+function Person(name) { this.name = name; }
+let ferdinand = Person("Ferdinand"); // forgot new
+// → TypeError: Cannot set property 'name' of undefined
+ +

We are immediately told that something is wrong. This is helpful.

+ +

Fortunately, constructors created with the class notation will always complain if they are called without new, making this less of a problem even in non-strict mode.

+ +

Strict mode does a few more things. It disallows giving a function multiple parameters with the same name and removes certain problematic language features entirely (such as the with statement, which is so wrong it is not further discussed in this book).

+ +

In short, putting "use strict" at the top of your program rarely hurts and might help you spot a problem.

+ +

Types

+ +

Some languages want to know the types of all your bindings and expressions before even running a program. They will tell you right away when a type is used in an inconsistent way. JavaScript considers types only when actually running the program, and even there often tries to implicitly convert values to the type it expects, so it’s not much help.

+ +

Still, types provide a useful framework for talking about programs. A lot of mistakes come from being confused about the kind of value that goes into or comes out of a function. If you have that information written down, you’re less likely to get confused.

+ +

You could add a comment like the following before the goalOrientedRobot function from the previous chapter to describe its type:

+ +
// (VillageState, Array) → {direction: string, memory: Array}
+function goalOrientedRobot(state, memory) {
+  // ...
+}
+ +

There are a number of different conventions for annotating JavaScript programs with types.

+ +

One thing about types is that they need to introduce their own complexity to be able to describe enough code to be useful. What do you think would be the type of the randomPick function that returns a random element from an array? You’d need to introduce a type +variable, T, which can stand in for any type, so that you can give randomPick a type like ([T]) → T (function from an array of Ts to a T).

+ +

When the types of a program are known, it is possible for the computer to check them for you, pointing out mistakes before the program is run. There are several JavaScript dialects that add types to the language and check them. The most popular one is called TypeScript. If you are interested in adding more rigor to your programs, I recommend you give it a try.

+ +

In this book, we’ll continue using raw, dangerous, untyped JavaScript code.

+ +

Testing

+ +

If the language is not going to do much to help us find mistakes, we’ll have to find them the hard way: by running the program and seeing whether it does the right thing.

+ +

Doing this by hand, again and again, is a really bad idea. Not only is it annoying, it also tends to be ineffective since it takes too much time to exhaustively test everything every time you make a change.

+ +

Computers are good at repetitive tasks, and testing is the ideal repetitive task. Automated testing is the process of writing a program that tests another program. Writing tests is a bit more work than testing manually, but once you’ve done it, you gain a kind of superpower: it takes you only a few seconds to verify that your program still behaves properly in all the situations you wrote tests for. When you break something, you’ll immediately notice, rather than randomly running into it at some later time.

+ +

Tests usually take the form of little labeled programs that verify some aspect of your code. For example, a set of tests for the (standard, probably already tested by someone else) toUpperCase method might look like this:

+ +
function test(label, body) {
+  if (!body()) console.log(`Failed: ${label}`);
+}
+
+test("convert Latin text to uppercase", () => {
+  return "hello".toUpperCase() == "HELLO";
+});
+test("convert Greek text to uppercase", () => {
+  return "Χαίρετε".toUpperCase() == "ΧΑΊΡΕΤΕ";
+});
+test("don't convert case-less characters", () => {
+  return "مرحبا".toUpperCase() == "مرحبا";
+});
+ +

Writing tests like this tends to produce rather repetitive, awkward code. Fortunately, there exist pieces of software that help you build and run collections of tests (test suites) by providing a language (in the form of functions and methods) suited to expressing tests and by outputting informative information when a test fails. These are usually called test runners.

+ +

Some code is easier to test than other code. Generally, the more external objects that the code interacts with, the harder it is to set up the context in which to test it. The style of programming shown in the previous chapter, which uses self-contained persistent values rather than changing objects, tends to be easy to test.

+ +

Debugging

+ +

Once you notice there is something wrong with your program because it misbehaves or produces errors, the next step is to figure out what the problem is.

+ +

Sometimes it is obvious. The error message will point at a specific line of your program, and if you look at the error description and that line of code, you can often see the problem.

+ +

But not always. Sometimes the line that triggered the problem is simply the first place where a flaky value produced elsewhere gets used in an invalid way. If you have been solving the exercises in earlier chapters, you will probably have already experienced such situations.

+ +

The following example program tries to convert a whole number to a string in a given base (decimal, binary, and so on) by repeatedly picking out the last digit and then dividing the number to get rid of this digit. But the strange output that it currently produces suggests that it has a bug.

+ +
function numberToString(n, base = 10) {
+  let result = "", sign = "";
+  if (n < 0) {
+    sign = "-";
+    n = -n;
+  }
+  do {
+    result = String(n % base) + result;
+    n /= base;
+  } while (n > 0);
+  return sign + result;
+}
+console.log(numberToString(13, 10));
+// → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…
+ +

Even if you see the problem already, pretend for a moment that you don’t. We know that our program is malfunctioning, and we want to find out why.

+ +

This is where you must resist the urge to start making random changes to the code to see whether that makes it better. Instead, think. Analyze what is happening and come up with a theory of why it might be happening. Then, make additional observations to test this theory—or, if you don’t yet have a theory, make additional observations to help you come up with one.

+ +

Putting a few strategic console.log calls into the program is a good way to get additional information about what the program is doing. In this case, we want n to take the values 13, 1, and then 0. Let’s write out its value at the start of the loop.

+ +
13
+1.3
+0.13
+0.013
+…
+1.5e-323
+ +

Right. Dividing 13 by 10 does not produce a whole number. Instead of n /= base, what we actually want is n = Math.floor(n / base) so that the number is properly “shifted” to the right.

+ +

An alternative to using console.log to peek into the program’s behavior is to use the debugger capabilities of your browser. Browsers come with the ability to set a breakpoint on a specific line of your code. When the execution of the program reaches a line with a breakpoint, it is paused, and you can inspect the values of bindings at that point. I won’t go into details, as debuggers differ from browser to browser, but look in your browser’s developer +tools or search the Web for more information.

+ +

Another way to set a breakpoint is to include a debugger statement (consisting of simply that keyword) in your program. If the developer tools of your browser are active, the program will pause whenever it reaches such a statement.

+ +

Error propagation

+ +

Not all problems can be prevented by the programmer, unfortunately. If your program communicates with the outside world in any way, it is possible to get malformed input, to become overloaded with work, or to have the network fail.

+ +

If you’re programming only for yourself, you can afford to just ignore such problems until they occur. But if you build something that is going to be used by anybody else, you usually want the program to do better than just crash. Sometimes the right thing to do is take the bad input in stride and continue running. In other cases, it is better to report to the user what went wrong and then give up. But in either situation, the program has to actively do something in response to the problem.

+ +

Say you have a function promptNumber that asks the user for a number and returns it. What should it return if the user inputs “orange”?

+ +

One option is to make it return a special value. Common choices for such values are null, undefined, or -1.

+ +
function promptNumber(question) {
+  let result = Number(prompt(question));
+  if (Number.isNaN(result)) return null;
+  else return result;
+}
+
+console.log(promptNumber("How many trees do you see?"));
+ +

Now any code that calls promptNumber must check whether an actual number was read and, failing that, must somehow recover—maybe by asking again or by filling in a default value. Or it could again return a special value to its caller to indicate that it failed to do what it was asked.

+ +

In many situations, mostly when errors are common and the caller should be explicitly taking them into account, returning a special value is a good way to indicate an error. It does, however, have its downsides. First, what if the function can already return every possible kind of value? In such a function, you’ll have to do something like wrap the result in an object to be able to distinguish success from failure.

+ +
function lastElement(array) {
+  if (array.length == 0) {
+    return {failed: true};
+  } else {
+    return {element: array[array.length - 1]};
+  }
+}
+ +

The second issue with returning special values is that it can lead to awkward code. If a piece of code calls promptNumber 10 times, it has to check 10 times whether null was returned. And if its response to finding null is to simply return null itself, callers of the function will in turn have to check for it, and so on.

+ +

Exceptions

+ +

When a function cannot proceed normally, what we would like to do is just stop what we are doing and immediately jump to a place that knows how to handle the problem. This is what exception handling does.

+ +

Exceptions are a mechanism that makes it possible for code that runs into a problem to raise (or throw) an exception. An exception can be any value. Raising one somewhat resembles a super-charged return from a function: it jumps out of not just the current function but also its callers, all the way down to the first call that started the current execution. This is called unwinding the +stack. You may remember the stack of function calls that was mentioned in Chapter 3. An exception zooms down this stack, throwing away all the call contexts it encounters.

+ +

If exceptions always zoomed right down to the bottom of the stack, they would not be of much use. They’d just provide a novel way to blow up your program. Their power lies in the fact that you can set “obstacles” along the stack to catch the exception as it is zooming down. Once you’ve caught an exception, you can do something with it to address the problem and then continue to run the program.

+ +

Here’s an example:

+ +
function promptDirection(question) {
+  let result = prompt(question);
+  if (result.toLowerCase() == "left") return "L";
+  if (result.toLowerCase() == "right") return "R";
+  throw new Error("Invalid direction: " + result);
+}
+
+function look() {
+  if (promptDirection("Which way?") == "L") {
+    return "a house";
+  } else {
+    return "two angry bears";
+  }
+}
+
+try {
+  console.log("You see", look());
+} catch (error) {
+  console.log("Something went wrong: " + error);
+}
+ +

The throw keyword is used to raise an exception. Catching one is done by wrapping a piece of code in a try block, followed by the keyword catch. When the code in the try block causes an exception to be raised, the catch block is evaluated, with the name in parentheses bound to the exception value. After the catch block finishes—or if the try block finishes without problems—the program proceeds beneath the entire try/catch statement.

+ +

In this case, we used the Error constructor to create our exception value. This is a standard JavaScript constructor that creates an object with a message property. In most JavaScript environments, instances of this constructor also gather information about the call stack that existed when the exception was created, a so-called stack trace. This information is stored in the stack property and can be helpful when trying to debug a problem: it tells us the function where the problem occurred and which functions made the failing call.

+ +

Note that the look function completely ignores the possibility that promptDirection might go wrong. This is the big advantage of exceptions: error-handling code is necessary only at the point where the error occurs and at the point where it is handled. The functions in between can forget all about it.

+ +

Well, almost...

+ +

Cleaning up after exceptions

+ +

The effect of an exception is another kind of control flow. Every action that might cause an exception, which is pretty much every function call and property access, might cause control to suddenly leave your code.

+ +

This means when code has several side effects, even if its “regular” control flow looks like they’ll always all happen, an exception might prevent some of them from taking place.

+ +

Here is some really bad banking code.

+ +
const accounts = {
+  a: 100,
+  b: 0,
+  c: 20
+};
+
+function getAccount() {
+  let accountName = prompt("Enter an account name");
+  if (!accounts.hasOwnProperty(accountName)) {
+    throw new Error(`No such account: ${accountName}`);
+  }
+  return accountName;
+}
+
+function transfer(from, amount) {
+  if (accounts[from] < amount) return;
+  accounts[from] -= amount;
+  accounts[getAccount()] += amount;
+}
+ +

The transfer function transfers a sum of money from a given account to another, asking for the name of the other account in the process. If given an invalid account name, getAccount throws an exception.

+ +

But transfer first removes the money from the account and then calls getAccount before it adds it to another account. If it is broken off by an exception at that point, it’ll just make the money disappear.

+ +

That code could have been written a little more intelligently, for example by calling getAccount before it starts moving money around. But often problems like this occur in more subtle ways. Even functions that don’t look like they will throw an exception might do so in exceptional circumstances or when they contain a programmer mistake.

+ +

One way to address this is to use fewer side effects. Again, a programming style that computes new values instead of changing existing data helps. If a piece of code stops running in the middle of creating a new value, no one ever sees the half-finished value, and there is no problem.

+ +

But that isn’t always practical. So there is another feature that try statements have. They may be followed by a finally block either instead of or in addition to a catch block. A finally block says “no matter what happens, run this code after trying to run the code in the try block.”

+ +
function transfer(from, amount) {
+  if (accounts[from] < amount) return;
+  let progress = 0;
+  try {
+    accounts[from] -= amount;
+    progress = 1;
+    accounts[getAccount()] += amount;
+    progress = 2;
+  } finally {
+    if (progress == 1) {
+      accounts[from] += amount;
+    }
+  }
+}
+ +

This version of the function tracks its progress, and if, when leaving, it notices that it was aborted at a point where it had created an inconsistent program state, it repairs the damage it did.

+ +

Note that even though the finally code is run when an exception is thrown in the try block, it does not interfere with the exception. After the finally block runs, the stack continues unwinding.

+ +

Writing programs that operate reliably even when exceptions pop up in unexpected places is hard. Many people simply don’t bother, and because exceptions are typically reserved for exceptional circumstances, the problem may occur so rarely that it is never even noticed. Whether that is a good thing or a really bad thing depends on how much damage the software will do when it fails.

+ +

Selective catching

+ +

When an exception makes it all the way to the bottom of the stack without being caught, it gets handled by the environment. What this means differs between environments. In browsers, a description of the error typically gets written to the JavaScript console (reachable through the browser’s Tools or Developer menu). Node.js, the browserless JavaScript environment we will discuss in Chapter 20, is more careful about data corruption. It aborts the whole process when an unhandled exception occurs.

+ +

For programmer mistakes, just letting the error go through is often the best you can do. An unhandled exception is a reasonable way to signal a broken program, and the JavaScript console will, on modern browsers, provide you with some information about which function calls were on the stack when the problem occurred.

+ +

For problems that are expected to happen during routine use, crashing with an unhandled exception is a terrible strategy.

+ +

Invalid uses of the language, such as referencing a nonexistent binding, looking up a property on null, or calling something that’s not a function, will also result in exceptions being raised. Such exceptions can also be caught.

+ +

When a catch body is entered, all we know is that something in our try body caused an exception. But we don’t know what did or which exception it caused.

+ +

JavaScript (in a rather glaring omission) doesn’t provide direct support for selectively catching exceptions: either you catch them all or you don’t catch any. This makes it tempting to assume that the exception you get is the one you were thinking about when you wrote the catch block.

+ +

But it might not be. Some other assumption might be violated, or you might have introduced a bug that is causing an exception. Here is an example that attempts to keep on calling promptDirection until it gets a valid answer:

+ +
for (;;) {
+  try {
+    let dir = promtDirection("Where?"); // ← typo!
+    console.log("You chose ", dir);
+    break;
+  } catch (e) {
+    console.log("Not a valid direction. Try again.");
+  }
+}
+ +

The for (;;) construct is a way to intentionally create a loop that doesn’t terminate on its own. We break out of the loop only when a valid direction is given. But we misspelled promptDirection, which will result in an “undefined variable” error. Because the catch block completely ignores its exception value (e), assuming it knows what the problem is, it wrongly treats the binding error as indicating bad input. Not only does this cause an infinite loop, it “buries” the useful error message about the misspelled binding.

+ +

As a general rule, don’t blanket-catch exceptions unless it is for the purpose of “routing” them somewhere—for example, over the network to tell another system that our program crashed. And even then, think carefully about how you might be hiding information.

+ +

So we want to catch a specific kind of exception. We can do this by checking in the catch block whether the exception we got is the one we are interested in and rethrowing it otherwise. But how do we recognize an exception?

+ +

We could compare its message property against the error message we happen to expect. But that’s a shaky way to write code—we’d be using information that’s intended for human consumption (the message) to make a programmatic decision. As soon as someone changes (or translates) the message, the code will stop working.

+ +

Rather, let’s define a new type of error and use instanceof to identify it.

+ +
class InputError extends Error {}
+
+function promptDirection(question) {
+  let result = prompt(question);
+  if (result.toLowerCase() == "left") return "L";
+  if (result.toLowerCase() == "right") return "R";
+  throw new InputError("Invalid direction: " + result);
+}
+ +

The new error class extends Error. It doesn’t define its own constructor, which means that it inherits the Error constructor, which expects a string message as argument. In fact, it doesn’t define anything at all—the class is empty. InputError objects behave like Error objects, except that they have a different class by which we can recognize them.

+ +

Now the loop can catch these more carefully.

+ +
for (;;) {
+  try {
+    let dir = promptDirection("Where?");
+    console.log("You chose ", dir);
+    break;
+  } catch (e) {
+    if (e instanceof InputError) {
+      console.log("Not a valid direction. Try again.");
+    } else {
+      throw e;
+    }
+  }
+}
+ +

This will catch only instances of InputError and let unrelated exceptions through. If you reintroduce the typo, the undefined binding error will be properly reported.

+ +

Assertions

+ +

Assertions are checks inside a program that verify that something is the way it is supposed to be. They are used not to handle situations that can come up in normal operation but to find programmer mistakes.

+ +

If, for example, firstElement is described as a function that should never be called on empty arrays, we might write it like this:

+ +
function firstElement(array) {
+  if (array.length == 0) {
+    throw new Error("firstElement called with []");
+  }
+  return array[0];
+}
+ +

Now, instead of silently returning undefined (which you get when reading an array property that does not exist), this will loudly blow up your program as soon as you misuse it. This makes it less likely for such mistakes to go unnoticed and easier to find their cause when they occur.

+ +

I do not recommend trying to write assertions for every possible kind of bad input. That’d be a lot of work and would lead to very noisy code. You’ll want to reserve them for mistakes that are easy to make (or that you find yourself making).

+ +

Summary

+ +

Mistakes and bad input are facts of life. An important part of programming is finding, diagnosing, and fixing bugs. Problems can become easier to notice if you have an automated test suite or add assertions to your programs.

+ +

Problems caused by factors outside the program’s control should usually be handled gracefully. Sometimes, when the problem can be handled locally, special return values are a good way to track them. Otherwise, exceptions may be preferable.

+ +

Throwing an exception causes the call stack to be unwound until the next enclosing try/catch block or until the bottom of the stack. The exception value will be given to the catch block that catches it, which should verify that it is actually the expected kind of exception and then do something with it. To help address the unpredictable control flow caused by exceptions, finally blocks can be used to ensure that a piece of code always runs when a block finishes.

+ +

Exercises

+ +

Retry

+ +

Say you have a function primitiveMultiply that in 20 percent of cases multiplies two numbers and in the other 80 percent of cases raises an exception of type MultiplicatorUnitFailure. Write a function that wraps this clunky function and just keeps trying until a call succeeds, after which it returns the result.

+ +

Make sure you handle only the exceptions you are trying to handle.

+ +
class MultiplicatorUnitFailure extends Error {}
+
+function primitiveMultiply(a, b) {
+  if (Math.random() < 0.2) {
+    return a * b;
+  } else {
+    throw new MultiplicatorUnitFailure("Klunk");
+  }
+}
+
+function reliableMultiply(a, b) {
+  // Your code here.
+}
+
+console.log(reliableMultiply(8, 8));
+// → 64
+ +
+ +

The call to primitiveMultiply should definitely happen in a try block. The corresponding catch block should rethrow the exception when it is not an instance of MultiplicatorUnitFailure and ensure the call is retried when it is.

+ +

To do the retrying, you can either use a loop that stops only when a call succeeds—as in the look example earlier in this chapter—or use recursion and hope you don’t get a string of failures so long that it overflows the stack (which is a pretty safe bet).

+ +
+ +

The locked box

+ +

Consider the following (rather contrived) object:

+ +
const box = {
+  locked: true,
+  unlock() { this.locked = false; },
+  lock() { this.locked = true;  },
+  _content: [],
+  get content() {
+    if (this.locked) throw new Error("Locked!");
+    return this._content;
+  }
+};
+ +

It is a box with a lock. There is an array in the box, but you can get at it only when the box is unlocked. Directly accessing the private _content property is forbidden.

+ +

Write a function called withBoxUnlocked that takes a function value as argument, unlocks the box, runs the function, and then ensures that the box is locked again before returning, regardless of whether the argument function returned normally or threw an exception.

+ +
const box = {
+  locked: true,
+  unlock() { this.locked = false; },
+  lock() { this.locked = true;  },
+  _content: [],
+  get content() {
+    if (this.locked) throw new Error("Locked!");
+    return this._content;
+  }
+};
+
+function withBoxUnlocked(body) {
+  // Your code here.
+}
+
+withBoxUnlocked(function() {
+  box.content.push("gold piece");
+});
+
+try {
+  withBoxUnlocked(function() {
+    throw new Error("Pirates on the horizon! Abort!");
+  });
+} catch (e) {
+  console.log("Error raised: " + e);
+}
+console.log(box.locked);
+// → true
+ +

For extra points, make sure that if you call withBoxUnlocked when the box is already unlocked, the box stays unlocked.

+ +
+ +

This exercise calls for a finally block. Your function should first unlock the box and then call the argument function from inside a try body. The finally block after it should lock the box again.

+ +

To make sure we don’t lock the box when it wasn’t already locked, check its lock at the start of the function and unlock and lock it only when it started out locked.

+ +
+
diff --git a/docs/09_regexp.html b/docs/09_regexp.html new file mode 100644 index 000000000..7c2b5b7d8 --- /dev/null +++ b/docs/09_regexp.html @@ -0,0 +1,853 @@ + + + + + Regular Expressions :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 9Regular Expressions

+ +
+ +

Some people, when confronted with a problem, think ‘I know, I’ll use regular expressions.’ Now they have two problems.

+ +
Jamie Zawinski
+ +
+ +
+ +

Yuan-Ma said, ‘When you cut against the grain of the wood, much strength is needed. When you program against the grain of the problem, much code is needed.’

+ +
Master Yuan-Ma, The Book of Programming
+ +
A railroad diagram
+ +

Programming tools and techniques survive and spread in a chaotic, evolutionary way. It’s not always the pretty or brilliant ones that win but rather the ones that function well enough within the right niche or that happen to be integrated with another successful piece of technology.

+ +

In this chapter, I will discuss one such tool, regular +expressions. Regular expressions are a way to describe patterns in string data. They form a small, separate language that is part of JavaScript and many other languages and systems.

+ +

Regular expressions are both terribly awkward and extremely useful. Their syntax is cryptic, and the programming interface JavaScript provides for them is clumsy. But they are a powerful tool for inspecting and processing strings. Properly understanding regular expressions will make you a more effective programmer.

+ +

Creating a regular expression

+ +

A regular expression is a type of object. It can be either constructed with the RegExp constructor or written as a literal value by enclosing a pattern in forward slash (/) characters.

+ +
let re1 = new RegExp("abc");
+let re2 = /abc/;
+ +

Both of those regular expression objects represent the same pattern: an a character followed by a b followed by a c.

+ +

When using the RegExp constructor, the pattern is written as a normal string, so the usual rules apply for backslashes.

+ +

The second notation, where the pattern appears between slash characters, treats backslashes somewhat differently. First, since a forward slash ends the pattern, we need to put a backslash before any forward slash that we want to be part of the pattern. In addition, backslashes that aren’t part of special character codes (like \n) will be preserved, rather than ignored as they are in strings, and change the meaning of the pattern. Some characters, such as question marks and plus signs, have special meanings in regular expressions and must be preceded by a backslash if they are meant to represent the character itself.

+ +
let eighteenPlus = /eighteen\+/;
+ +

Testing for matches

+ +

Regular expression objects have a number of methods. The simplest one is test. If you pass it a string, it will return a Boolean telling you whether the string contains a match of the pattern in the expression.

+ +
console.log(/abc/.test("abcde"));
+// → true
+console.log(/abc/.test("abxde"));
+// → false
+ +

A regular expression consisting of only nonspecial characters simply represents that sequence of characters. If abc occurs anywhere in the string we are testing against (not just at the start), test will return true.

+ +

Sets of characters

+ +

Finding out whether a string contains abc could just as well be done with a call to indexOf. Regular expressions allow us to express more complicated patterns.

+ +

Say we want to match any number. In a regular expression, putting a set of characters between square brackets makes that part of the expression match any of the characters between the brackets.

+ +

Both of the following expressions match all strings that contain a digit:

+ +
console.log(/[0123456789]/.test("in 1992"));
+// → true
+console.log(/[0-9]/.test("in 1992"));
+// → true
+ +

Within square brackets, a hyphen (-) between two characters can be used to indicate a range of characters, where the ordering is determined by the character’s Unicode number. Characters 0 to 9 sit right next to each other in this ordering (codes 48 to 57), so [0-9] covers all of them and matches any digit.

+ +

A number of common character groups have their own built-in shortcuts. Digits are one of them: \d means the same thing as [0-9].

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\dAny digit character
\wAn alphanumeric character (“word character”)
\sAny whitespace character (space, tab, newline, and similar)
\DA character that is not a digit
\WA nonalphanumeric character
\SA nonwhitespace character
.Any character except for newline
+ +

So you could match a date and time format like 01-30-2003 15:20 with the following expression:

+ +
let dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/;
+console.log(dateTime.test("01-30-2003 15:20"));
+// → true
+console.log(dateTime.test("30-jan-2003 15:20"));
+// → false
+ +

That looks completely awful, doesn’t it? Half of it is backslashes, producing a background noise that makes it hard to spot the actual pattern expressed. We’ll see a slightly improved version of this expression later.

+ +

These backslash codes can also be used inside square brackets. For example, [\d.] means any digit or a period character. But the period itself, between square brackets, loses its special meaning. The same goes for other special characters, such as +.

+ +

To invert a set of characters—that is, to express that you want to match any character except the ones in the set—you can write a caret (^) character after the opening bracket.

+ +
let notBinary = /[^01]/;
+console.log(notBinary.test("1100100010100110"));
+// → false
+console.log(notBinary.test("1100100010200110"));
+// → true
+ +

Repeating parts of a pattern

+ +

We now know how to match a single digit. What if we want to match a whole number—a sequence of one or more digits?

+ +

When you put a plus sign (+) after something in a regular expression, it indicates that the element may be repeated more than once. Thus, /\d+/ matches one or more digit characters.

+ +
console.log(/'\d+'/.test("'123'"));
+// → true
+console.log(/'\d+'/.test("''"));
+// → false
+console.log(/'\d*'/.test("'123'"));
+// → true
+console.log(/'\d*'/.test("''"));
+// → true
+ +

The star (*) has a similar meaning but also allows the pattern to match zero times. Something with a star after it never prevents a pattern from matching—it’ll just match zero instances if it can’t find any suitable text to match.

+ +

A question mark makes a part of a pattern optional, meaning it may occur zero times or one time. In the following example, the u character is allowed to occur, but the pattern also matches when it is missing.

+ +
let neighbor = /neighbou?r/;
+console.log(neighbor.test("neighbour"));
+// → true
+console.log(neighbor.test("neighbor"));
+// → true
+ +

To indicate that a pattern should occur a precise number of times, use braces. Putting {4} after an element, for example, requires it to occur exactly four times. It is also possible to specify a range this way: {2,4} means the element must occur at least twice and at most four times.

+ +

Here is another version of the date and time pattern that allows both single- and double-digit days, months, and hours. It is also slightly easier to decipher.

+ +
let dateTime = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/;
+console.log(dateTime.test("1-30-2003 8:45"));
+// → true
+ +

You can also specify open-ended ranges when using braces by omitting the number after the comma. So, {5,} means five or more times.

+ +

Grouping subexpressions

+ +

To use an operator like * or + on more than one element at a time, you have to use parentheses. A part of a regular expression that is enclosed in parentheses counts as a single element as far as the operators following it are concerned.

+ +
let cartoonCrying = /boo+(hoo+)+/i;
+console.log(cartoonCrying.test("Boohoooohoohooo"));
+// → true
+ +

The first and second + characters apply only to the second o in boo and hoo, respectively. The third + applies to the whole group (hoo+), matching one or more sequences like that.

+ +

The i at the end of the expression in the example makes this regular expression case insensitive, allowing it to match the uppercase B in the input string, even though the pattern is itself all lowercase.

+ +

Matches and groups

+ +

The test method is the absolute simplest way to match a regular expression. It tells you only whether it matched and nothing else. Regular expressions also have an exec (execute) method that will return null if no match was found and return an object with information about the match otherwise.

+ +
let match = /\d+/.exec("one two 100");
+console.log(match);
+// → ["100"]
+console.log(match.index);
+// → 8
+ +

An object returned from exec has an index property that tells us where in the string the successful match begins. Other than that, the object looks like (and in fact is) an array of strings, whose first element is the string that was matched. In the previous example, this is the sequence of digits that we were looking for.

+ +

String values have a match method that behaves similarly.

+ +
console.log("one two 100".match(/\d+/));
+// → ["100"]
+ +

When the regular expression contains subexpressions grouped with parentheses, the text that matched those groups will also show up in the array. The whole match is always the first element. The next element is the part matched by the first group (the one whose opening parenthesis comes first in the expression), then the second group, and so on.

+ +
let quotedText = /'([^']*)'/;
+console.log(quotedText.exec("she said 'hello'"));
+// → ["'hello'", "hello"]
+ +

When a group does not end up being matched at all (for example, when followed by a question mark), its position in the output array will hold undefined. Similarly, when a group is matched multiple times, only the last match ends up in the array.

+ +
console.log(/bad(ly)?/.exec("bad"));
+// → ["bad", undefined]
+console.log(/(\d)+/.exec("123"));
+// → ["123", "3"]
+ +

Groups can be useful for extracting parts of a string. If we don’t just want to verify whether a string contains a date but also extract it and construct an object that represents it, we can wrap parentheses around the digit patterns and directly pick the date out of the result of exec.

+ +

But first we’ll take a brief detour, in which we discuss the built-in way to represent date and time values in JavaScript.

+ +

The Date class

+ +

JavaScript has a standard class for representing dates—or, rather, points in time. It is called Date. If you simply create a date object using new, you get the current date and time.

+ +
console.log(new Date());
+// → Mon Nov 13 2017 16:19:11 GMT+0100 (CET)
+ +

You can also create an object for a specific time.

+ +
console.log(new Date(2009, 11, 9));
+// → Wed Dec 09 2009 00:00:00 GMT+0100 (CET)
+console.log(new Date(2009, 11, 9, 12, 59, 59, 999));
+// → Wed Dec 09 2009 12:59:59 GMT+0100 (CET)
+ +

JavaScript uses a convention where month numbers start at zero (so December is 11), yet day numbers start at one. This is confusing and silly. Be careful.

+ +

The last four arguments (hours, minutes, seconds, and milliseconds) are optional and taken to be zero when not given.

+ +

Timestamps are stored as the number of milliseconds since the start of 1970, in the UTC time zone. This follows a convention set by “Unix time”, which was invented around that time. You can use negative numbers for times before 1970. The getTime method on a date object returns this number. It is big, as you can imagine.

+ +
console.log(new Date(2013, 11, 19).getTime());
+// → 1387407600000
+console.log(new Date(1387407600000));
+// → Thu Dec 19 2013 00:00:00 GMT+0100 (CET)
+ +

If you give the Date constructor a single argument, that argument is treated as such a millisecond count. You can get the current millisecond count by creating a new Date object and calling getTime on it or by calling the Date.now function.

+ +

Date objects provide methods such as getFullYear, getMonth, getDate, getHours, getMinutes, and getSeconds to extract their components. Besides getFullYear there’s also getYear, which gives you the year minus 1900 (98 or 119) and is mostly useless.

+ +

Putting parentheses around the parts of the expression that we are interested in, we can now create a date object from a string.

+ +
function getDate(string) {
+  let [_, month, day, year] =
+    /(\d{1,2})-(\d{1,2})-(\d{4})/.exec(string);
+  return new Date(year, month - 1, day);
+}
+console.log(getDate("1-30-2003"));
+// → Thu Jan 30 2003 00:00:00 GMT+0100 (CET)
+ +

The _ (underscore) binding is ignored and used only to skip the full match element in the array returned by exec.

+ +

Word and string boundaries

+ +

Unfortunately, getDate will also happily extract the nonsensical date 00-1-3000 from the string "100-1-30000". A match may happen anywhere in the string, so in this case, it’ll just start at the second character and end at the second-to-last character.

+ +

If we want to enforce that the match must span the whole string, we can add the markers ^ and $. The caret matches the start of the input string, whereas the dollar sign matches the end. So, /^\d+$/ matches a string consisting entirely of one or more digits, /^!/ matches any string that starts with an exclamation mark, and /x^/ does not match any string (there cannot be an x before the start of the string).

+ +

If, on the other hand, we just want to make sure the date starts and ends on a word boundary, we can use the marker \b. A word boundary can be the start or end of the string or any point in the string that has a word character (as in \w) on one side and a nonword character on the other.

+ +
console.log(/cat/.test("concatenate"));
+// → true
+console.log(/\bcat\b/.test("concatenate"));
+// → false
+ +

Note that a boundary marker doesn’t match an actual character. It just enforces that the regular expression matches only when a certain condition holds at the place where it appears in the pattern.

+ +

Choice patterns

+ +

Say we want to know whether a piece of text contains not only a number but a number followed by one of the words pig, cow, or chicken, or any of their plural forms.

+ +

We could write three regular expressions and test them in turn, but there is a nicer way. The pipe character (|) denotes a choice between the pattern to its left and the pattern to its right. So I can say this:

+ +
let animalCount = /\b\d+ (pig|cow|chicken)s?\b/;
+console.log(animalCount.test("15 pigs"));
+// → true
+console.log(animalCount.test("15 pigchickens"));
+// → false
+ +

Parentheses can be used to limit the part of the pattern that the pipe operator applies to, and you can put multiple such operators next to each other to express a choice between more than two alternatives.

+ +

The mechanics of matching

+ +

Conceptually, when you use exec or test, the regular expression engine looks for a match in your string by trying to match the expression first from the start of the string, then from the second character, and so on, until it finds a match or reaches the end of the string. It’ll either return the first match that can be found or fail to find any match at all.

+ +

To do the actual matching, the engine treats a regular expression something like a flow diagram. This is the diagram for the livestock expression in the previous example:

Visualization of /\b\d+ (pig|cow|chicken)s?\b/
+ +

Our expression matches if we can find a path from the left side of the diagram to the right side. We keep a current position in the string, and every time we move through a box, we verify that the part of the string after our current position matches that box.

+ +

So if we try to match "the 3 pigs" from position 4, our progress through the flow chart would look like this:

+ +
    + +
  • + +

    At position 4, there is a word boundary, so we can move past the first box.

  • + +
  • + +

    Still at position 4, we find a digit, so we can also move past the second box.

  • + +
  • + +

    At position 5, one path loops back to before the second (digit) box, while the other moves forward through the box that holds a single space character. There is a space here, not a digit, so we must take the second path.

  • + +
  • + +

    We are now at position 6 (the start of pigs) and at the three-way branch in the diagram. We don’t see cow or chicken here, but we do see pig, so we take that branch.

  • + +
  • + +

    At position 9, after the three-way branch, one path skips the s box and goes straight to the final word boundary, while the other path matches an s. There is an s character here, not a word boundary, so we go through the s box.

  • + +
  • + +

    We’re at position 10 (the end of the string) and can match only a word boundary. The end of a string counts as a word boundary, so we go through the last box and have successfully matched this string.

+ +

Backtracking

+ +

The regular expression /\b([01]+b|[\da-f]+h|\d+)\b/ matches either a binary number followed by a b, a hexadecimal number (that is, base 16, with the letters a to f standing for the digits 10 to 15) followed by an h, or a regular decimal number with no suffix character. This is the corresponding diagram:

Visualization of /\b([01]+b|\d+|[\da-f]+h)\b/
+ +

When matching this expression, it will often happen that the top (binary) branch is entered even though the input does not actually contain a binary number. When matching the string "103", for example, it becomes clear only at the 3 that we are in the wrong branch. The string does match the expression, just not the branch we are currently in.

+ +

So the matcher backtracks. When entering a branch, it remembers its current position (in this case, at the start of the string, just past the first boundary box in the diagram) so that it can go back and try another branch if the current one does not work out. For the string "103", after encountering the 3 character, it will start trying the branch for hexadecimal numbers, which fails again because there is no h after the number. So it tries the decimal number branch. This one fits, and a match is reported after all.

+ +

The matcher stops as soon as it finds a full match. This means that if multiple branches could potentially match a string, only the first one (ordered by where the branches appear in the regular expression) is used.

+ +

Backtracking also happens for repetition operators like + and *. If you match /^.*x/ against "abcxe", the .* part will first try to consume the whole string. The engine will then realize that it needs an x to match the pattern. Since there is no x past the end of the string, the star operator tries to match one character less. But the matcher doesn’t find an x after abcx either, so it backtracks again, matching the star operator to just abc. Now it finds an x where it needs it and reports a successful match from positions 0 to 4.

+ +

It is possible to write regular expressions that will do a lot of backtracking. This problem occurs when a pattern can match a piece of input in many different ways. For example, if we get confused while writing a binary-number regular expression, we might accidentally write something like /([01]+)+b/.

Visualization of /([01]+)+b/
+ +

If that tries to match some long series of zeros and ones with no trailing b character, the matcher first goes through the inner loop until it runs out of digits. Then it notices there is no b, so it backtracks one position, goes through the outer loop once, and gives up again, trying to backtrack out of the inner loop once more. It will continue to try every possible route through these two loops. This means the amount of work doubles with each additional character. For even just a few dozen characters, the resulting match will take practically forever.

+ +

The replace method

+ +

String values have a replace method that can be used to replace part of the string with another string.

+ +
console.log("papa".replace("p", "m"));
+// → mapa
+ +

The first argument can also be a regular expression, in which case the first match of the regular expression is replaced. When a g option (for global) is added to the regular expression, all matches in the string will be replaced, not just the first.

+ +
console.log("Borobudur".replace(/[ou]/, "a"));
+// → Barobudur
+console.log("Borobudur".replace(/[ou]/g, "a"));
+// → Barabadar
+ +

It would have been sensible if the choice between replacing one match or all matches was made through an additional argument to replace or by providing a different method, replaceAll. But for some unfortunate reason, the choice relies on a property of the regular expression instead.

+ +

The real power of using regular expressions with replace comes from the fact that we can refer to matched groups in the replacement string. For example, say we have a big string containing the names of people, one name per line, in the format Lastname, Firstname. If we want to swap these names and remove the comma to get a Firstname Lastname format, we can use the following code:

+ +
console.log(
+  "Liskov, Barbara\nMcCarthy, John\nWadler, Philip"
+    .replace(/(\w+), (\w+)/g, "$2 $1"));
+// → Barbara Liskov
+//   John McCarthy
+//   Philip Wadler
+ +

The $1 and $2 in the replacement string refer to the parenthesized groups in the pattern. $1 is replaced by the text that matched against the first group, $2 by the second, and so on, up to $9. The whole match can be referred to with $&.

+ +

It is possible to pass a function—rather than a string—as the second argument to replace. For each replacement, the function will be called with the matched groups (as well as the whole match) as arguments, and its return value will be inserted into the new string.

+ +

Here’s a small example:

+ +
let s = "the cia and fbi";
+console.log(s.replace(/\b(fbi|cia)\b/g,
+            str => str.toUpperCase()));
+// → the CIA and FBI
+ +

Here’s a more interesting one:

+ +
let stock = "1 lemon, 2 cabbages, and 101 eggs";
+function minusOne(match, amount, unit) {
+  amount = Number(amount) - 1;
+  if (amount == 1) { // only one left, remove the 's'
+    unit = unit.slice(0, unit.length - 1);
+  } else if (amount == 0) {
+    amount = "no";
+  }
+  return amount + " " + unit;
+}
+console.log(stock.replace(/(\d+) (\w+)/g, minusOne));
+// → no lemon, 1 cabbage, and 100 eggs
+ +

This takes a string, finds all occurrences of a number followed by an alphanumeric word, and returns a string wherein every such occurrence is decremented by one.

+ +

The (\d+) group ends up as the amount argument to the function, and the (\w+) group gets bound to unit. The function converts amount to a number—which always works since it matched \d+—and makes some adjustments in case there is only one or zero left.

+ +

Greed

+ +

It is possible to use replace to write a function that removes all comments from a piece of JavaScript code. Here is a first attempt:

+ +
function stripComments(code) {
+  return code.replace(/\/\/.*|\/\*[^]*\*\//g, "");
+}
+console.log(stripComments("1 + /* 2 */3"));
+// → 1 + 3
+console.log(stripComments("x = 10;// ten!"));
+// → x = 10;
+console.log(stripComments("1 /* a */+/* b */ 1"));
+// → 1  1
+ +

The part before the or operator matches two slash characters followed by any number of non-newline characters. The part for multiline comments is more involved. We use [^] (any character that is not in the empty set of characters) as a way to match any character. We cannot just use a period here because block comments can continue on a new line, and the period character does not match newline characters.

+ +

But the output for the last line appears to have gone wrong. Why?

+ +

The [^]* part of the expression, as I described in the section on backtracking, will first match as much as it can. If that causes the next part of the pattern to fail, the matcher moves back one character and tries again from there. In the example, the matcher first tries to match the whole rest of the string and then moves back from there. It will find an occurrence of */ after going back four characters and match that. This is not what we wanted—the intention was to match a single comment, not to go all the way to the end of the code and find the end of the last block comment.

+ +

Because of this behavior, we say the repetition operators (+, *, ?, and {}) are greedy, meaning they match as much as they can and backtrack from there. If you put a question mark after them (+?, *?, ??, {}?), they become nongreedy and start by matching as little as possible, matching more only when the remaining pattern does not fit the smaller match.

+ +

And that is exactly what we want in this case. By having the star match the smallest stretch of characters that brings us to a */, we consume one block comment and nothing more.

+ +
function stripComments(code) {
+  return code.replace(/\/\/.*|\/\*[^]*?\*\//g, "");
+}
+console.log(stripComments("1 /* a */+/* b */ 1"));
+// → 1 + 1
+ +

A lot of bugs in regular expression programs can be traced to unintentionally using a greedy operator where a nongreedy one would work better. When using a repetition operator, consider the nongreedy variant first.

+ +

Dynamically creating RegExp objects

+ +

There are cases where you might not know the exact pattern you need to match against when you are writing your code. Say you want to look for the user’s name in a piece of text and enclose it in underscore characters to make it stand out. Since you will know the name only once the program is actually running, you can’t use the slash-based notation.

+ +

But you can build up a string and use the RegExp constructor on that. Here’s an example:

+ +
let name = "harry";
+let text = "Harry is a suspicious character.";
+let regexp = new RegExp("\\b(" + name + ")\\b", "gi");
+console.log(text.replace(regexp, "_$1_"));
+// → _Harry_ is a suspicious character.
+ +

When creating the \b boundary markers, we have to use two backslashes because we are writing them in a normal string, not a slash-enclosed regular expression. The second argument to the RegExp constructor contains the options for the regular expression—in this case, "gi" for global and case insensitive.

+ +

But what if the name is "dea+hl[]rd" because our user is a nerdy teenager? That would result in a nonsensical regular expression that won’t actually match the user’s name.

+ +

To work around this, we can add backslashes before any character that has a special meaning.

+ +
let name = "dea+hl[]rd";
+let text = "This dea+hl[]rd guy is super annoying.";
+let escaped = name.replace(/[\\[.+*?(){|^$]/g, "\\$&");
+let regexp = new RegExp("\\b" + escaped + "\\b", "gi");
+console.log(text.replace(regexp, "_$&_"));
+// → This _dea+hl[]rd_ guy is super annoying.
+ +

The search method

+ +

The indexOf method on strings cannot be called with a regular expression. But there is another method, search, that does expect a regular expression. Like indexOf, it returns the first index on which the expression was found, or -1 when it wasn’t found.

+ +
console.log("  word".search(/\S/));
+// → 2
+console.log("    ".search(/\S/));
+// → -1
+ +

Unfortunately, there is no way to indicate that the match should start at a given offset (like we can with the second argument to indexOf), which would often be useful.

+ +

The lastIndex property

+ +

The exec method similarly does not provide a convenient way to start searching from a given position in the string. But it does provide an inconvenient way.

+ +

Regular expression objects have properties. One such property is source, which contains the string that expression was created from. Another property is lastIndex, which controls, in some limited circumstances, where the next match will start.

+ +

Those circumstances are that the regular expression must have the global (g) or sticky (y) option enabled, and the match must happen through the exec method. Again, a less confusing solution would have been to just allow an extra argument to be passed to exec, but confusion is an essential feature of JavaScript’s regular expression interface.

+ +
let pattern = /y/g;
+pattern.lastIndex = 3;
+let match = pattern.exec("xyzzy");
+console.log(match.index);
+// → 4
+console.log(pattern.lastIndex);
+// → 5
+ +

If the match was successful, the call to exec automatically updates the lastIndex property to point after the match. If no match was found, lastIndex is set back to zero, which is also the value it has in a newly constructed regular expression object.

+ +

The difference between the global and the sticky options is that, when sticky is enabled, the match will succeed only if it starts directly at lastIndex, whereas with global, it will search ahead for a position where a match can start.

+ +
let global = /abc/g;
+console.log(global.exec("xyz abc"));
+// → ["abc"]
+let sticky = /abc/y;
+console.log(sticky.exec("xyz abc"));
+// → null
+ +

When using a shared regular expression value for multiple exec calls, these automatic updates to the lastIndex property can cause problems. Your regular expression might be accidentally starting at an index that was left over from a previous call.

+ +
let digit = /\d/g;
+console.log(digit.exec("here it is: 1"));
+// → ["1"]
+console.log(digit.exec("and now: 1"));
+// → null
+ +

Another interesting effect of the global option is that it changes the way the match method on strings works. When called with a global expression, instead of returning an array similar to that returned by exec, match will find all matches of the pattern in the string and return an array containing the matched strings.

+ +
console.log("Banana".match(/an/g));
+// → ["an", "an"]
+ +

So be cautious with global regular expressions. The cases where they are necessary—calls to replace and places where you want to explicitly use lastIndex—are typically the only places where you want to use them.

+ +

Looping over matches

+ +

A common thing to do is to scan through all occurrences of a pattern in a string, in a way that gives us access to the match object in the loop body. We can do this by using lastIndex and exec.

+ +
let input = "A string with 3 numbers in it... 42 and 88.";
+let number = /\b\d+\b/g;
+let match;
+while (match = number.exec(input)) {
+  console.log("Found", match[0], "at", match.index);
+}
+// → Found 3 at 14
+//   Found 42 at 33
+//   Found 88 at 40
+ +

This makes use of the fact that the value of an assignment expression (=) is the assigned value. So by using match = number.exec(input) as the condition in the while statement, we perform the match at the start of each iteration, save its result in a binding, and stop looping when no more matches are found.

+ +

Parsing an INI file

+ +

To conclude the chapter, we’ll look at a problem that calls for regular expressions. Imagine we are writing a program to automatically collect information about our enemies from the Internet. (We will not actually write that program here, just the part that reads the configuration file. Sorry.) The configuration file looks like this:

+ +
searchengine=https://duckduckgo.com/?q=$1
+spitefulness=9.7
+
+; comments are preceded by a semicolon...
+; each section concerns an individual enemy
+[larry]
+fullname=Larry Doe
+type=kindergarten bully
+website=http://www.geocities.com/CapeCanaveral/11451
+
+[davaeorn]
+fullname=Davaeorn
+type=evil wizard
+outputdir=/home/marijn/enemies/davaeorn
+ +

The exact rules for this format (which is a widely used format, usually called an INI file) are as follows:

+ +
    + +
  • + +

    Blank lines and lines starting with semicolons are ignored.

  • + +
  • + +

    Lines wrapped in [ and ] start a new section.

  • + +
  • + +

    Lines containing an alphanumeric identifier followed by an = character add a setting to the current section.

  • + +
  • + +

    Anything else is invalid.

+ +

Our task is to convert a string like this into an object whose properties hold strings for settings written before the first section header and subobjects for sections, with those subobjects holding the section’s settings.

+ +

Since the format has to be processed line by line, splitting up the file into separate lines is a good start. We saw the split method in Chapter 4. Some operating systems, however, use not just a newline character to separate lines but a carriage return character followed by a newline ("\r\n"). Given that the split method also allows a regular expression as its argument, we can use a regular expression like /\r?\n/ to split in a way that allows both "\n" and "\r\n" between lines.

+ +
function parseINI(string) {
+  // Start with an object to hold the top-level fields
+  let result = {};
+  let section = result;
+  string.split(/\r?\n/).forEach(line => {
+    let match;
+    if (match = line.match(/^(\w+)=(.*)$/)) {
+      section[match[1]] = match[2];
+    } else if (match = line.match(/^\[(.*)\]$/)) {
+      section = result[match[1]] = {};
+    } else if (!/^\s*(;.*)?$/.test(line)) {
+      throw new Error("Line '" + line + "' is not valid.");
+    }
+  });
+  return result;
+}
+
+console.log(parseINI(`
+name=Vasilis
+[address]
+city=Tessaloniki`));
+// → {name: "Vasilis", address: {city: "Tessaloniki"}}
+ +

The code goes over the file’s lines and builds up an object. Properties at the top are stored directly into that object, whereas properties found in sections are stored in a separate section object. The section binding points at the object for the current section.

+ +

There are two kinds of significant lines—section headers or property lines. When a line is a regular property, it is stored in the current section. When it is a section header, a new section object is created, and section is set to point at it.

+ +

Note the recurring use of ^ and $ to make sure the expression matches the whole line, not just part of it. Leaving these out results in code that mostly works but behaves strangely for some input, which can be a difficult bug to track down.

+ +

The pattern if (match = string.match(...)) is similar to the trick of using an assignment as the condition for while. You often aren’t sure that your call to match will succeed, so you can access the resulting object only inside an if statement that tests for this. To not break the pleasant chain of else if forms, we assign the result of the match to a binding and immediately use that assignment as the test for the if statement.

+ +

If a line is not a section header or a property, the function checks whether it is a comment or an empty line using the expression /^\s*(;.*)?$/. Do you see how it works? The part between the parentheses will match comments, and the ? makes sure it also matches lines containing only whitespace. When a line doesn’t match any of the expected forms, the function throws an exception.

+ +

International characters

+ +

Because of JavaScript’s initial simplistic implementation and the fact that this simplistic approach was later set in stone as standard behavior, JavaScript’s regular expressions are rather dumb about characters that do not appear in the English language. For example, as far as JavaScript’s regular expressions are concerned, a “word +character” is only one of the 26 characters in the Latin alphabet (uppercase or lowercase), decimal digits, and, for some reason, the underscore character. Things like é or β, which most definitely are word characters, will not match \w (and will match uppercase \W, the nonword category).

+ +

By a strange historical accident, \s (whitespace) does not have this problem and matches all characters that the Unicode standard considers whitespace, including things like the nonbreaking space and the Mongolian vowel separator.

+ +

Another problem is that, by default, regular expressions work on code units, as discussed in Chapter 5, not actual characters. This means characters that are composed of two code units behave strangely.

+ +
console.log(/🍎{3}/.test("🍎🍎🍎"));
+// → false
+console.log(/<.>/.test("<🌹>"));
+// → false
+console.log(/<.>/u.test("<🌹>"));
+// → true
+ +

The problem is that the 🍎 in the first line is treated as two code units, and the {3} part is applied only to the second one. Similarly, the dot matches a single code unit, not the two that make up the rose emoji.

+ +

You must add a u option (for Unicode) to your regular expression to make it treat such characters properly. The wrong behavior remains the default, unfortunately, because changing that might cause problems for existing code that depends on it.

+ +

Though this was only just standardized and is, at the time of writing, not widely supported yet, it is possible to use \p in a regular expression (that must have the Unicode option enabled) to match all characters to which the Unicode standard assigns a given property.

+ +
console.log(/\p{Script=Greek}/u.test("α"));
+// → true
+console.log(/\p{Script=Arabic}/u.test("α"));
+// → false
+console.log(/\p{Alphabetic}/u.test("α"));
+// → true
+console.log(/\p{Alphabetic}/u.test("!"));
+// → false
+ +

Unicode defines a number of useful properties, though finding the one that you need may not always be trivial. You can use the \p{Property=Value} notation to match any character that has the given value for that property. If the property name is left off, as in \p{Name}, the name is assumed to be either a binary property such as Alphabetic or a category such as Number.

+ +

Summary

+ +

Regular expressions are objects that represent patterns in strings. They use their own language to express these patterns.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
/abc/A sequence of characters
/[abc]/Any character from a set of characters
/[^abc]/Any character not in a set of characters
/[0-9]/Any character in a range of characters
/x+/One or more occurrences of the pattern x
/x+?/One or more occurrences, nongreedy
/x*/Zero or more occurrences
/x?/Zero or one occurrence
/x{2,4}/Two to four occurrences
/(abc)/A group
/a|b|c/Any one of several patterns
/\d/Any digit character
/\w/An alphanumeric character (“word character”)
/\s/Any whitespace character
/./Any character except newlines
/\b/A word boundary
/^/Start of input
/$/End of input
+ +

A regular expression has a method test to test whether a given string matches it. It also has a method exec that, when a match is found, returns an array containing all matched groups. Such an array has an index property that indicates where the match started.

+ +

Strings have a match method to match them against a regular expression and a search method to search for one, returning only the starting position of the match. Their replace method can replace matches of a pattern with a replacement string or function.

+ +

Regular expressions can have options, which are written after the closing slash. The i option makes the match case insensitive. The g option makes the expression global, which, among other things, causes the replace method to replace all instances instead of just the first. The y option makes it sticky, which means that it will not search ahead and skip part of the string when looking for a match. The u option turns on Unicode mode, which fixes a number of problems around the handling of characters that take up two code units.

+ +

Regular expressions are a sharp tool with an awkward handle. They simplify some tasks tremendously but can quickly become unmanageable when applied to complex problems. Part of knowing how to use them is resisting the urge to try to shoehorn things that they cannot cleanly express into them.

+ +

Exercises

+ +

It is almost unavoidable that, in the course of working on these exercises, you will get confused and frustrated by some regular expression’s inexplicable behavior. Sometimes it helps to enter your expression into an online tool like https://debuggex.com to see whether its visualization corresponds to what you intended and to experiment with the way it responds to various input strings.

+ +

Regexp golf

+ +

Code golf is a term used for the game of trying to express a particular program in as few characters as possible. Similarly, regexp golf is the practice of writing as tiny a regular expression as possible to match a given pattern, and only that pattern.

+ +

For each of the following items, write a regular expression to test whether any of the given substrings occur in a string. The regular expression should match only strings containing one of the substrings described. Do not worry about word boundaries unless explicitly mentioned. When your expression works, see whether you can make it any smaller.

+ +
    + +
  1. + +

    car and cat

  2. + +
  3. + +

    pop and prop

  4. + +
  5. + +

    ferret, ferry, and ferrari

  6. + +
  7. + +

    Any word ending in ious

  8. + +
  9. + +

    A whitespace character followed by a period, comma, colon, or semicolon

  10. + +
  11. + +

    A word longer than six letters

  12. + +
  13. + +

    A word without the letter e (or E)

  14. + +
+ +

Refer to the table in the chapter summary for help. Test each solution with a few test strings.

+ +
// Fill in the regular expressions
+
+verify(/.../,
+       ["my car", "bad cats"],
+       ["camper", "high art"]);
+
+verify(/.../,
+       ["pop culture", "mad props"],
+       ["plop", "prrrop"]);
+
+verify(/.../,
+       ["ferret", "ferry", "ferrari"],
+       ["ferrum", "transfer A"]);
+
+verify(/.../,
+       ["how delicious", "spacious room"],
+       ["ruinous", "consciousness"]);
+
+verify(/.../,
+       ["bad punctuation ."],
+       ["escape the period"]);
+
+verify(/.../,
+       ["hottentottententen"],
+       ["no", "hotten totten tenten"]);
+
+verify(/.../,
+       ["red platypus", "wobbling nest"],
+       ["earth bed", "learning ape", "BEET"]);
+
+
+function verify(regexp, yes, no) {
+  // Ignore unfinished exercises
+  if (regexp.source == "...") return;
+  for (let str of yes) if (!regexp.test(str)) {
+    console.log(`Failure to match '${str}'`);
+  }
+  for (let str of no) if (regexp.test(str)) {
+    console.log(`Unexpected match for '${str}'`);
+  }
+}
+ +

Quoting style

+ +

Imagine you have written a story and used single quotation marks throughout to mark pieces of dialogue. Now you want to replace all the dialogue quotes with double quotes, while keeping the single quotes used in contractions like aren’t.

+ +

Think of a pattern that distinguishes these two kinds of quote usage and craft a call to the replace method that does the proper replacement.

+ +
let text = "'I'm the cook,' he said, 'it's my job.'";
+// Change this call.
+console.log(text.replace(/A/g, "B"));
+// → "I'm the cook," he said, "it's my job."
+ +
+ +

The most obvious solution is to replace only quotes with a nonword character on at least one side—something like /\W'|'\W/. But you also have to take the start and end of the line into account.

+ +

In addition, you must ensure that the replacement also includes the characters that were matched by the \W pattern so that those are not dropped. This can be done by wrapping them in parentheses and including their groups in the replacement string ($1, $2). Groups that are not matched will be replaced by nothing.

+ +
+ +

Numbers again

+ +

Write an expression that matches only JavaScript-style numbers. It must support an optional minus or plus sign in front of the number, the decimal dot, and exponent notation—5e-3 or 1E10—again with an optional sign in front of the exponent. Also note that it is not necessary for there to be digits in front of or after the dot, but the number cannot be a dot alone. That is, .5 and 5. are valid JavaScript numbers, but a lone dot isn’t.

+ +
// Fill in this regular expression.
+let number = /^...$/;
+
+// Tests:
+for (let str of ["1", "-1", "+15", "1.55", ".5", "5.",
+                 "1.3e2", "1E-4", "1e+12"]) {
+  if (!number.test(str)) {
+    console.log(`Failed to match '${str}'`);
+  }
+}
+for (let str of ["1a", "+-1", "1.2.3", "1+1", "1e4.5",
+                 ".5.", "1f5", "."]) {
+  if (number.test(str)) {
+    console.log(`Incorrectly accepted '${str}'`);
+  }
+}
+ +
+ +

First, do not forget the backslash in front of the period.

+ +

Matching the optional sign in front of the number, as well as in front of the exponent, can be done with [+\-]? or (\+|-|) (plus, minus, or nothing).

+ +

The more complicated part of the exercise is the problem of matching both "5." and ".5" without also matching ".". For this, a good solution is to use the | operator to separate the two cases—either one or more digits optionally followed by a dot and zero or more digits or a dot followed by one or more digits.

+ +

Finally, to make the e case insensitive, either add an i option to the regular expression or use [eE].

+ +
+
diff --git a/docs/10_modules.html b/docs/10_modules.html new file mode 100644 index 000000000..f9fbf9ed6 --- /dev/null +++ b/docs/10_modules.html @@ -0,0 +1,375 @@ + + + + + Modules :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 10Modules

+ +
+ +

Write code that is easy to delete, not easy to extend.

+ +
Tef, Programming is Terrible
+ +
Picture of a building built from modular pieces
+ +

The ideal program has a crystal-clear structure. The way it works is easy to explain, and each part plays a well-defined role.

+ +

A typical real program grows organically. New pieces of functionality are added as new needs come up. Structuring—and preserving structure—is additional work. It’s work that will pay off only in the future, the next time someone works on the program. So it is tempting to neglect it and allow the parts of the program to become deeply entangled.

+ +

This causes two practical issues. First, understanding such a system is hard. If everything can touch everything else, it is difficult to look at any given piece in isolation. You are forced to build up a holistic understanding of the entire thing. Second, if you want to use any of the functionality from such a program in another situation, rewriting it may be easier than trying to disentangle it from its context.

+ +

The phrase “big ball of mud” is often used for such large, structureless programs. Everything sticks together, and when you try to pick out a piece, the whole thing comes apart, and your hands get dirty.

+ +

Modules

+ +

Modules are an attempt to avoid these problems. A module is a piece of program that specifies which other pieces it relies on and which functionality it provides for other modules to use (its interface).

+ +

Module interfaces have a lot in common with object interfaces, as we saw them in Chapter 6. They make part of the module available to the outside world and keep the rest private. By restricting the ways in which modules interact with each other, the system becomes more like LEGO, where pieces interact through well-defined connectors, and less like mud, where everything mixes with everything.

+ +

The relations between modules are called dependencies. When a module needs a piece from another module, it is said to depend on that module. When this fact is clearly specified in the module itself, it can be used to figure out which other modules need to be present to be able to use a given module and to automatically load dependencies.

+ +

To separate modules in that way, each needs its own private scope.

+ +

Just putting your JavaScript code into different files does not satisfy these requirements. The files still share the same global namespace. They can, intentionally or accidentally, interfere with each other’s bindings. And the dependency structure remains unclear. We can do better, as we’ll see later in the chapter.

+ +

Designing a fitting module structure for a program can be difficult. In the phase where you are still exploring the problem, trying different things to see what works, you might want to not worry about it too much since it can be a big distraction. Once you have something that feels solid, that’s a good time to take a step back and organize it.

+ +

Packages

+ +

One of the advantages of building a program out of separate pieces, and being actually able to run those pieces on their own, is that you might be able to apply the same piece in different programs.

+ +

But how do you set this up? Say I want to use the parseINI function from Chapter 9 in another program. If it is clear what the function depends on (in this case, nothing), I can just copy all the necessary code into my new project and use it. But then, if I find a mistake in that code, I’ll probably fix it in whichever program I’m working with at the time and forget to also fix it in the other program.

+ +

Once you start duplicating code, you’ll quickly find yourself wasting time and energy moving copies around and keeping them up-to-date.

+ +

That’s where packages come in. A package is a chunk of code that can be distributed (copied and installed). It may contain one or more modules and has information about which other packages it depends on. A package also usually comes with documentation explaining what it does so that people who didn’t write it might still be able to use it.

+ +

When a problem is found in a package or a new feature is added, the package is updated. Now the programs that depend on it (which may also be packages) can upgrade to the new version.

+ +

Working in this way requires infrastructure. We need a place to store and find packages and a convenient way to install and upgrade them. In the JavaScript world, this infrastructure is provided by NPM (https://npmjs.org).

+ +

NPM is two things: an online service where one can download (and upload) packages and a program (bundled with Node.js) that helps you install and manage them.

+ +

At the time of writing, there are more than half a million different packages available on NPM. A large portion of those are rubbish, I should mention, but almost every useful, publicly available package can be found on there. For example, an INI file parser, similar to the one we built in Chapter 9, is available under the package name ini.

+ +

Chapter 20 will show how to install such packages locally using the npm command line program.

+ +

Having quality packages available for download is extremely valuable. It means that we can often avoid reinventing a program that 100 people have written before and get a solid, well-tested implementation at the press of a few keys.

+ +

Software is cheap to copy, so once someone has written it, distributing it to other people is an efficient process. But writing it in the first place is work, and responding to people who have found problems in the code, or who want to propose new features, is even more work.

+ +

By default, you own the copyright to the code you write, and other people may use it only with your permission. But because some people are just nice and because publishing good software can help make you a little bit famous among programmers, many packages are published under a license that explicitly allows other people to use it.

+ +

Most code on NPM is licensed this way. Some licenses require you to also publish code that you build on top of the package under the same license. Others are less demanding, just requiring that you keep the license with the code as you distribute it. The JavaScript community mostly uses the latter type of license. When using other people’s packages, make sure you are aware of their license.

+ +

Improvised modules

+ +

Until 2015, the JavaScript language had no built-in module system. Yet people had been building large systems in JavaScript for more than a decade, and they needed modules.

+ +

So they designed their own module systems on top of the language. You can use JavaScript functions to create local scopes and objects to represent module interfaces.

+ +

This is a module for going between day names and numbers (as returned by Date’s getDay method). Its interface consists of weekDay.name and weekDay.number, and it hides its local binding names inside the scope of a function expression that is immediately invoked.

+ +
const weekDay = function() {
+  const names = ["Sunday", "Monday", "Tuesday", "Wednesday",
+                 "Thursday", "Friday", "Saturday"];
+  return {
+    name(number) { return names[number]; },
+    number(name) { return names.indexOf(name); }
+  };
+}();
+
+console.log(weekDay.name(weekDay.number("Sunday")));
+// → Sunday
+ +

This style of modules provides isolation, to a certain degree, but it does not declare dependencies. Instead, it just puts its interface into the global scope and expects its dependencies, if any, to do the same. For a long time this was the main approach used in web programming, but it is mostly obsolete now.

+ +

If we want to make dependency relations part of the code, we’ll have to take control of loading dependencies. Doing that requires being able to execute strings as code. JavaScript can do this.

+ +

Evaluating data as code

+ +

There are several ways to take data (a string of code) and run it as part of the current program.

+ +

The most obvious way is the special operator eval, which will execute a string in the current scope. This is usually a bad idea because it breaks some of the properties that scopes normally have, such as it being easily predictable which binding a given name refers to.

+ +
const x = 1;
+function evalAndReturnX(code) {
+  eval(code);
+  return x;
+}
+
+console.log(evalAndReturnX("var x = 2"));
+// → 2
+console.log(x);
+// → 1
+ +

A less scary way of interpreting data as code is to use the Function constructor. It takes two arguments: a string containing a comma-separated list of argument names and a string containing the function body. It wraps the code in a function value so that it gets its own scope and won’t do odd things with other scopes.

+ +
let plusOne = Function("n", "return n + 1;");
+console.log(plusOne(4));
+// → 5
+ +

This is precisely what we need for a module system. We can wrap the module’s code in a function and use that function’s scope as module scope.

+ +

CommonJS

+ +

The most widely used approach to bolted-on JavaScript modules is called CommonJS modules. Node.js uses it and is the system used by most packages on NPM.

+ +

The main concept in CommonJS modules is a function called require. When you call this with the module name of a dependency, it makes sure the module is loaded and returns its interface.

+ +

Because the loader wraps the module code in a function, modules automatically get their own local scope. All they have to do is call require to access their dependencies and put their interface in the object bound to exports.

+ +

This example module provides a date-formatting function. It uses two packages from NPM—ordinal to convert numbers to strings like "1st" and "2nd", and date-names to get the English names for weekdays and months. It exports a single function, formatDate, which takes a Date object and a template string.

+ +

The template string may contain codes that direct the format, such as YYYY for the full year and Do for the ordinal day of the month. You could give it a string like "MMMM Do YYYY" to get output like “November 22nd 2017”.

+ +
const ordinal = require("ordinal");
+const {days, months} = require("date-names");
+
+exports.formatDate = function(date, format) {
+  return format.replace(/YYYY|M(MMM)?|Do?|dddd/g, tag => {
+    if (tag == "YYYY") return date.getFullYear();
+    if (tag == "M") return date.getMonth();
+    if (tag == "MMMM") return months[date.getMonth()];
+    if (tag == "D") return date.getDate();
+    if (tag == "Do") return ordinal(date.getDate());
+    if (tag == "dddd") return days[date.getDay()];
+  });
+};
+ +

The interface of ordinal is a single function, whereas date-names exports an object containing multiple things—days and months are arrays of names. Destructuring is very convenient when creating bindings for imported interfaces.

+ +

The module adds its interface function to exports so that modules that depend on it get access to it. We could use the module like this:

+ +
const {formatDate} = require("./format-date");
+
+console.log(formatDate(new Date(2017, 9, 13),
+                       "dddd the Do"));
+// → Friday the 13th
+ +

We can define require, in its most minimal form, like this:

+ +
require.cache = Object.create(null);
+
+function require(name) {
+  if (!(name in require.cache)) {
+    let code = readFile(name);
+    let module = {exports: {}};
+    require.cache[name] = module;
+    let wrapper = Function("require, exports, module", code);
+    wrapper(require, module.exports, module);
+  }
+  return require.cache[name].exports;
+}
+ +

In this code, readFile is a made-up function that reads a file and returns its contents as a string. Standard JavaScript provides no such functionality—but different JavaScript environments, such as the browser and Node.js, provide their own ways of accessing files. The example just pretends that readFile exists.

+ +

To avoid loading the same module multiple times, require keeps a store (cache) of already loaded modules. When called, it first checks if the requested module has been loaded and, if not, loads it. This involves reading the module’s code, wrapping it in a function, and calling it.

+ +

The interface of the ordinal package we saw before is not an object but a function. A quirk of the CommonJS modules is that, though the module system will create an empty interface object for you (bound to exports), you can replace that with any value by overwriting module.exports. This is done by many modules to export a single value instead of an interface object.

+ +

By defining require, exports, and module as parameters for the generated wrapper function (and passing the appropriate values when calling it), the loader makes sure that these bindings are available in the module’s scope.

+ +

The way the string given to require is translated to an actual filename or web address differs in different systems. When it starts with "./" or "../", it is generally interpreted as relative to the current module’s filename. So "./format-date" would be the file named format-date.js in the same directory.

+ +

When the name isn’t relative, Node.js will look for an installed package by that name. In the example code in this chapter, we’ll interpret such names as referring to NPM packages. We’ll go into more detail on how to install and use NPM modules in Chapter 20.

+ +

Now, instead of writing our own INI file parser, we can use one from NPM.

+ +
const {parse} = require("ini");
+
+console.log(parse("x = 10\ny = 20"));
+// → {x: "10", y: "20"}
+ +

ECMAScript modules

+ +

CommonJS modules work quite well and, in combination with NPM, have allowed the JavaScript community to start sharing code on a large scale.

+ +

But they remain a bit of a duct-tape hack. The notation is slightly awkward—the things you add to exports are not available in the local scope, for example. And because require is a normal function call taking any kind of argument, not just a string literal, it can be hard to determine the dependencies of a module without running its code.

+ +

This is why the JavaScript standard from 2015 introduces its own, different module system. It is usually called ES modules, where ES stands for ECMAScript. The main concepts of dependencies and interfaces remain the same, but the details differ. For one thing, the notation is now integrated into the language. Instead of calling a function to access a dependency, you use a special import keyword.

+ +
import ordinal from "ordinal";
+import {days, months} from "date-names";
+
+export function formatDate(date, format) { /* ... */ }
+ +

Similarly, the export keyword is used to export things. It may appear in front of a function, class, or binding definition (let, const, or var).

+ +

An ES module’s interface is not a single value but a set of named bindings. The preceding module binds formatDate to a function. When you import from another module, you import the binding, not the value, which means an exporting module may change the value of the binding at any time, and the modules that import it will see its new value.

+ +

When there is a binding named default, it is treated as the module’s main exported value. If you import a module like ordinal in the example, without braces around the binding name, you get its default binding. Such modules can still export other bindings under different names alongside their default export.

+ +

To create a default export, you write export default before an expression, a function declaration, or a class declaration.

+ +
export default ["Winter", "Spring", "Summer", "Autumn"];
+ +

It is possible to rename imported bindings using the word as.

+ +
import {days as dayNames} from "date-names";
+
+console.log(dayNames.length);
+// → 7
+ +

Another important difference is that ES module imports happen before a module’s script starts running. That means import declarations may not appear inside functions or blocks, and the names of dependencies must be quoted strings, not arbitrary expressions.

+ +

At the time of writing, the JavaScript community is in the process of adopting this module style. But it has been a slow process. It took a few years, after the format was specified, for browsers and Node.js to start supporting it. And though they mostly support it now, this support still has issues, and the discussion on how such modules should be distributed through NPM is still ongoing.

+ +

Many projects are written using ES modules and then automatically converted to some other format when published. We are in a transitional period in which two different module systems are used side by side, and it is useful to be able to read and write code in either of them.

+ +

Building and bundling

+ +

In fact, many JavaScript projects aren’t even, technically, written in JavaScript. There are extensions, such as the type checking dialect mentioned in Chapter 8, that are widely used. People also often start using planned extensions to the language long before they have been added to the platforms that actually run JavaScript.

+ +

To make this possible, they compile their code, translating it from their chosen JavaScript dialect to plain old JavaScript—or even to a past version of JavaScript—so that old browsers can run it.

+ +

Including a modular program that consists of 200 different files in a web page produces its own problems. If fetching a single file over the network takes 50 milliseconds, loading the whole program takes 10 seconds, or maybe half that if you can load several files simultaneously. That’s a lot of wasted time. Because fetching a single big file tends to be faster than fetching a lot of tiny ones, web programmers have started using tools that roll their programs (which they painstakingly split into modules) back into a single big file before they publish it to the Web. Such tools are called bundlers.

+ +

And we can go further. Apart from the number of files, the size of the files also determines how fast they can be transferred over the network. Thus, the JavaScript community has invented minifiers. These are tools that take a JavaScript program and make it smaller by automatically removing comments and whitespace, renaming bindings, and replacing pieces of code with equivalent code that take up less space.

+ +

So it is not uncommon for the code that you find in an NPM package or that runs on a web page to have gone through multiple stages of transformation—converted from modern JavaScript to historic JavaScript, from ES module format to CommonJS, bundled, and minified. We won’t go into the details of these tools in this book since they tend to be boring and change rapidly. Just be aware that the JavaScript code you run is often not the code as it was written.

+ +

Module design

+ +

Structuring programs is one of the subtler aspects of programming. Any nontrivial piece of functionality can be modeled in various ways.

+ +

Good program design is subjective—there are trade-offs involved and matters of taste. The best way to learn the value of well-structured design is to read or work on a lot of programs and notice what works and what doesn’t. Don’t assume that a painful mess is “just the way it is”. You can improve the structure of almost everything by putting more thought into it.

+ +

One aspect of module design is ease of use. If you are designing something that is intended to be used by multiple people—or even by yourself, in three months when you no longer remember the specifics of what you did—it is helpful if your interface is simple and predictable.

+ +

That may mean following existing conventions. A good example is the ini package. This module imitates the standard JSON object by providing parse and stringify (to write an INI file) functions, and, like JSON, converts between strings and plain objects. So the interface is small and familiar, and after you’ve worked with it once, you’re likely to remember how to use it.

+ +

Even if there’s no standard function or widely used package to imitate, you can keep your modules predictable by using simple data +structures and doing a single, focused thing. Many of the INI-file parsing modules on NPM provide a function that directly reads such a file from the hard disk and parses it, for example. This makes it impossible to use such modules in the browser, where we don’t have direct file system access, and adds complexity that would have been better addressed by composing the module with some file-reading function.

+ +

This points to another helpful aspect of module design—the ease with which something can be composed with other code. Focused modules that compute values are applicable in a wider range of programs than bigger modules that perform complicated actions with side effects. An INI file reader that insists on reading the file from disk is useless in a scenario where the file’s content comes from some other source.

+ +

Relatedly, stateful objects are sometimes useful or even necessary, but if something can be done with a function, use a function. Several of the INI file readers on NPM provide an interface style that requires you to first create an object, then load the file into your object, and finally use specialized methods to get at the results. This type of thing is common in the object-oriented tradition, and it’s terrible. Instead of making a single function call and moving on, you have to perform the ritual of moving your object through various states. And because the data is now wrapped in a specialized object type, all code that interacts with it has to know about that type, creating unnecessary interdependencies.

+ +

Often defining new data structures can’t be avoided—only a few basic ones are provided by the language standard, and many types of data have to be more complex than an array or a map. But when an array suffices, use an array.

+ +

An example of a slightly more complex data structure is the graph from Chapter 7. There is no single obvious way to represent a graph in JavaScript. In that chapter, we used an object whose properties hold arrays of strings—the other nodes reachable from that node.

+ +

There are several different pathfinding packages on NPM, but none of them uses this graph format. They usually allow the graph’s edges to have a weight, which is the cost or distance associated with it. That isn’t possible in our representation.

+ +

For example, there’s the dijkstrajs package. A well-known approach to pathfinding, quite similar to our findRoute function, is called Dijkstra’s algorithm, after Edsger Dijkstra, who first wrote it down. The js suffix is often added to package names to indicate the fact that they are written in JavaScript. This dijkstrajs package uses a graph format similar to ours, but instead of arrays, it uses objects whose property values are numbers—the weights of the edges.

+ +

So if we wanted to use that package, we’d have to make sure that our graph was stored in the format it expects. All edges get the same weight since our simplified model treats each road as having the same cost (one turn).

+ +
const {find_path} = require("dijkstrajs");
+
+let graph = {};
+for (let node of Object.keys(roadGraph)) {
+  let edges = graph[node] = {};
+  for (let dest of roadGraph[node]) {
+    edges[dest] = 1;
+  }
+}
+
+console.log(find_path(graph, "Post Office", "Cabin"));
+// → ["Post Office", "Alice's House", "Cabin"]
+ +

This can be a barrier to composition—when various packages are using different data structures to describe similar things, combining them is difficult. Therefore, if you want to design for composability, find out what data structures other people are using and, when possible, follow their example.

+ +

Summary

+ +

Modules provide structure to bigger programs by separating the code into pieces with clear interfaces and dependencies. The interface is the part of the module that’s visible from other modules, and the dependencies are the other modules that it makes use of.

+ +

Because JavaScript historically did not provide a module system, the CommonJS system was built on top of it. Then at some point it did get a built-in system, which now coexists uneasily with the CommonJS system.

+ +

A package is a chunk of code that can be distributed on its own. NPM is a repository of JavaScript packages. You can download all kinds of useful (and useless) packages from it.

+ +

Exercises

+ +

A modular robot

+ +

These are the bindings that the project from Chapter 7 creates:

+ +
roads
+buildGraph
+roadGraph
+VillageState
+runRobot
+randomPick
+randomRobot
+mailRoute
+routeRobot
+findRoute
+goalOrientedRobot
+ +

If you were to write that project as a modular program, what modules would you create? Which module would depend on which other module, and what would their interfaces look like?

+ +

Which pieces are likely to be available prewritten on NPM? Would you prefer to use an NPM package or write them yourself?

+ +
+ +

Here’s what I would have done (but again, there is no single right way to design a given module):

+ +

The code used to build the road graph lives in the graph module. Because I’d rather use dijkstrajs from NPM than our own pathfinding code, we’ll make this build the kind of graph data that dijkstrajs expects. This module exports a single function, buildGraph. I’d have buildGraph accept an array of two-element arrays, rather than strings containing hyphens, to make the module less dependent on the input format.

+ +

The roads module contains the raw road data (the roads array) and the roadGraph binding. This module depends on ./graph and exports the road graph.

+ +

The VillageState class lives in the state module. It depends on the ./roads module because it needs to be able to verify that a given road exists. It also needs randomPick. Since that is a three-line function, we could just put it into the state module as an internal helper function. But randomRobot needs it too. So we’d have to either duplicate it or put it into its own module. Since this function happens to exist on NPM in the random-item package, a good solution is to just make both modules depend on that. We can add the runRobot function to this module as well, since it’s small and closely related to state management. The module exports both the VillageState class and the runRobot function.

+ +

Finally, the robots, along with the values they depend on such as mailRoute, could go into an example-robots module, which depends on ./roads and exports the robot functions. To make it possible for goalOrientedRobot to do route-finding, this module also depends on dijkstrajs.

+ +

By offloading some work to NPM modules, the code became a little smaller. Each individual module does something rather simple and can be read on its own. Dividing code into modules also often suggests further improvements to the program’s design. In this case, it seems a little odd that the VillageState and the robots depend on a specific road graph. It might be a better idea to make the graph an argument to the state’s constructor and make the robots read it from the state object—this reduces dependencies (which is always good) and makes it possible to run simulations on different maps (which is even better).

+ +

Is it a good idea to use NPM modules for things that we could have written ourselves? In principle, yes—for nontrivial things like the pathfinding function you are likely to make mistakes and waste time writing them yourself. For tiny functions like random-item, writing them yourself is easy enough. But adding them wherever you need them does tend to clutter your modules.

+ +

However, you should also not underestimate the work involved in finding an appropriate NPM package. And even if you find one, it might not work well or may be missing some feature you need. On top of that, depending on NPM packages means you have to make sure they are installed, you have to distribute them with your program, and you might have to periodically upgrade them.

+ +

So again, this is a trade-off, and you can decide either way depending on how much the packages help you.

+ +
+ +

Roads module

+ +

Write a CommonJS module, based on the example from Chapter 7, that contains the array of roads and exports the graph data structure representing them as roadGraph. It should depend on a module ./graph, which exports a function buildGraph that is used to build the graph. This function expects an array of two-element arrays (the start and end points of the roads).

+ +
// Add dependencies and exports
+
+const roads = [
+  "Alice's House-Bob's House",   "Alice's House-Cabin",
+  "Alice's House-Post Office",   "Bob's House-Town Hall",
+  "Daria's House-Ernie's House", "Daria's House-Town Hall",
+  "Ernie's House-Grete's House", "Grete's House-Farm",
+  "Grete's House-Shop",          "Marketplace-Farm",
+  "Marketplace-Post Office",     "Marketplace-Shop",
+  "Marketplace-Town Hall",       "Shop-Town Hall"
+];
+ +
+ +

Since this is a CommonJS module, you have to use require to import the graph module. That was described as exporting a buildGraph function, which you can pick out of its interface object with a destructuring const declaration.

+ +

To export roadGraph, you add a property to the exports object. Because buildGraph takes a data structure that doesn’t precisely match roads, the splitting of the road strings must happen in your module.

+ +
+ +

Circular dependencies

+ +

A circular dependency is a situation where module A depends on B, and B also, directly or indirectly, depends on A. Many module systems simply forbid this because whichever order you choose for loading such modules, you cannot make sure that each module’s dependencies have been loaded before it runs.

+ +

CommonJS modules allow a limited form of cyclic dependencies. As long as the modules do not replace their default exports object and don’t access each other’s interface until after they finish loading, cyclic dependencies are okay.

+ +

The require function given earlier in this chapter supports this type of dependency cycle. Can you see how it handles cycles? What would go wrong when a module in a cycle does replace its default exports object?

+ +
+ +

The trick is that require adds modules to its cache before it starts loading the module. That way, if any require call made while it is running tries to load it, it is already known, and the current interface will be returned, rather than starting to load the module once more (which would eventually overflow the stack).

+ +

If a module overwrites its module.exports value, any other module that has received its interface value before it finished loading will have gotten hold of the default interface object (which is likely empty), rather than the intended interface value.

+ +
+
diff --git a/docs/11_async.html b/docs/11_async.html new file mode 100644 index 000000000..0fa419ee3 --- /dev/null +++ b/docs/11_async.html @@ -0,0 +1,681 @@ + + + + + Asynchronous Programming :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 11Asynchronous Programming

+ +
+ +

Who can wait quietly while the mud settles?
Who can remain still until the moment of action?

+ +
Laozi, Tao Te Ching
+ +
Picture of two crows on a branch
+ +

The central part of a computer, the part that carries out the individual steps that make up our programs, is called the processor. The programs we have seen so far are things that will keep the processor busy until they have finished their work. The speed at which something like a loop that manipulates numbers can be executed depends pretty much entirely on the speed of the processor.

+ +

But many programs interact with things outside of the processor. For example, they may communicate over a computer network or request data from the hard disk—which is a lot slower than getting it from memory.

+ +

When such a thing is happening, it would be a shame to let the processor sit idle—there might be some other work it could do in the meantime. In part, this is handled by your operating system, which will switch the processor between multiple running programs. But that doesn’t help when we want a single program to be able to make progress while it is waiting for a network request.

+ +

Asynchronicity

+ +

In a synchronous programming model, things happen one at a time. When you call a function that performs a long-running action, it returns only when the action has finished and it can return the result. This stops your program for the time the action takes.

+ +

An asynchronous model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk).

+ +

We can compare synchronous and asynchronous programming using a small example: a program that fetches two resources from the network and then combines results.

+ +

In a synchronous environment, where the request function returns only after it has done its work, the easiest way to perform this task is to make the requests one after the other. This has the drawback that the second request will be started only when the first has finished. The total time taken will be at least the sum of the two response times.

+ +

The solution to this problem, in a synchronous system, is to start additional threads of control. A thread is another running program whose execution may be interleaved with other programs by the operating system—since most modern computers contain multiple processors, multiple threads may even run at the same time, on different processors. A second thread could start the second request, and then both threads wait for their results to come back, after which they resynchronize to combine their results.

+ +

In the following diagram, the thick lines represent time the program spends running normally, and the thin lines represent time spent waiting for the network. In the synchronous model, the time taken by the network is part of the timeline for a given thread of control. In the asynchronous model, starting a network action conceptually causes a split in the timeline. The program that initiated the action continues running, and the action happens alongside it, notifying the program when it is finished.

Control flow for synchronous and asynchronous programming
+ +

Another way to describe the difference is that waiting for actions to finish is implicit in the synchronous model, while it is explicit, under our control, in the asynchronous one.

+ +

Asynchronicity cuts both ways. It makes expressing programs that do not fit the straight-line model of control easier, but it can also make expressing programs that do follow a straight line more awkward. We’ll see some ways to address this awkwardness later in the chapter.

+ +

Both of the important JavaScript programming platforms—browsers and Node.js—make operations that might take a while asynchronous, rather than relying on threads. Since programming with threads is notoriously hard (understanding what a program does is much more difficult when it’s doing multiple things at once), this is generally considered a good thing.

+ +

Crow tech

+ +

Most people are aware of the fact that crows are very smart birds. They can use tools, plan ahead, remember things, and even communicate these things among themselves.

+ +

What most people don’t know is that they are capable of many things that they keep well hidden from us. I’ve been told by a reputable (if somewhat eccentric) expert on corvids that crow technology is not far behind human technology, and they are catching up.

+ +

For example, many crow cultures have the ability to construct computing devices. These are not electronic, as human computing devices are, but operate through the actions of tiny insects, a species closely related to the termite, which has developed a symbiotic relationship with the crows. The birds provide them with food, and in return the insects build and operate their complex colonies that, with the help of the living creatures inside them, perform computations.

+ +

Such colonies are usually located in big, long-lived nests. The birds and insects work together to build a network of bulbous clay structures, hidden between the twigs of the nest, in which the insects live and work.

+ +

To communicate with other devices, these machines use light signals. The crows embed pieces of reflective material in special communication stalks, and the insects aim these to reflect light at another nest, encoding data as a sequence of quick flashes. This means that only nests that have an unbroken visual connection can communicate.

+ +

Our friend the corvid expert has mapped the network of crow nests in the village of Hières-sur-Amby, on the banks of the river Rhône. This map shows the nests and their connections:

A network of crow nests in a small village
+ +

In an astounding example of convergent evolution, crow computers run JavaScript. In this chapter we’ll write some basic networking functions for them.

+ +

Callbacks

+ +

One approach to asynchronous programming is to make functions that perform a slow action take an extra argument, a callback +function. The action is started, and when it finishes, the callback function is called with the result.

+ +

As an example, the setTimeout function, available both in Node.js and in browsers, waits a given number of milliseconds (a second is a thousand milliseconds) and then calls a function.

+ +
setTimeout(() => console.log("Tick"), 500);
+ +

Waiting is not generally a very important type of work, but it can be useful when doing something like updating an animation or checking whether something is taking longer than a given amount of time.

+ +

Performing multiple asynchronous actions in a row using callbacks means that you have to keep passing new functions to handle the continuation of the computation after the actions.

+ +

Most crow nest computers have a long-term data storage bulb, where pieces of information are etched into twigs so that they can be retrieved later. Etching, or finding a piece of data, takes a moment, so the interface to long-term storage is asynchronous and uses callback functions.

+ +

Storage bulbs store pieces of JSON-encodable data under names. A crow might store information about the places where it’s hidden food under the name "food caches", which could hold an array of names that point at other pieces of data, describing the actual cache. To look up a food cache in the storage bulbs of the Big Oak nest, a crow could run code like this:

+ +
import {bigOak} from "./crow-tech";
+
+bigOak.readStorage("food caches", caches => {
+  let firstCache = caches[0];
+  bigOak.readStorage(firstCache, info => {
+    console.log(info);
+  });
+});
+ +

(All binding names and strings have been translated from crow language to English.)

+ +

This style of programming is workable, but the indentation level increases with each asynchronous action because you end up in another function. Doing more complicated things, such as running multiple actions at the same time, can get a little awkward.

+ +

Crow nest computers are built to communicate using request-response pairs. That means one nest sends a message to another nest, which then immediately sends a message back, confirming receipt and possibly including a reply to a question asked in the message.

+ +

Each message is tagged with a type, which determines how it is handled. Our code can define handlers for specific request types, and when such a request comes in, the handler is called to produce a response.

+ +

The interface exported by the "./crow-tech" module provides callback-based functions for communication. Nests have a send method that sends off a request. It expects the name of the target nest, the type of the request, and the content of the request as its first three arguments, and it expects a function to call when a response comes in as its fourth and last argument.

+ +
bigOak.send("Cow Pasture", "note", "Let's caw loudly at 7PM",
+            () => console.log("Note delivered."));
+ +

But to make nests capable of receiving that request, we first have to define a request type named "note". The code that handles the requests has to run not just on this nest-computer but on all nests that can receive messages of this type. We’ll just assume that a crow flies over and installs our handler code on all the nests.

+ +
import {defineRequestType} from "./crow-tech";
+
+defineRequestType("note", (nest, content, source, done) => {
+  console.log(`${nest.name} received note: ${content}`);
+  done();
+});
+ +

The defineRequestType function defines a new type of request. The example adds support for "note" requests, which just sends a note to a given nest. Our implementation calls console.log so that we can verify that the request arrived. Nests have a name property that holds their name.

+ +

The fourth argument given to the handler, done, is a callback function that it must call when it is done with the request. If we had used the handler’s return value as the response value, that would mean that a request handler can’t itself perform asynchronous actions. A function doing asynchronous work typically returns before the work is done, having arranged for a callback to be called when it completes. So we need some asynchronous mechanism—in this case, another callback function—to signal when a response is available.

+ +

In a way, asynchronicity is contagious. Any function that calls a function that works asynchronously must itself be asynchronous, using a callback or similar mechanism to deliver its result. Calling a callback is somewhat more involved and error-prone than simply returning a value, so needing to structure large parts of your program that way is not great.

+ +

Promises

+ +

Working with abstract concepts is often easier when those concepts can be represented by values. In the case of asynchronous actions, you could, instead of arranging for a function to be called at some point in the future, return an object that represents this future event.

+ +

This is what the standard class Promise is for. A promise is an asynchronous action that may complete at some point and produce a value. It is able to notify anyone who is interested when its value is available.

+ +

The easiest way to create a promise is by calling Promise.resolve. This function ensures that the value you give it is wrapped in a promise. If it’s already a promise, it is simply returned—otherwise, you get a new promise that immediately finishes with your value as its result.

+ +
let fifteen = Promise.resolve(15);
+fifteen.then(value => console.log(`Got ${value}`));
+// → Got 15
+ +

To get the result of a promise, you can use its then method. This registers a callback function to be called when the promise resolves and produces a value. You can add multiple callbacks to a single promise, and they will be called, even if you add them after the promise has already resolved (finished).

+ +

But that’s not all the then method does. It returns another promise, which resolves to the value that the handler function returns or, if that returns a promise, waits for that promise and then resolves to its result.

+ +

It is useful to think of promises as a device to move values into an asynchronous reality. A normal value is simply there. A promised value is a value that might already be there or might appear at some point in the future. Computations defined in terms of promises act on such wrapped values and are executed asynchronously as the values become available.

+ +

To create a promise, you can use Promise as a constructor. It has a somewhat odd interface—the constructor expects a function as argument, which it immediately calls, passing it a function that it can use to resolve the promise. It works this way, instead of for example with a resolve method, so that only the code that created the promise can resolve it.

+ +

This is how you’d create a promise-based interface for the readStorage function:

+ +
function storage(nest, name) {
+  return new Promise(resolve => {
+    nest.readStorage(name, result => resolve(result));
+  });
+}
+
+storage(bigOak, "enemies")
+  .then(value => console.log("Got", value));
+ +

This asynchronous function returns a meaningful value. This is the main advantage of promises—they simplify the use of asynchronous functions. Instead of having to pass around callbacks, promise-based functions look similar to regular ones: they take input as arguments and return their output. The only difference is that the output may not be available yet.

+ +

Failure

+ +

Regular JavaScript computations can fail by throwing an exception. Asynchronous computations often need something like that. A network request may fail, or some code that is part of the asynchronous computation may throw an exception.

+ +

One of the most pressing problems with the callback style of asynchronous programming is that it makes it extremely difficult to make sure failures are properly reported to the callbacks.

+ +

A widely used convention is that the first argument to the callback is used to indicate that the action failed, and the second contains the value produced by the action when it was successful. Such callback functions must always check whether they received an exception and make sure that any problems they cause, including exceptions thrown by functions they call, are caught and given to the right function.

+ +

Promises make this easier. They can be either resolved (the action finished successfully) or rejected (it failed). Resolve handlers (as registered with then) are called only when the action is successful, and rejections are automatically propagated to the new promise that is returned by then. And when a handler throws an exception, this automatically causes the promise produced by its then call to be rejected. So if any element in a chain of asynchronous actions fails, the outcome of the whole chain is marked as rejected, and no success handlers are called beyond the point where it failed.

+ +

Much like resolving a promise provides a value, rejecting one also provides one, usually called the reason of the rejection. When an exception in a handler function causes the rejection, the exception value is used as the reason. Similarly, when a handler returns a promise that is rejected, that rejection flows into the next promise. There’s a Promise.reject function that creates a new, immediately rejected promise.

+ +

To explicitly handle such rejections, promises have a catch method that registers a handler to be called when the promise is rejected, similar to how then handlers handle normal resolution. It’s also very much like then in that it returns a new promise, which resolves to the original promise’s value if it resolves normally and to the result of the catch handler otherwise. If a catch handler throws an error, the new promise is also rejected.

+ +

As a shorthand, then also accepts a rejection handler as a second argument, so you can install both types of handlers in a single method call.

+ +

A function passed to the Promise constructor receives a second argument, alongside the resolve function, which it can use to reject the new promise.

+ +

The chains of promise values created by calls to then and catch can be seen as a pipeline through which asynchronous values or failures move. Since such chains are created by registering handlers, each link has a success handler or a rejection handler (or both) associated with it. Handlers that don’t match the type of outcome (success or failure) are ignored. But those that do match are called, and their outcome determines what kind of value comes next—success when it returns a non-promise value, rejection when it throws an exception, and the outcome of a promise when it returns one of those.

+ +
new Promise((_, reject) => reject(new Error("Fail")))
+  .then(value => console.log("Handler 1"))
+  .catch(reason => {
+    console.log("Caught failure " + reason);
+    return "nothing";
+  })
+  .then(value => console.log("Handler 2", value));
+// → Caught failure Error: Fail
+// → Handler 2 nothing
+ +

Much like an uncaught exception is handled by the environment, JavaScript environments can detect when a promise rejection isn’t handled and will report this as an error.

+ +

Networks are hard

+ +

Occasionally, there isn’t enough light for the crows’ mirror systems to transmit a signal or something is blocking the path of the signal. It is possible for a signal to be sent but never received.

+ +

As it is, that will just cause the callback given to send to never be called, which will probably cause the program to stop without even noticing there is a problem. It would be nice if, after a given period of not getting a response, a request would time out and report failure.

+ +

Often, transmission failures are random accidents, like a car’s headlight interfering with the light signals, and simply retrying the request may cause it to succeed. So while we’re at it, let’s make our request function automatically retry the sending of the request a few times before it gives up.

+ +

And, since we’ve established that promises are a good thing, we’ll also make our request function return a promise. In terms of what they can express, callbacks and promises are equivalent. Callback-based functions can be wrapped to expose a promise-based interface, and vice versa.

+ +

Even when a request and its response are successfully delivered, the response may indicate failure—for example, if the request tries to use a request type that hasn’t been defined or the handler throws an error. To support this, send and defineRequestType follow the convention mentioned before, where the first argument passed to callbacks is the failure reason, if any, and the second is the actual result.

+ +

These can be translated to promise resolution and rejection by our wrapper.

+ +
class Timeout extends Error {}
+
+function request(nest, target, type, content) {
+  return new Promise((resolve, reject) => {
+    let done = false;
+    function attempt(n) {
+      nest.send(target, type, content, (failed, value) => {
+        done = true;
+        if (failed) reject(failed);
+        else resolve(value);
+      });
+      setTimeout(() => {
+        if (done) return;
+        else if (n < 3) attempt(n + 1);
+        else reject(new Timeout("Timed out"));
+      }, 250);
+    }
+    attempt(1);
+  });
+}
+ +

Because promises can be resolved (or rejected) only once, this will work. The first time resolve or reject is called determines the outcome of the promise, and any further calls, such as the timeout arriving after the request finishes or a request coming back after another request finished, are ignored.

+ +

To build an asynchronous loop, for the retries, we need to use a recursive function—a regular loop doesn’t allow us to stop and wait for an asynchronous action. The attempt function makes a single attempt to send a request. It also sets a timeout that, if no response has come back after 250 milliseconds, either starts the next attempt or, if this was the fourth attempt, rejects the promise with an instance of Timeout as the reason.

+ +

Retrying every quarter-second and giving up when no response has come in after a second is definitely somewhat arbitrary. It is even possible, if the request did come through but the handler is just taking a bit longer, for requests to be delivered multiple times. We’ll write our handlers with that problem in mind—duplicate messages should be harmless.

+ +

In general, we will not be building a world-class, robust network today. But that’s okay—crows don’t have very high expectations yet when it comes to computing.

+ +

To isolate ourselves from callbacks altogether, we’ll go ahead and also define a wrapper for defineRequestType that allows the handler function to return a promise or plain value and wires that up to the callback for us.

+ +
function requestType(name, handler) {
+  defineRequestType(name, (nest, content, source,
+                           callback) => {
+    try {
+      Promise.resolve(handler(nest, content, source))
+        .then(response => callback(null, response),
+              failure => callback(failure));
+    } catch (exception) {
+      callback(exception);
+    }
+  });
+}
+ +

Promise.resolve is used to convert the value returned by handler to a promise if it isn’t already.

+ +

Note that the call to handler had to be wrapped in a try block to make sure any exception it raises directly is given to the callback. This nicely illustrates the difficulty of properly handling errors with raw callbacks—it is easy to forget to properly route exceptions like that, and if you don’t do it, failures won’t get reported to the right callback. Promises make this mostly automatic and thus less error-prone.

+ +

Collections of promises

+ +

Each nest computer keeps an array of other nests within transmission distance in its neighbors property. To check which of those are currently reachable, you could write a function that tries to send a "ping" request (a request that simply asks for a response) to each of them and see which ones come back.

+ +

When working with collections of promises running at the same time, the Promise.all function can be useful. It returns a promise that waits for all of the promises in the array to resolve and then resolves to an array of the values that these promises produced (in the same order as the original array). If any promise is rejected, the result of Promise.all is itself rejected.

+ +
requestType("ping", () => "pong");
+
+function availableNeighbors(nest) {
+  let requests = nest.neighbors.map(neighbor => {
+    return request(nest, neighbor, "ping")
+      .then(() => true, () => false);
+  });
+  return Promise.all(requests).then(result => {
+    return nest.neighbors.filter((_, i) => result[i]);
+  });
+}
+ +

When a neighbor isn’t available, we don’t want the entire combined promise to fail since then we still wouldn’t know anything. So the function that is mapped over the set of neighbors to turn them into request promises attaches handlers that make successful requests produce true and rejected ones produce false.

+ +

In the handler for the combined promise, filter is used to remove those elements from the neighbors array whose corresponding value is false. This makes use of the fact that filter passes the array index of the current element as a second argument to its filtering function (map, some, and similar higher-order array methods do the same).

+ +

Network flooding

+ +

The fact that nests can talk only to their neighbors greatly inhibits the usefulness of this network.

+ +

For broadcasting information to the whole network, one solution is to set up a type of request that is automatically forwarded to neighbors. These neighbors then in turn forward it to their neighbors, until the whole network has received the message.

+ +
import {everywhere} from "./crow-tech";
+
+everywhere(nest => {
+  nest.state.gossip = [];
+});
+
+function sendGossip(nest, message, exceptFor = null) {
+  nest.state.gossip.push(message);
+  for (let neighbor of nest.neighbors) {
+    if (neighbor == exceptFor) continue;
+    request(nest, neighbor, "gossip", message);
+  }
+}
+
+requestType("gossip", (nest, message, source) => {
+  if (nest.state.gossip.includes(message)) return;
+  console.log(`${nest.name} received gossip '${
+               message}' from ${source}`);
+  sendGossip(nest, message, source);
+});
+ +

To avoid sending the same message around the network forever, each nest keeps an array of gossip strings that it has already seen. To define this array, we use the everywhere function—which runs code on every nest—to add a property to the nest’s state object, which is where we’ll keep nest-local state.

+ +

When a nest receives a duplicate gossip message, which is very likely to happen with everybody blindly resending them, it ignores it. But when it receives a new message, it excitedly tells all its neighbors except for the one who sent it the message.

+ +

This will cause a new piece of gossip to spread through the network like an ink stain in water. Even when some connections aren’t currently working, if there is an alternative route to a given nest, the gossip will reach it through there.

+ +

This style of network communication is called flooding—it floods the network with a piece of information until all nodes have it.

+ +

We can call sendGossip to see a message flow through the village.

+ +
sendGossip(bigOak, "Kids with airgun in the park");
+ +

Message routing

+ +

If a given node wants to talk to a single other node, flooding is not a very efficient approach. Especially when the network is big, that would lead to a lot of useless data transfers.

+ +

An alternative approach is to set up a way for messages to hop from node to node until they reach their destination. The difficulty with that is it requires knowledge about the layout of the network. To send a request in the direction of a faraway nest, it is necessary to know which neighboring nest gets it closer to its destination. Sending it in the wrong direction will not do much good.

+ +

Since each nest knows only about its direct neighbors, it doesn’t have the information it needs to compute a route. We must somehow spread the information about these connections to all nests, preferably in a way that allows it to change over time, when nests are abandoned or new nests are built.

+ +

We can use flooding again, but instead of checking whether a given message has already been received, we now check whether the new set of neighbors for a given nest matches the current set we have for it.

+ +
requestType("connections", (nest, {name, neighbors},
+                            source) => {
+  let connections = nest.state.connections;
+  if (JSON.stringify(connections.get(name)) ==
+      JSON.stringify(neighbors)) return;
+  connections.set(name, neighbors);
+  broadcastConnections(nest, name, source);
+});
+
+function broadcastConnections(nest, name, exceptFor = null) {
+  for (let neighbor of nest.neighbors) {
+    if (neighbor == exceptFor) continue;
+    request(nest, neighbor, "connections", {
+      name,
+      neighbors: nest.state.connections.get(name)
+    });
+  }
+}
+
+everywhere(nest => {
+  nest.state.connections = new Map;
+  nest.state.connections.set(nest.name, nest.neighbors);
+  broadcastConnections(nest, nest.name);
+});
+ +

The comparison uses JSON.stringify because ==, on objects or arrays, will return true only when the two are the exact same value, which is not what we need here. Comparing the JSON strings is a crude but effective way to compare their content.

+ +

The nodes immediately start broadcasting their connections, which should, unless some nests are completely unreachable, quickly give every nest a map of the current network graph.

+ +

A thing you can do with graphs is find routes in them, as we saw in Chapter 7. If we have a route toward a message’s destination, we know which direction to send it in.

+ +

This findRoute function, which greatly resembles the findRoute from Chapter 7, searches for a way to reach a given node in the network. But instead of returning the whole route, it just returns the next step. That next nest will itself, using its current information about the network, decide where it sends the message.

+ +
function findRoute(from, to, connections) {
+  let work = [{at: from, via: null}];
+  for (let i = 0; i < work.length; i++) {
+    let {at, via} = work[i];
+    for (let next of connections.get(at) || []) {
+      if (next == to) return via;
+      if (!work.some(w => w.at == next)) {
+        work.push({at: next, via: via || next});
+      }
+    }
+  }
+  return null;
+}
+ +

Now we can build a function that can send long-distance messages. If the message is addressed to a direct neighbor, it is delivered as usual. If not, it is packaged in an object and sent to a neighbor that is closer to the target, using the "route" request type, which will cause that neighbor to repeat the same behavior.

+ +
function routeRequest(nest, target, type, content) {
+  if (nest.neighbors.includes(target)) {
+    return request(nest, target, type, content);
+  } else {
+    let via = findRoute(nest.name, target,
+                        nest.state.connections);
+    if (!via) throw new Error(`No route to ${target}`);
+    return request(nest, via, "route",
+                   {target, type, content});
+  }
+}
+
+requestType("route", (nest, {target, type, content}) => {
+  return routeRequest(nest, target, type, content);
+});
+ +

We can now send a message to the nest in the church tower, which is four network hops removed.

+ +
routeRequest(bigOak, "Church Tower", "note",
+             "Incoming jackdaws!");
+ +

We’ve constructed several layers of functionality on top of a primitive communication system to make it convenient to use. This is a nice (though simplified) model of how real computer networks work.

+ +

A distinguishing property of computer networks is that they aren’t reliable—abstractions built on top of them can help, but you can’t abstract away network failure. So network programming is typically very much about anticipating and dealing with failures.

+ +

Async functions

+ +

To store important information, crows are known to duplicate it across nests. That way, when a hawk destroys a nest, the information isn’t lost.

+ +

To retrieve a given piece of information that it doesn’t have in its own storage bulb, a nest computer might consult random other nests in the network until it finds one that has it.

+ +
requestType("storage", (nest, name) => storage(nest, name));
+
+function findInStorage(nest, name) {
+  return storage(nest, name).then(found => {
+    if (found != null) return found;
+    else return findInRemoteStorage(nest, name);
+  });
+}
+
+function network(nest) {
+  return Array.from(nest.state.connections.keys());
+}
+
+function findInRemoteStorage(nest, name) {
+  let sources = network(nest).filter(n => n != nest.name);
+  function next() {
+    if (sources.length == 0) {
+      return Promise.reject(new Error("Not found"));
+    } else {
+      let source = sources[Math.floor(Math.random() *
+                                      sources.length)];
+      sources = sources.filter(n => n != source);
+      return routeRequest(nest, source, "storage", name)
+        .then(value => value != null ? value : next(),
+              next);
+    }
+  }
+  return next();
+}
+ +

Because connections is a Map, Object.keys doesn’t work on it. It has a keys method, but that returns an iterator rather than an array. An iterator (or iterable value) can be converted to an array with the Array.from function.

+ +

Even with promises this is some rather awkward code. Multiple asynchronous actions are chained together in non-obvious ways. We again need a recursive function (next) to model looping through the nests.

+ +

And the thing the code actually does is completely linear—it always waits for the previous action to complete before starting the next one. In a synchronous programming model, it’d be simpler to express.

+ +

The good news is that JavaScript allows you to write pseudo-synchronous code to describe asynchronous computation. An async function is a function that implicitly returns a promise and that can, in its body, await other promises in a way that looks synchronous.

+ +

We can rewrite findInStorage like this:

+ +
async function findInStorage(nest, name) {
+  let local = await storage(nest, name);
+  if (local != null) return local;
+
+  let sources = network(nest).filter(n => n != nest.name);
+  while (sources.length > 0) {
+    let source = sources[Math.floor(Math.random() *
+                                    sources.length)];
+    sources = sources.filter(n => n != source);
+    try {
+      let found = await routeRequest(nest, source, "storage",
+                                     name);
+      if (found != null) return found;
+    } catch (_) {}
+  }
+  throw new Error("Not found");
+}
+ +

An async function is marked by the word async before the function keyword. Methods can also be made async by writing async before their name. When such a function or method is called, it returns a promise. As soon as the body returns something, that promise is resolved. If it throws an exception, the promise is rejected.

+ +
findInStorage(bigOak, "events on 2017-12-21")
+  .then(console.log);
+ +

Inside an async function, the word await can be put in front of an expression to wait for a promise to resolve and only then continue the execution of the function.

+ +

Such a function no longer, like a regular JavaScript function, runs from start to completion in one go. Instead, it can be frozen at any point that has an await, and can be resumed at a later time.

+ +

For non-trivial asynchronous code, this notation is usually more convenient than directly using promises. Even if you need to do something that doesn’t fit the synchronous model, such as perform multiple actions at the same time, it is easy to combine await with the direct use of promises.

+ +

Generators

+ +

This ability of functions to be paused and then resumed again is not exclusive to async functions. JavaScript also has a feature called generator functions. These are similar, but without the promises.

+ +

When you define a function with function* (placing an asterisk after the word function), it becomes a generator. When you call a generator, it returns an iterator, which we already saw in Chapter 6.

+ +
function* powers(n) {
+  for (let current = n;; current *= n) {
+    yield current;
+  }
+}
+
+for (let power of powers(3)) {
+  if (power > 50) break;
+  console.log(power);
+}
+// → 3
+// → 9
+// → 27
+ +

Initially, when you call powers, the function is frozen at its start. Every time you call next on the iterator, the function runs until it hits a yield expression, which pauses it and causes the yielded value to become the next value produced by the iterator. When the function returns (the one in the example never does), the iterator is done.

+ +

Writing iterators is often much easier when you use generator functions. The iterator for the Group class (from the exercise in Chapter 6) can be written with this generator:

+ +
Group.prototype[Symbol.iterator] = function*() {
+  for (let i = 0; i < this.members.length; i++) {
+    yield this.members[i];
+  }
+};
+ +

There’s no longer a need to create an object to hold the iteration state—generators automatically save their local state every time they yield.

+ +

Such yield expressions may occur only directly in the generator function itself and not in an inner function you define inside of it. The state a generator saves, when yielding, is only its local environment and the position where it yielded.

+ +

An async function is a special type of generator. It produces a promise when called, which is resolved when it returns (finishes) and rejected when it throws an exception. Whenever it yields (awaits) a promise, the result of that promise (value or thrown exception) is the result of the await expression.

+ +

The event loop

+ +

Asynchronous programs are executed piece by piece. Each piece may start some actions and schedule code to be executed when the action finishes or fails. In between these pieces, the program sits idle, waiting for the next action.

+ +

So callbacks are not directly called by the code that scheduled them. If I call setTimeout from within a function, that function will have returned by the time the callback function is called. And when the callback returns, control does not go back to the function that scheduled it.

+ +

Asynchronous behavior happens on its own empty function call +stack. This is one of the reasons that, without promises, managing exceptions across asynchronous code is hard. Since each callback starts with a mostly empty stack, your catch handlers won’t be on the stack when they throw an exception.

+ +
try {
+  setTimeout(() => {
+    throw new Error("Woosh");
+  }, 20);
+} catch (_) {
+  // This will not run
+  console.log("Caught!");
+}
+ +

No matter how closely together events—such as timeouts or incoming requests—happen, a JavaScript environment will run only one program at a time. You can think of this as it running a big loop around your program, called the event loop. When there’s nothing to be done, that loop is stopped. But as events come in, they are added to a queue, and their code is executed one after the other. Because no two things run at the same time, slow-running code might delay the handling of other events.

+ +

This example sets a timeout but then dallies until after the timeout’s intended point of time, causing the timeout to be late.

+ +
let start = Date.now();
+setTimeout(() => {
+  console.log("Timeout ran at", Date.now() - start);
+}, 20);
+while (Date.now() < start + 50) {}
+console.log("Wasted time until", Date.now() - start);
+// → Wasted time until 50
+// → Timeout ran at 55
+ +

Promises always resolve or reject as a new event. Even if a promise is already resolved, waiting for it will cause your callback to run after the current script finishes, rather than right away.

+ +
Promise.resolve("Done").then(console.log);
+console.log("Me first!");
+// → Me first!
+// → Done
+ +

In later chapters we’ll see various other types of events that run on the event loop.

+ +

Asynchronous bugs

+ +

When your program runs synchronously, in a single go, there are no state changes happening except those that the program itself makes. For asynchronous programs this is different—they may have gaps in their execution during which other code can run.

+ +

Let’s look at an example. One of the hobbies of our crows is to count the number of chicks that hatch throughout the village every year. Nests store this count in their storage bulbs. The following code tries to enumerate the counts from all the nests for a given year:

+ +
function anyStorage(nest, source, name) {
+  if (source == nest.name) return storage(nest, name);
+  else return routeRequest(nest, source, "storage", name);
+}
+
+async function chicks(nest, year) {
+  let list = "";
+  await Promise.all(network(nest).map(async name => {
+    list += `${name}: ${
+      await anyStorage(nest, name, `chicks in ${year}`)
+    }\n`;
+  }));
+  return list;
+}
+ +

The async name => part shows that arrow functions can also be made async by putting the word async in front of them.

+ +

The code doesn’t immediately look suspicious...it maps the async arrow function over the set of nests, creating an array of promises, and then uses Promise.all to wait for all of these before returning the list they build up.

+ +

But it is seriously broken. It’ll always return only a single line of output, listing the nest that was slowest to respond.

+ +
chicks(bigOak, 2017).then(console.log);
+ +

Can you work out why?

+ +

The problem lies in the += operator, which takes the current value of list at the time where the statement starts executing and then, when the await finishes, sets the list binding to be that value plus the added string.

+ +

But between the time where the statement starts executing and the time where it finishes there’s an asynchronous gap. The map expression runs before anything has been added to the list, so each of the += operators starts from an empty string and ends up, when its storage retrieval finishes, setting list to a single-line list—the result of adding its line to the empty string.

+ +

This could have easily been avoided by returning the lines from the mapped promises and calling join on the result of Promise.all, instead of building up the list by changing a binding. As usual, computing new values is less error-prone than changing existing values.

+ +
async function chicks(nest, year) {
+  let lines = network(nest).map(async name => {
+    return name + ": " +
+      await anyStorage(nest, name, `chicks in ${year}`);
+  });
+  return (await Promise.all(lines)).join("\n");
+}
+ +

Mistakes like this are easy to make, especially when using await, and you should be aware of where the gaps in your code occur. An advantage of JavaScript’s explicit asynchronicity (whether through callbacks, promises, or await) is that spotting these gaps is relatively easy.

+ +

Summary

+ +

Asynchronous programming makes it possible to express waiting for long-running actions without freezing the program during these actions. JavaScript environments typically implement this style of programming using callbacks, functions that are called when the actions complete. An event loop schedules such callbacks to be called when appropriate, one after the other, so that their execution does not overlap.

+ +

Programming asynchronously is made easier by promises, objects that represent actions that might complete in the future, and async functions, which allow you to write an asynchronous program as if it were synchronous.

+ +

Exercises

+ +

Tracking the scalpel

+ +

The village crows own an old scalpel that they occasionally use on special missions—say, to cut through screen doors or packaging. To be able to quickly track it down, every time the scalpel is moved to another nest, an entry is added to the storage of both the nest that had it and the nest that took it, under the name "scalpel", with its new location as the value.

+ +

This means that finding the scalpel is a matter of following the breadcrumb trail of storage entries, until you find a nest where that points at the nest itself.

+ +

Write an async function locateScalpel that does this, starting at the nest on which it runs. You can use the anyStorage function defined earlier to access storage in arbitrary nests. The scalpel has been going around long enough that you may assume that every nest has a "scalpel" entry in its data storage.

+ +

Next, write the same function again without using async and await.

+ +

Do request failures properly show up as rejections of the returned promise in both versions? How?

+ +
async function locateScalpel(nest) {
+  // Your code here.
+}
+
+function locateScalpel2(nest) {
+  // Your code here.
+}
+
+locateScalpel(bigOak).then(console.log);
+// → Butcher Shop
+ +
+ +

This can be done with a single loop that searches through the nests, moving forward to the next when it finds a value that doesn’t match the current nest’s name and returning the name when it finds a matching value. In the async function, a regular for or while loop can be used.

+ +

To do the same in a plain function, you will have to build your loop using a recursive function. The easiest way to do this is to have that function return a promise by calling then on the promise that retrieves the storage value. Depending on whether that value matches the name of the current nest, the handler returns that value or a further promise created by calling the loop function again.

+ +

Don’t forget to start the loop by calling the recursive function once from the main function.

+ +

In the async function, rejected promises are converted to exceptions by await. When an async function throws an exception, its promise is rejected. So that works.

+ +

If you implemented the non-async function as outlined earlier, the way then works also automatically causes a failure to end up in the returned promise. If a request fails, the handler passed to then isn’t called, and the promise it returns is rejected with the same reason.

+ +
+ +

Building Promise.all

+ +

Given an array of promises, Promise.all returns a promise that waits for all of the promises in the array to finish. It then succeeds, yielding an array of result values. If a promise in the array fails, the promise returned by all fails too, with the failure reason from the failing promise.

+ +

Implement something like this yourself as a regular function called Promise_all.

+ +

Remember that after a promise has succeeded or failed, it can’t succeed or fail again, and further calls to the functions that resolve it are ignored. This can simplify the way you handle failure of your promise.

+ +
function Promise_all(promises) {
+  return new Promise((resolve, reject) => {
+    // Your code here.
+  });
+}
+
+// Test code.
+Promise_all([]).then(array => {
+  console.log("This should be []:", array);
+});
+function soon(val) {
+  return new Promise(resolve => {
+    setTimeout(() => resolve(val), Math.random() * 500);
+  });
+}
+Promise_all([soon(1), soon(2), soon(3)]).then(array => {
+  console.log("This should be [1, 2, 3]:", array);
+});
+Promise_all([soon(1), Promise.reject("X"), soon(3)])
+  .then(array => {
+    console.log("We should not get here");
+  })
+  .catch(error => {
+    if (error != "X") {
+      console.log("Unexpected failure:", error);
+    }
+  });
+ +
+ +

The function passed to the Promise constructor will have to call then on each of the promises in the given array. When one of them succeeds, two things need to happen. The resulting value needs to be stored in the correct position of a result array, and we must check whether this was the last pending promise and finish our own promise if it was.

+ +

The latter can be done with a counter that is initialized to the length of the input array and from which we subtract 1 every time a promise succeeds. When it reaches 0, we are done. Make sure you take into account the situation where the input array is empty (and thus no promise will ever resolve).

+ +

Handling failure requires some thought but turns out to be extremely simple. Just pass the reject function of the wrapping promise to each of the promises in the array as a catch handler or as a second argument to then so that a failure in one of them triggers the rejection of the whole wrapper promise.

+ +
+
diff --git a/docs/12_language.html b/docs/12_language.html new file mode 100644 index 000000000..aa32b6168 --- /dev/null +++ b/docs/12_language.html @@ -0,0 +1,502 @@ + + + + + Project: A Programming Language :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 12Project: A Programming Language

+ +
+ +

The evaluator, which determines the meaning of expressions in a programming language, is just another program.

+ +
Hal Abelson and Gerald Sussman, Structure and Interpretation of Computer Programs
+ +
Picture of an egg with smaller eggs inside
+ +

Building your own programming language is surprisingly easy (as long as you do not aim too high) and very enlightening.

+ +

The main thing I want to show in this chapter is that there is no magic involved in building your own language. I’ve often felt that some human inventions were so immensely clever and complicated that I’d never be able to understand them. But with a little reading and experimenting, they often turn out to be quite mundane.

+ +

We will build a programming language called Egg. It will be a tiny, simple language—but one that is powerful enough to express any computation you can think of. It will allow simple abstraction based on functions.

+ +

Parsing

+ +

The most immediately visible part of a programming language is its syntax, or notation. A parser is a program that reads a piece of text and produces a data structure that reflects the structure of the program contained in that text. If the text does not form a valid program, the parser should point out the error.

+ +

Our language will have a simple and uniform syntax. Everything in Egg is an expression. An expression can be the name of a binding, a number, a string, or an application. Applications are used for function calls but also for constructs such as if or while.

+ +

To keep the parser simple, strings in Egg do not support anything like backslash escapes. A string is simply a sequence of characters that are not double quotes, wrapped in double quotes. A number is a sequence of digits. Binding names can consist of any character that is not whitespace and that does not have a special meaning in the syntax.

+ +

Applications are written the way they are in JavaScript, by putting parentheses after an expression and having any number of arguments between those parentheses, separated by commas.

+ +
do(define(x, 10),
+   if(>(x, 5),
+      print("large"),
+      print("small")))
+ +

The uniformity of the Egg language means that things that are operators in JavaScript (such as >) are normal bindings in this language, applied just like other functions. And since the syntax has no concept of a block, we need a do construct to represent doing multiple things in sequence.

+ +

The data structure that the parser will use to describe a program consists of expression objects, each of which has a type property indicating the kind of expression it is and other properties to describe its content.

+ +

Expressions of type "value" represent literal strings or numbers. Their value property contains the string or number value that they represent. Expressions of type "word" are used for identifiers (names). Such objects have a name property that holds the identifier’s name as a string. Finally, "apply" expressions represent applications. They have an operator property that refers to the expression that is being applied, as well as an args property that holds an array of argument expressions.

+ +

The >(x, 5) part of the previous program would be represented like this:

+ +
{
+  type: "apply",
+  operator: {type: "word", name: ">"},
+  args: [
+    {type: "word", name: "x"},
+    {type: "value", value: 5}
+  ]
+}
+ +

Such a data structure is called a syntax tree. If you imagine the objects as dots and the links between them as lines between those dots, it has a treelike shape. The fact that expressions contain other expressions, which in turn might contain more expressions, is similar to the way tree branches split and split again.

The structure of a syntax tree
+ +

Contrast this to the parser we wrote for the configuration file format in Chapter 9, which had a simple structure: it split the input into lines and handled those lines one at a time. There were only a few simple forms that a line was allowed to have.

+ +

Here we must find a different approach. Expressions are not separated into lines, and they have a recursive structure. Application expressions contain other expressions.

+ +

Fortunately, this problem can be solved very well by writing a parser function that is recursive in a way that reflects the recursive nature of the language.

+ +

We define a function parseExpression, which takes a string as input and returns an object containing the data structure for the expression at the start of the string, along with the part of the string left after parsing this expression. When parsing subexpressions (the argument to an application, for example), this function can be called again, yielding the argument expression as well as the text that remains. This text may in turn contain more arguments or may be the closing parenthesis that ends the list of arguments.

+ +

This is the first part of the parser:

+ +
function parseExpression(program) {
+  program = skipSpace(program);
+  let match, expr;
+  if (match = /^"([^"]*)"/.exec(program)) {
+    expr = {type: "value", value: match[1]};
+  } else if (match = /^\d+\b/.exec(program)) {
+    expr = {type: "value", value: Number(match[0])};
+  } else if (match = /^[^\s(),#"]+/.exec(program)) {
+    expr = {type: "word", name: match[0]};
+  } else {
+    throw new SyntaxError("Unexpected syntax: " + program);
+  }
+
+  return parseApply(expr, program.slice(match[0].length));
+}
+
+function skipSpace(string) {
+  let first = string.search(/\S/);
+  if (first == -1) return "";
+  return string.slice(first);
+}
+ +

Because Egg, like JavaScript, allows any amount of whitespace between its elements, we have to repeatedly cut the whitespace off the start of the program string. That is what the skipSpace function helps with.

+ +

After skipping any leading space, parseExpression uses three regular expressions to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which one matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use SyntaxError instead of Error as the exception constructor, which is another standard error type, because it is a little more specific—it is also the error type thrown when an attempt is made to run an invalid JavaScript program.

+ +

We then cut off the part that was matched from the program string and pass that, along with the object for the expression, to parseApply, which checks whether the expression is an application. If so, it parses a parenthesized list of arguments.

+ +
function parseApply(expr, program) {
+  program = skipSpace(program);
+  if (program[0] != "(") {
+    return {expr: expr, rest: program};
+  }
+
+  program = skipSpace(program.slice(1));
+  expr = {type: "apply", operator: expr, args: []};
+  while (program[0] != ")") {
+    let arg = parseExpression(program);
+    expr.args.push(arg.expr);
+    program = skipSpace(arg.rest);
+    if (program[0] == ",") {
+      program = skipSpace(program.slice(1));
+    } else if (program[0] != ")") {
+      throw new SyntaxError("Expected ',' or ')'");
+    }
+  }
+  return parseApply(expr, program.slice(1));
+}
+ +

If the next character in the program is not an opening parenthesis, this is not an application, and parseApply returns the expression it was given.

+ +

Otherwise, it skips the opening parenthesis and creates the syntax +tree object for this application expression. It then recursively calls parseExpression to parse each argument until a closing parenthesis is found. The recursion is indirect, through parseApply and parseExpression calling each other.

+ +

Because an application expression can itself be applied (such as in multiplier(2)(1)), parseApply must, after it has parsed an application, call itself again to check whether another pair of parentheses follows.

+ +

This is all we need to parse Egg. We wrap it in a convenient parse function that verifies that it has reached the end of the input string after parsing the expression (an Egg program is a single expression), and that gives us the program’s data structure.

+ +
function parse(program) {
+  let {expr, rest} = parseExpression(program);
+  if (skipSpace(rest).length > 0) {
+    throw new SyntaxError("Unexpected text after program");
+  }
+  return expr;
+}
+
+console.log(parse("+(a, 10)"));
+// → {type: "apply",
+//    operator: {type: "word", name: "+"},
+//    args: [{type: "word", name: "a"},
+//           {type: "value", value: 10}]}
+ +

It works! It doesn’t give us very helpful information when it fails and doesn’t store the line and column on which each expression starts, which might be helpful when reporting errors later, but it’s good enough for our purposes.

+ +

The evaluator

+ +

What can we do with the syntax tree for a program? Run it, of course! And that is what the evaluator does. You give it a syntax tree and a scope object that associates names with values, and it will evaluate the expression that the tree represents and return the value that this produces.

+ +
const specialForms = Object.create(null);
+
+function evaluate(expr, scope) {
+  if (expr.type == "value") {
+    return expr.value;
+  } else if (expr.type == "word") {
+    if (expr.name in scope) {
+      return scope[expr.name];
+    } else {
+      throw new ReferenceError(
+        `Undefined binding: ${expr.name}`);
+    }
+  } else if (expr.type == "apply") {
+    let {operator, args} = expr;
+    if (operator.type == "word" &&
+        operator.name in specialForms) {
+      return specialForms[operator.name](expr.args, scope);
+    } else {
+      let op = evaluate(operator, scope);
+      if (typeof op == "function") {
+        return op(...args.map(arg => evaluate(arg, scope)));
+      } else {
+        throw new TypeError("Applying a non-function.");
+      }
+    }
+  }
+}
+ +

The evaluator has code for each of the expression types. A literal value expression produces its value. (For example, the expression 100 just evaluates to the number 100.) For a binding, we must check whether it is actually defined in the scope and, if it is, fetch the binding’s value.

+ +

Applications are more involved. If they are a special form, like if, we do not evaluate anything and pass the argument expressions, along with the scope, to the function that handles this form. If it is a normal call, we evaluate the operator, verify that it is a function, and call it with the evaluated arguments.

+ +

We use plain JavaScript function values to represent Egg’s function values. We will come back to this later, when the special form called fun is defined.

+ +

The recursive structure of evaluate resembles the similar structure of the parser, and both mirror the structure of the language itself. It would also be possible to integrate the parser with the evaluator and evaluate during parsing, but splitting them up this way makes the program clearer.

+ +

This is really all that is needed to interpret Egg. It is that simple. But without defining a few special forms and adding some useful values to the environment, you can’t do much with this language yet.

+ +

Special forms

+ +

The specialForms object is used to define special syntax in Egg. It associates words with functions that evaluate such forms. It is currently empty. Let’s add if.

+ +
specialForms.if = (args, scope) => {
+  if (args.length != 3) {
+    throw new SyntaxError("Wrong number of args to if");
+  } else if (evaluate(args[0], scope) !== false) {
+    return evaluate(args[1], scope);
+  } else {
+    return evaluate(args[2], scope);
+  }
+};
+ +

Egg’s if construct expects exactly three arguments. It will evaluate the first, and if the result isn’t the value false, it will evaluate the second. Otherwise, the third gets evaluated. This if form is more similar to JavaScript’s ternary ?: operator than to JavaScript’s if. It is an expression, not a statement, and it produces a value, namely, the result of the second or third argument.

+ +

Egg also differs from JavaScript in how it handles the condition value to if. It will not treat things like zero or the empty string as false, only the precise value false.

+ +

The reason we need to represent if as a special form, rather than a regular function, is that all arguments to functions are evaluated before the function is called, whereas if should evaluate only either its second or its third argument, depending on the value of the first.

+ +

The while form is similar.

+ +
specialForms.while = (args, scope) => {
+  if (args.length != 2) {
+    throw new SyntaxError("Wrong number of args to while");
+  }
+  while (evaluate(args[0], scope) !== false) {
+    evaluate(args[1], scope);
+  }
+
+  // Since undefined does not exist in Egg, we return false,
+  // for lack of a meaningful result.
+  return false;
+};
+ +

Another basic building block is do, which executes all its arguments from top to bottom. Its value is the value produced by the last argument.

+ +
specialForms.do = (args, scope) => {
+  let value = false;
+  for (let arg of args) {
+    value = evaluate(arg, scope);
+  }
+  return value;
+};
+ +

To be able to create bindings and give them new values, we also create a form called define. It expects a word as its first argument and an expression producing the value to assign to that word as its second argument. Since define, like everything, is an expression, it must return a value. We’ll make it return the value that was assigned (just like JavaScript’s = operator).

+ +
specialForms.define = (args, scope) => {
+  if (args.length != 2 || args[0].type != "word") {
+    throw new SyntaxError("Incorrect use of define");
+  }
+  let value = evaluate(args[1], scope);
+  scope[args[0].name] = value;
+  return value;
+};
+ +

The environment

+ +

The scope accepted by evaluate is an object with properties whose names correspond to binding names and whose values correspond to the values those bindings are bound to. Let’s define an object to represent the global scope.

+ +

To be able to use the if construct we just defined, we must have access to Boolean values. Since there are only two Boolean values, we do not need special syntax for them. We simply bind two names to the values true and false and use them.

+ +
const topScope = Object.create(null);
+
+topScope.true = true;
+topScope.false = false;
+ +

We can now evaluate a simple expression that negates a Boolean value.

+ +
let prog = parse(`if(true, false, true)`);
+console.log(evaluate(prog, topScope));
+// → false
+ +

To supply basic arithmetic and comparison operators, we will also add some function values to the scope. In the interest of keeping the code short, we’ll use Function to synthesize a bunch of operator functions in a loop, instead of defining them individually.

+ +
for (let op of ["+", "-", "*", "/", "==", "<", ">"]) {
+  topScope[op] = Function("a, b", `return a ${op} b;`);
+}
+ +

A way to output values is also useful, so we’ll wrap console.log in a function and call it print.

+ +
topScope.print = value => {
+  console.log(value);
+  return value;
+};
+ +

That gives us enough elementary tools to write simple programs. The following function provides a convenient way to parse a program and run it in a fresh scope:

+ +
function run(program) {
+  return evaluate(parse(program), Object.create(topScope));
+}
+ +

We’ll use object prototype chains to represent nested scopes so that the program can add bindings to its local scope without changing the top-level scope.

+ +
run(`
+do(define(total, 0),
+   define(count, 1),
+   while(<(count, 11),
+         do(define(total, +(total, count)),
+            define(count, +(count, 1)))),
+   print(total))
+`);
+// → 55
+ +

This is the program we’ve seen several times before, which computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in less than 150 lines of code.

+ +

Functions

+ +

A programming language without functions is a poor programming language indeed.

+ +

Fortunately, it isn’t hard to add a fun construct, which treats its last argument as the function’s body and uses all arguments before that as the names of the function’s parameters.

+ +
specialForms.fun = (args, scope) => {
+  if (!args.length) {
+    throw new SyntaxError("Functions need a body");
+  }
+  let body = args[args.length - 1];
+  let params = args.slice(0, args.length - 1).map(expr => {
+    if (expr.type != "word") {
+      throw new SyntaxError("Parameter names must be words");
+    }
+    return expr.name;
+  });
+
+  return function() {
+    if (arguments.length != params.length) {
+      throw new TypeError("Wrong number of arguments");
+    }
+    let localScope = Object.create(scope);
+    for (let i = 0; i < arguments.length; i++) {
+      localScope[params[i]] = arguments[i];
+    }
+    return evaluate(body, localScope);
+  };
+};
+ +

Functions in Egg get their own local scope. The function produced by the fun form creates this local scope and adds the argument bindings to it. It then evaluates the function body in this scope and returns the result.

+ +
run(`
+do(define(plusOne, fun(a, +(a, 1))),
+   print(plusOne(10)))
+`);
+// → 11
+
+run(`
+do(define(pow, fun(base, exp,
+     if(==(exp, 0),
+        1,
+        *(base, pow(base, -(exp, 1)))))),
+   print(pow(2, 10)))
+`);
+// → 1024
+ +

Compilation

+ +

What we have built is an interpreter. During evaluation, it acts directly on the representation of the program produced by the parser.

+ +

Compilation is the process of adding another step between the parsing and the running of a program, which transforms the program into something that can be evaluated more efficiently by doing as much work as possible in advance. For example, in well-designed languages it is obvious, for each use of a binding, which binding is being referred to, without actually running the program. This can be used to avoid looking up the binding by name every time it is accessed, instead directly fetching it from some predetermined memory location.

+ +

Traditionally, compilation involves converting the program to machine code, the raw format that a computer’s processor can execute. But any process that converts a program to a different representation can be thought of as compilation.

+ +

It would be possible to write an alternative evaluation strategy for Egg, one that first converts the program to a JavaScript program, uses Function to invoke the JavaScript compiler on it, and then runs the result. When done right, this would make Egg run very fast while still being quite simple to implement.

+ +

If you are interested in this topic and willing to spend some time on it, I encourage you to try to implement such a compiler as an exercise.

+ +

Cheating

+ +

When we defined if and while, you probably noticed that they were more or less trivial wrappers around JavaScript’s own if and while. Similarly, the values in Egg are just regular old JavaScript values.

+ +

If you compare the implementation of Egg, built on top of JavaScript, with the amount of work and complexity required to build a programming language directly on the raw functionality provided by a machine, the difference is huge. Regardless, this example ideally gave you an impression of the way programming languages work.

+ +

And when it comes to getting something done, cheating is more effective than doing everything yourself. Though the toy language in this chapter doesn’t do anything that couldn’t be done better in JavaScript, there are situations where writing small languages helps get real work done.

+ +

Such a language does not have to resemble a typical programming language. If JavaScript didn’t come equipped with regular expressions, for example, you could write your own parser and evaluator for regular expressions.

+ +

Or imagine you are building a giant robotic dinosaur and need to program its behavior. JavaScript might not be the most effective way to do this. You might instead opt for a language that looks like this:

+ +
behavior walk
+  perform when
+    destination ahead
+  actions
+    move left-foot
+    move right-foot
+
+behavior attack
+  perform when
+    Godzilla in-view
+  actions
+    fire laser-eyes
+    launch arm-rockets
+ +

This is what is usually called a domain-specific language, a language tailored to express a narrow domain of knowledge. Such a language can be more expressive than a general-purpose language because it is designed to describe exactly the things that need to be described in its domain, and nothing else.

+ +

Exercises

+ +

Arrays

+ +

Add support for arrays to Egg by adding the following three functions to the top scope: array(...values) to construct an array containing the argument values, length(array) to get an array’s length, and element(array, n) to fetch the nth element from an array.

+ +
// Modify these definitions...
+
+topScope.array = "...";
+
+topScope.length = "...";
+
+topScope.element = "...";
+
+run(`
+do(define(sum, fun(array,
+     do(define(i, 0),
+        define(sum, 0),
+        while(<(i, length(array)),
+          do(define(sum, +(sum, element(array, i))),
+             define(i, +(i, 1)))),
+        sum))),
+   print(sum(array(1, 2, 3))))
+`);
+// → 6
+ +
+ +

The easiest way to do this is to represent Egg arrays with JavaScript arrays.

+ +

The values added to the top scope must be functions. By using a rest argument (with triple-dot notation), the definition of array can be very simple.

+ +
+ +

Closure

+ +

The way we have defined fun allows functions in Egg to reference the surrounding scope, allowing the function’s body to use local values that were visible at the time the function was defined, just like JavaScript functions do.

+ +

The following program illustrates this: function f returns a function that adds its argument to f’s argument, meaning that it needs access to the local scope inside f to be able to use binding a.

+ +
run(`
+do(define(f, fun(a, fun(b, +(a, b)))),
+   print(f(4)(5)))
+`);
+// → 9
+ +

Go back to the definition of the fun form and explain which mechanism causes this to work.

+ +
+ +

Again, we are riding along on a JavaScript mechanism to get the equivalent feature in Egg. Special forms are passed the local scope in which they are evaluated so that they can evaluate their subforms in that scope. The function returned by fun has access to the scope argument given to its enclosing function and uses that to create the function’s local scope when it is called.

+ +

This means that the prototype of the local scope will be the scope in which the function was created, which makes it possible to access bindings in that scope from the function. This is all there is to implementing closure (though to compile it in a way that is actually efficient, you’d need to do some more work).

+ +
+ +

Comments

+ +

It would be nice if we could write comments in Egg. For example, whenever we find a hash sign (#), we could treat the rest of the line as a comment and ignore it, similar to // in JavaScript.

+ +

We do not have to make any big changes to the parser to support this. We can simply change skipSpace to skip comments as if they are whitespace so that all the points where skipSpace is called will now also skip comments. Make this change.

+ +
// This is the old skipSpace. Modify it...
+function skipSpace(string) {
+  let first = string.search(/\S/);
+  if (first == -1) return "";
+  return string.slice(first);
+}
+
+console.log(parse("# hello\nx"));
+// → {type: "word", name: "x"}
+
+console.log(parse("a # one\n   # two\n()"));
+// → {type: "apply",
+//    operator: {type: "word", name: "a"},
+//    args: []}
+ +
+ +

Make sure your solution handles multiple comments in a row, with potentially whitespace between or after them.

+ +

A regular expression is probably the easiest way to solve this. Write something that matches “whitespace or a comment, zero or more times”. Use the exec or match method and look at the length of the first element in the returned array (the whole match) to find out how many characters to slice off.

+ +
+ +

Fixing scope

+ +

Currently, the only way to assign a binding a value is define. This construct acts as a way both to define new bindings and to give existing ones a new value.

+ +

This ambiguity causes a problem. When you try to give a nonlocal binding a new value, you will end up defining a local one with the same name instead. Some languages work like this by design, but I’ve always found it an awkward way to handle scope.

+ +

Add a special form set, similar to define, which gives a binding a new value, updating the binding in an outer scope if it doesn’t already exist in the inner scope. If the binding is not defined at all, throw a ReferenceError (another standard error type).

+ +

The technique of representing scopes as simple objects, which has made things convenient so far, will get in your way a little at this point. You might want to use the Object.getPrototypeOf function, which returns the prototype of an object. Also remember that scopes do not derive from Object.prototype, so if you want to call hasOwnProperty on them, you have to use this clumsy expression:

+ +
Object.prototype.hasOwnProperty.call(scope, name);
+ +
specialForms.set = (args, scope) => {
+  // Your code here.
+};
+
+run(`
+do(define(x, 4),
+   define(setx, fun(val, set(x, val))),
+   setx(50),
+   print(x))
+`);
+// → 50
+run(`set(quux, true)`);
+// → Some kind of ReferenceError
+ +
+ +

You will have to loop through one scope at a time, using Object.getPrototypeOf to go to the next outer scope. For each scope, use hasOwnProperty to find out whether the binding, indicated by the name property of the first argument to set, exists in that scope. If it does, set it to the result of evaluating the second argument to set and then return that value.

+ +

If the outermost scope is reached (Object.getPrototypeOf returns null) and we haven’t found the binding yet, it doesn’t exist, and an error should be thrown.

+ +
+
diff --git a/docs/13_browser.html b/docs/13_browser.html new file mode 100644 index 000000000..3649e531a --- /dev/null +++ b/docs/13_browser.html @@ -0,0 +1,176 @@ + + + + + JavaScript and the Browser :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 13JavaScript and the Browser

+ +
+ +

The dream behind the Web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished.

+ +
Tim Berners-Lee, The World Wide Web: A very short personal history
+ +
Picture of a telephone switchboard
+ +

The next chapters of this book will talk about web browsers. Without web browsers, there would be no JavaScript. Or even if there were, no one would ever have paid any attention to it.

+ +

Web technology has been decentralized from the start, not just technically but also in the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which then, sometimes, ended up being adopted by others—and finally set down as in standards.

+ +

This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose collaboration (or occasionally open hostility). On the other hand, the haphazard way in which the Web was developed means that the resulting system is not exactly a shining example of internal consistency. Some parts of it are downright confusing and poorly conceived.

+ +

Networks and the Internet

+ +

Computer networks have been around since the 1950s. If you put cables between two or more computers and allow them to send data back and forth through these cables, you can do all kinds of wonderful things.

+ +

And if connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the Internet. It has lived up to its promise.

+ +

A computer can use this network to shoot bits at another computer. For any effective communication to arise out of this bit-shooting, the computers on both ends must know what the bits are supposed to represent. The meaning of any given sequence of bits depends entirely on the kind of thing that it is trying to express and on the encoding mechanism used.

+ +

A network protocol describes a style of communication over a network. There are protocols for sending email, for fetching email, for sharing files, and even for controlling computers that happen to be infected by malicious software.

+ +

For example, the Hypertext Transfer Protocol (HTTP) is a protocol for retrieving named resources (chunks of information, such as web pages or pictures). It specifies that the side making the request should start with a line like this, naming the resource and the version of the protocol that it is trying to use:

+ +
GET /index.html HTTP/1.1
+ +

There are a lot more rules about the way the requester can include more information in the request and the way the other side, which returns the resource, packages up its content. We’ll look at HTTP in a little more detail in Chapter 18.

+ +

Most protocols are built on top of other protocols. HTTP treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. As we saw in Chapter 11, ensuring those things is already a rather difficult problem.

+ +

The Transmission Control Protocol (TCP) is a protocol that addresses this problem. All Internet-connected devices “speak” it, and most communication on the Internet is built on top of it.

+ +

A TCP connection works as follows: one computer must be waiting, or listening, for other computers to start talking to it. To be able to listen for different kinds of communication at the same time on a single machine, each listener has a number (called a port) associated with it. Most protocols specify which port should be used by default. For example, when we want to send an email using the SMTP protocol, the machine through which we send it is expected to be listening on port 25.

+ +

Another computer can then establish a connection by connecting to the target machine using the correct port number. If the target machine can be reached and is listening on that port, the connection is successfully created. The listening computer is called the server, and the connecting computer is called the client.

+ +

Such a connection acts as a two-way pipe through which bits can flow—the machines on both ends can put data into it. Once the bits are successfully transmitted, they can be read out again by the machine on the other side. This is a convenient model. You could say that TCP provides an abstraction of the network.

+ +

The Web

+ +

The World Wide Web (not to be confused with the Internet as a whole) is a set of protocols and formats that allow us to visit web pages in a browser. The “Web” part in the name refers to the fact that such pages can easily link to each other, thus connecting into a huge mesh that users can move through.

+ +

To become part of the Web, all you need to do is connect a machine to the Internet and have it listen on port 80 with the HTTP protocol so that other computers can ask it for documents.

+ +

Each document on the Web is named by a Uniform Resource Locator (URL), which looks something like this:

+ +
  http://eloquentjavascript.net/13_browser.html
+ |      |                      |               |
+ protocol       server               path
+ +

The first part tells us that this URL uses the HTTP protocol (as opposed to, for example, encrypted HTTP, which would be https://). Then comes the part that identifies which server we are requesting the document from. Last is a path string that identifies the specific document (or resource) we are interested in.

+ +

Machines connected to the Internet get an IP address, which is a number that can be used to send messages to that machine, and looks something like 149.210.142.219 or 2001:4860:4860::8888. But lists of more or less random numbers are hard to remember and awkward to type, so you can instead register a domain name for a specific address or set of addresses. I registered eloquentjavascript.net to point at the IP address of a machine I control and can thus use that domain name to serve web pages.

+ +

If you type this URL into your browser’s address bar, the browser will try to retrieve and display the document at that URL. First, your browser has to find out what address eloquentjavascript.net refers to. Then, using the HTTP protocol, it will make a connection to the server at that address and ask for the resource /13_browser.html. If all goes well, the server sends back a document, which your browser then displays on your screen.

+ +

HTML

+ +

HTML, which stands for Hypertext Markup Language, is the document format used for web pages. An HTML document contains text, as well as tags that give structure to the text, describing things such as links, paragraphs, and headings.

+ +

A short HTML document might look like this:

+ +
<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>My home page</title>
+  </head>
+  <body>
+    <h1>My home page</h1>
+    <p>Hello, I am Marijn and this is my home page.</p>
+    <p>I also wrote a book! Read it
+      <a href="http://eloquentjavascript.net">here</a>.</p>
+  </body>
+</html>
+ +

The tags, wrapped in angle brackets (< and >, the symbols for less than and greater than), provide information about the structure of the document. The other text is just plain text.

+ +

The document starts with <!doctype html>, which tells the browser to interpret the page as modern HTML, as opposed to various dialects that were in use in the past.

+ +

HTML documents have a head and a body. The head contains information about the document, and the body contains the document itself. In this case, the head declares that the title of this document is “My home page” and that it uses the UTF-8 encoding, which is a way to encode Unicode text as binary data. The document’s body contains a heading (<h1>, meaning “heading 1”—<h2> to <h6> produce subheadings) and two paragraphs (<p>).

+ +

Tags come in several forms. An element, such as the body, a paragraph, or a link, is started by an opening tag like <p> and ended by a closing tag like </p>. Some opening tags, such as the one for the link (<a>), contain extra information in the form of name="value" pairs. These are called attributes. In this case, the destination of the link is indicated with href="http://eloquentjavascript.net", where href stands for “hypertext reference”.

+ +

Some kinds of tags do not enclose anything and thus do not need to be closed. The metadata tag <meta charset="utf-8"> is an example of this.

+ +

To be able to include angle brackets in the text of a document, even though they have a special meaning in HTML, yet another form of special notation has to be introduced. A plain opening angle bracket is written as &lt; (“less than”), and a closing bracket is written as &gt; (“greater than”). In HTML, an ampersand (&) character followed by a name or character code and a semicolon (;) is called an entity and will be replaced by the character it encodes.

+ +

This is analogous to the way backslashes are used in JavaScript strings. Since this mechanism gives ampersand characters a special meaning, too, they need to be escaped as &amp;. Inside attribute values, which are wrapped in double quotes, &quot; can be used to insert an actual quote character.

+ +

HTML is parsed in a remarkably error-tolerant way. When tags that should be there are missing, the browser reconstructs them. The way in which this is done has been standardized, and you can rely on all modern browsers to do it in the same way.

+ +

The following document will be treated just like the one shown previously:

+ +
<!doctype html>
+
+<meta charset=utf-8>
+<title>My home page</title>
+
+<h1>My home page</h1>
+<p>Hello, I am Marijn and this is my home page.
+<p>I also wrote a book! Read it
+  <a href=http://eloquentjavascript.net>here</a>.
+ +

The <html>, <head>, and <body> tags are gone completely. The browser knows that <meta> and <title> belong in the head and that <h1> means the body has started. Furthermore, I am no longer explicitly closing the paragraphs since opening a new paragraph or ending the document will close them implicitly. The quotes around the attribute values are also gone.

+ +

This book will usually omit the <html>, <head>, and <body> tags from examples to keep them short and free of clutter. But I will close tags and include quotes around attributes.

+ +

I will also usually omit the doctype and charset declaration. This is not to be taken as an encouragement to drop these from HTML documents. Browsers will often do ridiculous things when you forget them. You should consider the doctype and the charset metadata to be implicitly present in examples, even when they are not actually shown in the text.

+ +

HTML and JavaScript

+ +

In the context of this book, the most important HTML tag is <script>. This tag allows us to include a piece of JavaScript in a document.

+ +
<h1>Testing alert</h1>
+<script>alert("hello!");</script>
+ +

Such a script will run as soon as its <script> tag is encountered while the browser reads the HTML. This page will pop up a dialog when opened—the alert function resembles prompt, in that it pops up a little window, but only shows a message without asking for input.

+ +

Including large programs directly in HTML documents is often impractical. The <script> tag can be given an src attribute to fetch a script file (a text file containing a JavaScript program) from a URL.

+ +
<h1>Testing alert</h1>
+<script src="code/hello.js"></script>
+ +

The code/hello.js file included here contains the same program—alert("hello!"). When an HTML page references other URLs as part of itself—for example, an image file or a script—web browsers will retrieve them immediately and include them in the page.

+ +

A script tag must always be closed with </script>, even if it refers to a script file and doesn’t contain any code. If you forget this, the rest of the page will be interpreted as part of the script.

+ +

You can load ES modules (see Chapter 10) in the browser by giving your script tag a type="module" attribute. Such modules can depend on other modules by using URLs relative to themselves as module names in import declarations.

+ +

Some attributes can also contain a JavaScript program. The <button> tag shown next (which shows up as a button) has an onclick attribute. The attribute’s value will be run whenever the button is clicked.

+ +
<button onclick="alert('Boom!');">DO NOT PRESS</button>
+ +

Note that I had to use single quotes for the string in the onclick attribute because double quotes are already used to quote the whole attribute. I could also have used &quot;.

+ +

In the sandbox

+ +

Running programs downloaded from the Internet is potentially dangerous. You do not know much about the people behind most sites you visit, and they do not necessarily mean well. Running programs by people who do not mean well is how you get your computer infected by viruses, your data stolen, and your accounts hacked.

+ +

Yet the attraction of the Web is that you can browse it without necessarily trusting all the pages you visit. This is why browsers severely limit the things a JavaScript program may do: it can’t look at the files on your computer or modify anything not related to the web page it was embedded in.

+ +

Isolating a programming environment in this way is called sandboxing, the idea being that the program is harmlessly playing in a sandbox. But you should imagine this particular kind of sandbox as having a cage of thick steel bars over it so that the programs playing in it can’t actually get out.

+ +

The hard part of sandboxing is allowing the programs enough room to be useful yet at the same time restricting them from doing anything dangerous. Lots of useful functionality, such as communicating with other servers or reading the content of the copy-paste clipboard, can also be used to do problematic, privacy-invading things.

+ +

Every now and then, someone comes up with a new way to circumvent the limitations of a browser and do something harmful, ranging from leaking minor private information to taking over the whole machine that the browser runs on. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized, rather than secretly exploited by some government agency or mafia.

+ +

Compatibility and the browser wars

+ +

In the early stages of the Web, a browser called Mosaic dominated the market. After a few years, the balance shifted to Netscape, which was then, in turn, largely supplanted by Microsoft’s Internet Explorer. At any point where a single browser was dominant, that browser’s vendor would feel entitled to unilaterally invent new features for the Web. Since most users used the most popular browser, websites would simply start using those features—never mind the other browsers.

+ +

This was the dark age of compatibility, often called the browser wars. Web developers were left with not one unified Web but two or three incompatible platforms. To make things worse, the browsers in use around 2003 were all full of bugs, and of course the bugs were different for each browser. Life was hard for people writing web pages.

+ +

Mozilla Firefox, a not-for-profit offshoot of Netscape, challenged Internet Explorer’s position in the late 2000s. Because Microsoft was not particularly interested in staying competitive at the time, Firefox took a lot of market share away from it. Around the same time, Google introduced its Chrome browser, and Apple’s Safari browser gained popularity, leading to a situation where there were four major players, rather than one.

+ +

The new players had a more serious attitude toward standards and better engineering practices, giving us less incompatibility and fewer bugs. Microsoft, seeing its market share crumble, came around and adopted these attitudes in its Edge browser, which replaces Internet Explorer. If you are starting to learn web development today, consider yourself lucky. The latest versions of the major browsers behave quite uniformly and have relatively few bugs.

+
diff --git a/docs/14_dom.html b/docs/14_dom.html new file mode 100644 index 000000000..f816c432a --- /dev/null +++ b/docs/14_dom.html @@ -0,0 +1,572 @@ + + + + + The Document Object Model :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 14The Document Object Model

+ +
+ +

Too bad! Same old story! Once you’ve finished building your house you notice you’ve accidentally learned something that you really should have known—before you started.

+ +
Friedrich Nietzsche, Beyond Good and Evil
+ +
Picture of a tree with letters and scripts hanging from its branches
+ +

When you open a web page in your browser, the browser retrieves the page’s HTML text and parses it, much like the way our parser from Chapter 12 parsed programs. The browser builds up a model of the document’s structure and uses this model to draw the page on the screen.

+ +

This representation of the document is one of the toys that a JavaScript program has available in its sandbox. It is a data +structure that you can read or modify. It acts as a live data structure: when it’s modified, the page on the screen is updated to reflect the changes.

+ +

Document structure

+ +

You can imagine an HTML document as a nested set of boxes. Tags such as <body> and </body> enclose other tags, which in turn contain other tags or text. Here’s the example document from the previous chapter:

+ +
<!doctype html>
+<html>
+  <head>
+    <title>My home page</title>
+  </head>
+  <body>
+    <h1>My home page</h1>
+    <p>Hello, I am Marijn and this is my home page.</p>
+    <p>I also wrote a book! Read it
+      <a href="http://eloquentjavascript.net">here</a>.</p>
+  </body>
+</html>
+ +

This page has the following structure:

HTML document as nested boxes
+ +

The data structure the browser uses to represent the document follows this shape. For each box, there is an object, which we can interact with to find out things such as what HTML tag it represents and which boxes and text it contains. This representation is called the Document Object Model, or DOM for short.

+ +

The global binding document gives us access to these objects. Its documentElement property refers to the object representing the <html> tag. Since every HTML document has a head and a body, it also has head and body properties, pointing at those elements.

+ +

Trees

+ +

Think back to the syntax trees from Chapter 12 for a moment. Their structures are strikingly similar to the structure of a browser’s document. Each node may refer to other nodes, children, which in turn may have their own children. This shape is typical of nested structures where elements can contain subelements that are similar to themselves.

+ +

We call a data structure a tree when it has a branching structure, has no cycles (a node may not contain itself, directly or indirectly), and has a single, well-defined root. In the case of the DOM, document.documentElement serves as the root.

+ +

Trees come up a lot in computer science. In addition to representing recursive structures such as HTML documents or programs, they are often used to maintain sorted sets of data because elements can usually be found or inserted more efficiently in a tree than in a flat array.

+ +

A typical tree has different kinds of nodes. The syntax tree for the Egg language had identifiers, values, and application nodes. Application nodes may have children, whereas identifiers and values are leaves, or nodes without children.

+ +

The same goes for the DOM. Nodes for elements, which represent HTML tags, determine the structure of the document. These can have child nodes. An example of such a node is document.body. Some of these children can be leaf nodes, such as pieces of text or comment nodes.

+ +

Each DOM node object has a nodeType property, which contains a code (number) that identifies the type of node. Elements have code 1, which is also defined as the constant property Node.ELEMENT_NODE. Text nodes, representing a section of text in the document, get code 3 (Node.TEXT_NODE). Comments have code 8 (Node.COMMENT_NODE).

+ +

Another way to visualize our document tree is as follows:

HTML document as a tree
+ +

The leaves are text nodes, and the arrows indicate parent-child relationships between nodes.

+ +

The standard

+ +

Using cryptic numeric codes to represent node types is not a very JavaScript-like thing to do. Later in this chapter, we’ll see that other parts of the DOM interface also feel cumbersome and alien. The reason for this is that the DOM wasn’t designed for just JavaScript. Rather, it tries to be a language-neutral interface that can be used in other systems as well—not just for HTML but also for XML, which is a generic data format with an HTML-like syntax.

+ +

This is unfortunate. Standards are often useful. But in this case, the advantage (cross-language consistency) isn’t all that compelling. Having an interface that is properly integrated with the language you are using will save you more time than having a familiar interface across languages.

+ +

As an example of this poor integration, consider the childNodes property that element nodes in the DOM have. This property holds an array-like object, with a length property and properties labeled by numbers to access the child nodes. But it is an instance of the NodeList type, not a real array, so it does not have methods such as slice and map.

+ +

Then there are issues that are simply poor design. For example, there is no way to create a new node and immediately add children or attributes to it. Instead, you have to first create it and then add the children and attributes one by one, using side effects. Code that interacts heavily with the DOM tends to get long, repetitive, and ugly.

+ +

But these flaws aren’t fatal. Since JavaScript allows us to create our own abstractions, it is possible to design improved ways to express the operations you are performing. Many libraries intended for browser programming come with such tools.

+ +

Moving through the tree

+ +

DOM nodes contain a wealth of links to other nearby nodes. The following diagram illustrates these:

Links between DOM nodes
+ +

Although the diagram shows only one link of each type, every node has a parentNode property that points to the node it is part of, if any. Likewise, every element node (node type 1) has a childNodes property that points to an array-like object holding its children.

+ +

In theory, you could move anywhere in the tree using just these parent and child links. But JavaScript also gives you access to a number of additional convenience links. The firstChild and lastChild properties point to the first and last child elements or have the value null for nodes without children. Similarly, previousSibling and nextSibling point to adjacent nodes, which are nodes with the same parent that appear immediately before or after the node itself. For a first child, previousSibling will be null, and for a last child, nextSibling will be null.

+ +

There’s also the children property, which is like childNodes but contains only element (type 1) children, not other types of child nodes. This can be useful when you aren’t interested in text nodes.

+ +

When dealing with a nested data structure like this one, recursive functions are often useful. The following function scans a document for text nodes containing a given string and returns true when it has found one:

+ +
function talksAbout(node, string) {
+  if (node.nodeType == Node.ELEMENT_NODE) {
+    for (let i = 0; i < node.childNodes.length; i++) {
+      if (talksAbout(node.childNodes[i], string)) {
+        return true;
+      }
+    }
+    return false;
+  } else if (node.nodeType == Node.TEXT_NODE) {
+    return node.nodeValue.indexOf(string) > -1;
+  }
+}
+
+console.log(talksAbout(document.body, "book"));
+// → true
+ +

Because childNodes is not a real array, we cannot loop over it with for/of and have to run over the index range using a regular for loop or use Array.from.

+ +

The nodeValue property of a text node holds the string of text that it represents.

+ +

Finding elements

+ +

Navigating these links among parents, children, and siblings is often useful. But if we want to find a specific node in the document, reaching it by starting at document.body and following a fixed path of properties is a bad idea. Doing so bakes assumptions into our program about the precise structure of the document—a structure you might want to change later. Another complicating factor is that text nodes are created even for the whitespace between nodes. The example document’s <body> tag does not have just three children (<h1> and two <p> elements) but actually has seven: those three, plus the spaces before, after, and between them.

+ +

So if we want to get the href attribute of the link in that document, we don’t want to say something like “Get the second child of the sixth child of the document body”. It’d be better if we could say “Get the first link in the document”. And we can.

+ +
let link = document.body.getElementsByTagName("a")[0];
+console.log(link.href);
+ +

All element nodes have a getElementsByTagName method, which collects all elements with the given tag name that are descendants (direct or indirect children) of that node and returns them as an array-like +object.

+ +

To find a specific single node, you can give it an id attribute and use document.getElementById instead.

+ +
<p>My ostrich Gertrude:</p>
+<p><img id="gertrude" src="img/ostrich.png"></p>
+
+<script>
+  let ostrich = document.getElementById("gertrude");
+  console.log(ostrich.src);
+</script>
+ +

A third, similar method is getElementsByClassName, which, like getElementsByTagName, searches through the contents of an element node and retrieves all elements that have the given string in their class attribute.

+ +

Changing the document

+ +

Almost everything about the DOM data structure can be changed. The shape of the document tree can be modified by changing parent-child relationships. Nodes have a remove method to remove them from their current parent node. To add a child node to an element node, we can use appendChild, which puts it at the end of the list of children, or insertBefore, which inserts the node given as the first argument before the node given as the second argument.

+ +
<p>One</p>
+<p>Two</p>
+<p>Three</p>
+
+<script>
+  let paragraphs = document.body.getElementsByTagName("p");
+  document.body.insertBefore(paragraphs[2], paragraphs[0]);
+</script>
+ +

A node can exist in the document in only one place. Thus, inserting paragraph Three in front of paragraph One will first remove it from the end of the document and then insert it at the front, resulting in Three/One/Two. All operations that insert a node somewhere will, as a side effect, cause it to be removed from its current position (if it has one).

+ +

The replaceChild method is used to replace a child node with another one. It takes as arguments two nodes: a new node and the node to be replaced. The replaced node must be a child of the element the method is called on. Note that both replaceChild and insertBefore expect the new node as their first argument.

+ +

Creating nodes

+ +

Say we want to write a script that replaces all images (<img> tags) in the document with the text held in their alt attributes, which specifies an alternative textual representation of the image.

+ +

This involves not only removing the images but adding a new text node to replace them. Text nodes are created with the document.createTextNode method.

+ +
<p>The <img src="img/cat.png" alt="Cat"> in the
+  <img src="img/hat.png" alt="Hat">.</p>
+
+<p><button onclick="replaceImages()">Replace</button></p>
+
+<script>
+  function replaceImages() {
+    let images = document.body.getElementsByTagName("img");
+    for (let i = images.length - 1; i >= 0; i--) {
+      let image = images[i];
+      if (image.alt) {
+        let text = document.createTextNode(image.alt);
+        image.parentNode.replaceChild(text, image);
+      }
+    }
+  }
+</script>
+ +

Given a string, createTextNode gives us a text node that we can insert into the document to make it show up on the screen.

+ +

The loop that goes over the images starts at the end of the list. This is necessary because the node list returned by a method like getElementsByTagName (or a property like childNodes) is live. That is, it is updated as the document changes. If we started from the front, removing the first image would cause the list to lose its first element so that the second time the loop repeats, where i is 1, it would stop because the length of the collection is now also 1.

+ +

If you want a solid collection of nodes, as opposed to a live one, you can convert the collection to a real array by calling Array.from.

+ +
let arrayish = {0: "one", 1: "two", length: 2};
+let array = Array.from(arrayish);
+console.log(array.map(s => s.toUpperCase()));
+// → ["ONE", "TWO"]
+ +

To create element nodes, you can use the document.createElement method. This method takes a tag name and returns a new empty node of the given type.

+ +

The following example defines a utility elt, which creates an element node and treats the rest of its arguments as children to that node. This function is then used to add an attribution to a quote.

+ +
<blockquote id="quote">
+  No book can ever be finished. While working on it we learn
+  just enough to find it immature the moment we turn away
+  from it.
+</blockquote>
+
+<script>
+  function elt(type, ...children) {
+    let node = document.createElement(type);
+    for (let child of children) {
+      if (typeof child != "string") node.appendChild(child);
+      else node.appendChild(document.createTextNode(child));
+    }
+    return node;
+  }
+
+  document.getElementById("quote").appendChild(
+    elt("footer", "—",
+        elt("strong", "Karl Popper"),
+        ", preface to the second edition of ",
+        elt("em", "The Open Society and Its Enemies"),
+        ", 1950"));
+</script>
+ +

Attributes

+ +

Some element attributes, such as href for links, can be accessed through a property of the same name on the element’s DOM object. This is the case for most commonly used standard attributes.

+ +

But HTML allows you to set any attribute you want on nodes. This can be useful because it allows you to store extra information in a document. If you make up your own attribute names, though, such attributes will not be present as properties on the element’s node. Instead, you have to use the getAttribute and setAttribute methods to work with them.

+ +
<p data-classified="secret">The launch code is 00000000.</p>
+<p data-classified="unclassified">I have two feet.</p>
+
+<script>
+  let paras = document.body.getElementsByTagName("p");
+  for (let para of Array.from(paras)) {
+    if (para.getAttribute("data-classified") == "secret") {
+      para.remove();
+    }
+  }
+</script>
+ +

It is recommended to prefix the names of such made-up attributes with data- to ensure they do not conflict with any other attributes.

+ +

There is a commonly used attribute, class, which is a keyword in the JavaScript language. For historical reasons—some old JavaScript implementations could not handle property names that matched keywords—the property used to access this attribute is called className. You can also access it under its real name, "class", by using the getAttribute and setAttribute methods.

+ +

Layout

+ +

You may have noticed that different types of elements are laid out differently. Some, such as paragraphs (<p>) or headings (<h1>), take up the whole width of the document and are rendered on separate lines. These are called block elements. Others, such as links (<a>) or the <strong> element, are rendered on the same line with their surrounding text. Such elements are called inline elements.

+ +

For any given document, browsers are able to compute a layout, which gives each element a size and position based on its type and content. This layout is then used to actually draw the document.

+ +

The size and position of an element can be accessed from JavaScript. The offsetWidth and offsetHeight properties give you the space the element takes up in pixels. A pixel is the basic unit of measurement in the browser. It traditionally corresponds to the smallest dot that the screen can draw, but on modern displays, which can draw very small dots, that may no longer be the case, and a browser pixel may span multiple display dots.

+ +

Similarly, clientWidth and clientHeight give you the size of the space inside the element, ignoring border width.

+ +
<p style="border: 3px solid red">
+  I'm boxed in
+</p>
+
+<script>
+  let para = document.body.getElementsByTagName("p")[0];
+  console.log("clientHeight:", para.clientHeight);
+  console.log("offsetHeight:", para.offsetHeight);
+</script>
+ +

The most effective way to find the precise position of an element on the screen is the getBoundingClientRect method. It returns an object with top, bottom, left, and right properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. If you want them relative to the whole document, you must add the current scroll position, which you can find in the pageXOffset and pageYOffset bindings.

+ +

Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time you change it but wait as long as they can. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout to draw the changed document to the screen. When a program asks for the position or size of something by reading properties such as offsetHeight or calling getBoundingClientRect, providing correct information also requires computing a layout.

+ +

A program that repeatedly alternates between reading DOM layout information and changing the DOM forces a lot of layout computations to happen and will consequently run very slowly. The following code is an example of this. It contains two different programs that build up a line of X characters 2,000 pixels wide and measures the time each one takes.

+ +
<p><span id="one"></span></p>
+<p><span id="two"></span></p>
+
+<script>
+  function time(name, action) {
+    let start = Date.now(); // Current time in milliseconds
+    action();
+    console.log(name, "took", Date.now() - start, "ms");
+  }
+
+  time("naive", () => {
+    let target = document.getElementById("one");
+    while (target.offsetWidth < 2000) {
+      target.appendChild(document.createTextNode("X"));
+    }
+  });
+  // → naive took 32 ms
+
+  time("clever", function() {
+    let target = document.getElementById("two");
+    target.appendChild(document.createTextNode("XXXXX"));
+    let total = Math.ceil(2000 / (target.offsetWidth / 5));
+    target.firstChild.nodeValue = "X".repeat(total);
+  });
+  // → clever took 1 ms
+</script>
+ +

Styling

+ +

We have seen that different HTML elements are drawn differently. Some are displayed as blocks, others inline. Some add styling—<strong> makes its content bold, and <a> makes it blue and underlines it.

+ +

The way an <img> tag shows an image or an <a> tag causes a link to be followed when it is clicked is strongly tied to the element type. But we can change the styling associated with an element, such as the text color or underline. Here is an example that uses the style property:

+ +
<p><a href=".">Normal link</a></p>
+<p><a href="." style="color: green">Green link</a></p>
+ +

A style attribute may contain one or more declarations, which are a property (such as color) followed by a colon and a value (such as green). When there is more than one declaration, they must be separated by semicolons, as in "color: red; border: none".

+ +

A lot of aspects of the document can be influenced by styling. For example, the display property controls whether an element is displayed as a block or an inline element.

+ +
This text is displayed <strong>inline</strong>,
+<strong style="display: block">as a block</strong>, and
+<strong style="display: none">not at all</strong>.
+ +

The block tag will end up on its own line since block elements are not displayed inline with the text around them. The last tag is not displayed at all—display: none prevents an element from showing up on the screen. This is a way to hide elements. It is often preferable to removing them from the document entirely because it makes it easy to reveal them again later.

+ +

JavaScript code can directly manipulate the style of an element through the element’s style property. This property holds an object that has properties for all possible style properties. The values of these properties are strings, which we can write to in order to change a particular aspect of the element’s style.

+ +
<p id="para" style="color: purple">
+  Nice text
+</p>
+
+<script>
+  let para = document.getElementById("para");
+  console.log(para.style.color);
+  para.style.color = "magenta";
+</script>
+ +

Some style property names contain hyphens, such as font-family. Because such property names are awkward to work with in JavaScript (you’d have to say style["font-family"]), the property names in the style object for such properties have their hyphens removed and the letters after them capitalized (style.fontFamily).

+ +

Cascading styles

+ +

The styling system for HTML is called CSS, for Cascading Style Sheets. A style sheet is a set of rules for how to style elements in a document. It can be given inside a <style> tag.

+ +
<style>
+  strong {
+    font-style: italic;
+    color: gray;
+  }
+</style>
+<p>Now <strong>strong text</strong> is italic and gray.</p>
+ +

The cascading in the name refers to the fact that multiple such rules are combined to produce the final style for an element. In the example, the default styling for <strong> tags, which gives them font-weight: bold, is overlaid by the rule in the <style> tag, which adds font-style and color.

+ +

When multiple rules define a value for the same property, the most recently read rule gets a higher precedence and wins. So if the rule in the <style> tag included font-weight: normal, contradicting the default font-weight rule, the text would be normal, not bold. Styles in a style attribute applied directly to the node have the highest precedence and always win.

+ +

It is possible to target things other than tag names in CSS rules. A rule for .abc applies to all elements with "abc" in their class attribute. A rule for #xyz applies to the element with an id attribute of "xyz" (which should be unique within the document).

+ +
.subtle {
+  color: gray;
+  font-size: 80%;
+}
+#header {
+  background: blue;
+  color: white;
+}
+/* p elements with id main and with classes a and b */
+p#main.a.b {
+  margin-bottom: 20px;
+}
+ +

The precedence rule favoring the most recently defined rule applies only when the rules have the same specificity. A rule’s specificity is a measure of how precisely it describes matching elements, determined by the number and kind (tag, class, or ID) of element aspects it requires. For example, a rule that targets p.a is more specific than rules that target p or just .a and would thus take precedence over them.

+ +

The notation p > a {…} applies the given styles to all <a> tags that are direct children of <p> tags. Similarly, p a {…} applies to all <a> tags inside <p> tags, whether they are direct or indirect children.

+ +

Query selectors

+ +

We won’t be using style sheets all that much in this book. Understanding them is helpful when programming in the browser, but they are complicated enough to warrant a separate book.

+ +

The main reason I introduced selector syntax—the notation used in style sheets to determine which elements a set of styles apply to—is that we can use this same mini-language as an effective way to find DOM elements.

+ +

The querySelectorAll method, which is defined both on the document object and on element nodes, takes a selector string and returns a NodeList containing all the elements that it matches.

+ +
<p>And if you go chasing
+  <span class="animal">rabbits</span></p>
+<p>And you know you're going to fall</p>
+<p>Tell 'em a <span class="character">hookah smoking
+  <span class="animal">caterpillar</span></span></p>
+<p>Has given you the call</p>
+
+<script>
+  function count(selector) {
+    return document.querySelectorAll(selector).length;
+  }
+  console.log(count("p"));           // All <p> elements
+  // → 4
+  console.log(count(".animal"));     // Class animal
+  // → 2
+  console.log(count("p .animal"));   // Animal inside of <p>
+  // → 2
+  console.log(count("p > .animal")); // Direct child of <p>
+  // → 1
+</script>
+ +

Unlike methods such as getElementsByTagName, the object returned by querySelectorAll is not live. It won’t change when you change the document. It is still not a real array, though, so you still need to call Array.from if you want to treat it like one.

+ +

The querySelector method (without the All part) works in a similar way. This one is useful if you want a specific, single element. It will return only the first matching element or null when no element matches.

+ +

Positioning and animating

+ +

The position style property influences layout in a powerful way. By default it has a value of static, meaning the element sits in its normal place in the document. When it is set to relative, the element still takes up space in the document, but now the top and left style properties can be used to move it relative to that normal place. When position is set to absolute, the element is removed from the normal document flow—that is, it no longer takes up space and may overlap with other elements. Also, its top and left properties can be used to absolutely position it relative to the top-left corner of the nearest enclosing element whose position property isn’t static, or relative to the document if no such enclosing element exists.

+ +

We can use this to create an animation. The following document displays a picture of a cat that moves around in an ellipse:

+ +
<p style="text-align: center">
+  <img src="img/cat.png" style="position: relative">
+</p>
+<script>
+  let cat = document.querySelector("img");
+  let angle = Math.PI / 2;
+  function animate(time, lastTime) {
+    if (lastTime != null) {
+      angle += (time - lastTime) * 0.001;
+    }
+    cat.style.top = (Math.sin(angle) * 20) + "px";
+    cat.style.left = (Math.cos(angle) * 200) + "px";
+    requestAnimationFrame(newTime => animate(newTime, time));
+  }
+  requestAnimationFrame(animate);
+</script>
+ +

Our picture is centered on the page and given a position of relative. We’ll repeatedly update that picture’s top and left styles to move it.

+ +

The script uses requestAnimationFrame to schedule the animate function to run whenever the browser is ready to repaint the screen. The animate function itself again calls requestAnimationFrame to schedule the next update. When the browser window (or tab) is active, this will cause updates to happen at a rate of about 60 per second, which tends to produce a good-looking animation.

+ +

If we just updated the DOM in a loop, the page would freeze, and nothing would show up on the screen. Browsers do not update their display while a JavaScript program is running, nor do they allow any interaction with the page. This is why we need requestAnimationFrame—it lets the browser know that we are done for now, and it can go ahead and do the things that browsers do, such as updating the screen and responding to user actions.

+ +

The animation function is passed the current time as an argument. To ensure that the motion of the cat per millisecond is stable, it bases the speed at which the angle changes on the difference between the current time and the last time the function ran. If it just moved the angle by a fixed amount per step, the motion would stutter if, for example, another heavy task running on the same computer were to prevent the function from running for a fraction of a second.

+ +

Moving in circles is done using the trigonometry functions Math.cos and Math.sin. For those who aren’t familiar with these, I’ll briefly introduce them since we will occasionally use them in this book.

+ +

Math.cos and Math.sin are useful for finding points that lie on a circle around point (0,0) with a radius of one. Both functions interpret their argument as the position on this circle, with zero denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. Math.cos tells you the x-coordinate of the point that corresponds to the given position, and Math.sin yields the y-coordinate. Positions (or angles) greater than 2π or less than 0 are valid—the rotation repeats so that a+2π refers to the same angle as a.

+ +

This unit for measuring angles is called radians—a full circle is 2π radians, similar to how it is 360 degrees when measuring in degrees. The constant π is available as Math.PI in JavaScript.

Using cosine and sine to compute coordinates
+ +

The cat animation code keeps a counter, angle, for the current angle of the animation and increments it every time the animate function is called. It can then use this angle to compute the current position of the image element. The top style is computed with Math.sin and multiplied by 20, which is the vertical radius of our ellipse. The left style is based on Math.cos and multiplied by 200 so that the ellipse is much wider than it is high.

+ +

Note that styles usually need units. In this case, we have to append "px" to the number to tell the browser that we are counting in pixels (as opposed to centimeters, “ems”, or other units). This is easy to forget. Using numbers without units will result in your style being ignored—unless the number is 0, which always means the same thing, regardless of its unit.

+ +

Summary

+ +

JavaScript programs may inspect and interfere with the document that the browser is displaying through a data structure called the DOM. This data structure represents the browser’s model of the document, and a JavaScript program can modify it to change the visible document.

+ +

The DOM is organized like a tree, in which elements are arranged hierarchically according to the structure of the document. The objects representing elements have properties such as parentNode and childNodes, which can be used to navigate through this tree.

+ +

The way a document is displayed can be influenced by styling, both by attaching styles to nodes directly and by defining rules that match certain nodes. There are many different style properties, such as color or display. JavaScript code can manipulate an element’s style directly through its style property.

+ +

Exercises

+ +

Build a table

+ +

An HTML table is built with the following tag structure:

+ +
<table>
+  <tr>
+    <th>name</th>
+    <th>height</th>
+    <th>place</th>
+  </tr>
+  <tr>
+    <td>Kilimanjaro</td>
+    <td>5895</td>
+    <td>Tanzania</td>
+  </tr>
+</table>
+ +

For each row, the <table> tag contains a <tr> tag. Inside of these <tr> tags, we can put cell elements: either heading cells (<th>) or regular cells (<td>).

+ +

Given a data set of mountains, an array of objects with name, height, and place properties, generate the DOM structure for a table that enumerates the objects. It should have one column per key and one row per object, plus a header row with <th> elements at the top, listing the column names.

+ +

Write this so that the columns are automatically derived from the objects, by taking the property names of the first object in the data.

+ +

Add the resulting table to the element with an id attribute of "mountains" so that it becomes visible in the document.

+ +

Once you have this working, right-align cells that contain number values by setting their style.textAlign property to "right".

+ +
<h1>Mountains</h1>
+
+<div id="mountains"></div>
+
+<script>
+  const MOUNTAINS = [
+    {name: "Kilimanjaro", height: 5895, place: "Tanzania"},
+    {name: "Everest", height: 8848, place: "Nepal"},
+    {name: "Mount Fuji", height: 3776, place: "Japan"},
+    {name: "Vaalserberg", height: 323, place: "Netherlands"},
+    {name: "Denali", height: 6168, place: "United States"},
+    {name: "Popocatepetl", height: 5465, place: "Mexico"},
+    {name: "Mont Blanc", height: 4808, place: "Italy/France"}
+  ];
+
+  // Your code here
+</script>
+ +
+ +

You can use document.createElement to create new element nodes, document.createTextNode to create text nodes, and the appendChild method to put nodes into other nodes.

+ +

You’ll want to loop over the key names once to fill in the top row and then again for each object in the array to construct the data rows. To get an array of key names from the first object, Object.keys will be useful.

+ +

To add the table to the correct parent node, you can use document.getElementById or document.querySelector to find the node with the proper id attribute.

+ +
+ +

Elements by tag name

+ +

The document.getElementsByTagName method returns all child elements with a given tag name. Implement your own version of this as a function that takes a node and a string (the tag name) as arguments and returns an array containing all descendant element nodes with the given tag name.

+ +

To find the tag name of an element, use its nodeName property. But note that this will return the tag name in all uppercase. Use the toLowerCase or toUpperCase string methods to compensate for this.

+ +
<h1>Heading with a <span>span</span> element.</h1>
+<p>A paragraph with <span>one</span>, <span>two</span>
+  spans.</p>
+
+<script>
+  function byTagName(node, tagName) {
+    // Your code here.
+  }
+
+  console.log(byTagName(document.body, "h1").length);
+  // → 1
+  console.log(byTagName(document.body, "span").length);
+  // → 3
+  let para = document.querySelector("p");
+  console.log(byTagName(para, "span").length);
+  // → 2
+</script>
+ +
+ +

The solution is most easily expressed with a recursive function, similar to the talksAbout function defined earlier in this chapter.

+ +

You could call byTagname itself recursively, concatenating the resulting arrays to produce the output. Or you could create an inner function that calls itself recursively and that has access to an array binding defined in the outer function, to which it can add the matching elements it finds. Don’t forget to call the inner +function once from the outer function to start the process.

+ +

The recursive function must check the node type. Here we are interested only in node type 1 (Node.ELEMENT_NODE). For such nodes, we must loop over their children and, for each child, see whether the child matches the query while also doing a recursive call on it to inspect its own children.

+ +
+ +

The cat’s hat

+ +

Extend the cat animation defined earlier so that both the cat and his hat (<img src="img/hat.png">) orbit at opposite sides of the ellipse.

+ +

Or make the hat circle around the cat. Or alter the animation in some other interesting way.

+ +

To make positioning multiple objects easier, it is probably a good idea to switch to absolute positioning. This means that top and left are counted relative to the top left of the document. To avoid using negative coordinates, which would cause the image to move outside of the visible page, you can add a fixed number of pixels to the position values.

+ +
<style>body { min-height: 200px }</style>
+<img src="img/cat.png" id="cat" style="position: absolute">
+<img src="img/hat.png" id="hat" style="position: absolute">
+
+<script>
+  let cat = document.querySelector("#cat");
+  let hat = document.querySelector("#hat");
+
+  let angle = 0;
+  let lastTime = null;
+  function animate(time) {
+    if (lastTime != null) angle += (time - lastTime) * 0.001;
+    lastTime = time;
+    cat.style.top = (Math.sin(angle) * 40 + 40) + "px";
+    cat.style.left = (Math.cos(angle) * 200 + 230) + "px";
+
+    // Your extensions here.
+
+    requestAnimationFrame(animate);
+  }
+  requestAnimationFrame(animate);
+</script>
+ +
+ +

Math.cos and Math.sin measure angles in radians, where a full circle is 2π. For a given angle, you can get the opposite angle by adding half of this, which is Math.PI. This can be useful for putting the hat on the opposite side of the orbit.

+ +
+
diff --git a/docs/15_event.html b/docs/15_event.html new file mode 100644 index 000000000..9c829e6cc --- /dev/null +++ b/docs/15_event.html @@ -0,0 +1,582 @@ + + + + + Handling Events :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 15Handling Events

+ +
+ +

You have power over your mind—not outside events. Realize this, and you will find strength.

+ +
Marcus Aurelius, Meditations
+ +
Picture a Rube Goldberg machine
+ +

Some programs work with direct user input, such as mouse and keyboard actions. That kind of input isn’t available as a well-organized data structure—it comes in piece by piece, in real time, and the program is expected to respond to it as it happens.

+ +

Event handlers

+ +

Imagine an interface where the only way to find out whether a key on the keyboard is being pressed is to read the current state of that key. To be able to react to keypresses, you would have to constantly read the key’s state so that you’d catch it before it’s released again. It would be dangerous to perform other time-intensive computations since you might miss a keypress.

+ +

Some primitive machines do handle input like that. A step up from this would be for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there.

+ +

Of course, it has to remember to look at the queue, and to do it often, because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called polling. Most programmers prefer to avoid it.

+ +

A better mechanism is for the system to actively notify our code when an event occurs. Browsers do this by allowing us to register functions as handlers for specific events.

+ +
<p>Click this document to activate the handler.</p>
+<script>
+  window.addEventListener("click", () => {
+    console.log("You knocked?");
+  });
+</script>
+ +

The window binding refers to a built-in object provided by the browser. It represents the browser window that contains the document. Calling its addEventListener method registers the second argument to be called whenever the event described by its first argument occurs.

+ +

Events and DOM nodes

+ +

Each browser event handler is registered in a context. In the previous example we called addEventListener on the window object to register a handler for the whole window. Such a method can also be found on DOM elements and some other types of objects. Event listeners are called only when the event happens in the context of the object they are registered on.

+ +
<button>Click me</button>
+<p>No handler here.</p>
+<script>
+  let button = document.querySelector("button");
+  button.addEventListener("click", () => {
+    console.log("Button clicked.");
+  });
+</script>
+ +

That example attaches a handler to the button node. Clicks on the button cause that handler to run, but clicks on the rest of the document do not.

+ +

Giving a node an onclick attribute has a similar effect. This works for most types of events—you can attach a handler through the attribute whose name is the event name with on in front of it.

+ +

But a node can have only one onclick attribute, so you can register only one handler per node that way. The addEventListener method allows you to add any number of handlers so that it is safe to add handlers even if there is already another handler on the element.

+ +

The removeEventListener method, called with arguments similar to addEventListener, removes a handler.

+ +
<button>Act-once button</button>
+<script>
+  let button = document.querySelector("button");
+  function once() {
+    console.log("Done.");
+    button.removeEventListener("click", once);
+  }
+  button.addEventListener("click", once);
+</script>
+ +

The function given to removeEventListener has to be the same function value that was given to addEventListener. So, to unregister a handler, you’ll want to give the function a name (once, in the example) to be able to pass the same function value to both methods.

+ +

Event objects

+ +

Though we have ignored it so far, event handler functions are passed an argument: the event object. This object holds additional information about the event. For example, if we want to know which mouse button was pressed, we can look at the event object’s button property.

+ +
<button>Click me any way you want</button>
+<script>
+  let button = document.querySelector("button");
+  button.addEventListener("mousedown", event => {
+    if (event.button == 0) {
+      console.log("Left button");
+    } else if (event.button == 1) {
+      console.log("Middle button");
+    } else if (event.button == 2) {
+      console.log("Right button");
+    }
+  });
+</script>
+ +

The information stored in an event object differs per type of event. We’ll discuss different types later in the chapter. The object’s type property always holds a string identifying the event (such as "click" or "mousedown").

+ +

Propagation

+ +

For most event types, handlers registered on nodes with children will also receive events that happen in the children. If a button inside a paragraph is clicked, event handlers on the paragraph will also see the click event.

+ +

But if both the paragraph and the button have a handler, the more specific handler—the one on the button—gets to go first. The event is said to propagate outward, from the node where it happened to that node’s parent node and on to the root of the document. Finally, after all handlers registered on a specific node have had their turn, handlers registered on the whole window get a chance to respond to the event.

+ +

At any point, an event handler can call the stopPropagation method on the event object to prevent handlers further up from receiving the event. This can be useful when, for example, you have a button inside another clickable element and you don’t want clicks on the button to activate the outer element’s click behavior.

+ +

The following example registers "mousedown" handlers on both a button and the paragraph around it. When clicked with the right mouse button, the handler for the button calls stopPropagation, which will prevent the handler on the paragraph from running. When the button is clicked with another mouse button, both handlers will run.

+ +
<p>A paragraph with a <button>button</button>.</p>
+<script>
+  let para = document.querySelector("p");
+  let button = document.querySelector("button");
+  para.addEventListener("mousedown", () => {
+    console.log("Handler for paragraph.");
+  });
+  button.addEventListener("mousedown", event => {
+    console.log("Handler for button.");
+    if (event.button == 2) event.stopPropagation();
+  });
+</script>
+ +

Most event objects have a target property that refers to the node where they originated. You can use this property to ensure that you’re not accidentally handling something that propagated up from a node you do not want to handle.

+ +

It is also possible to use the target property to cast a wide net for a specific type of event. For example, if you have a node containing a long list of buttons, it may be more convenient to register a single click handler on the outer node and have it use the target property to figure out whether a button was clicked, rather than register individual handlers on all of the buttons.

+ +
<button>A</button>
+<button>B</button>
+<button>C</button>
+<script>
+  document.body.addEventListener("click", event => {
+    if (event.target.nodeName == "BUTTON") {
+      console.log("Clicked", event.target.textContent);
+    }
+  });
+</script>
+ +

Default actions

+ +

Many events have a default action associated with them. If you click a link, you will be taken to the link’s target. If you press the down arrow, the browser will scroll the page down. If you right-click, you’ll get a context menu. And so on.

+ +

For most types of events, the JavaScript event handlers are called before the default behavior takes place. If the handler doesn’t want this normal behavior to happen, typically because it has already taken care of handling the event, it can call the preventDefault method on the event object.

+ +

This can be used to implement your own keyboard shortcuts or context menu. It can also be used to obnoxiously interfere with the behavior that users expect. For example, here is a link that cannot be followed:

+ +
<a href="https://developer.mozilla.org/">MDN</a>
+<script>
+  let link = document.querySelector("a");
+  link.addEventListener("click", event => {
+    console.log("Nope.");
+    event.preventDefault();
+  });
+</script>
+ +

Try not to do such things unless you have a really good reason to. It’ll be unpleasant for people who use your page when expected behavior is broken.

+ +

Depending on the browser, some events can’t be intercepted at all. On Chrome, for example, the keyboard shortcut to close the current tab (control-W or command-W) cannot be handled by JavaScript.

+ +

Key events

+ +

When a key on the keyboard is pressed, your browser fires a "keydown" event. When it is released, you get a "keyup" event.

+ +
<p>This page turns violet when you hold the V key.</p>
+<script>
+  window.addEventListener("keydown", event => {
+    if (event.key == "v") {
+      document.body.style.background = "violet";
+    }
+  });
+  window.addEventListener("keyup", event => {
+    if (event.key == "v") {
+      document.body.style.background = "";
+    }
+  });
+</script>
+ +

Despite its name, "keydown" fires not only when the key is physically pushed down. When a key is pressed and held, the event fires again every time the key repeats. Sometimes you have to be careful about this. For example, if you add a button to the DOM when a key is pressed and remove it again when the key is released, you might accidentally add hundreds of buttons when the key is held down longer.

+ +

The example looked at the key property of the event object to see which key the event is about. This property holds a string that, for most keys, corresponds to the thing that pressing that key would type. For special keys such as enter, it holds a string that names the key ("Enter", in this case). If you hold shift while pressing a key, that might also influence the name of the key—"v" becomes "V", and "1" may become "!", if that is what pressing shift-1 produces on your keyboard.

+ +

Modifier keys such as shift, control, alt, and meta (command on Mac) generate key events just like normal keys. But when looking for key combinations, you can also find out whether these keys are held down by looking at the shiftKey, ctrlKey, altKey, and metaKey properties of keyboard and mouse events.

+ +
<p>Press Control-Space to continue.</p>
+<script>
+  window.addEventListener("keydown", event => {
+    if (event.key == " " && event.ctrlKey) {
+      console.log("Continuing!");
+    }
+  });
+</script>
+ +

The DOM node where a key event originates depends on the element that has focus when the key is pressed. Most nodes cannot have focus unless you give them a tabindex attribute, but things like links, buttons, and form fields can. We’ll come back to form fields in Chapter 18. When nothing in particular has focus, document.body acts as the target node of key events.

+ +

When the user is typing text, using key events to figure out what is being typed is problematic. Some platforms, most notably the virtual +keyboard on Android phones, don’t fire key events. But even when you have an old-fashioned keyboard, some types of text input don’t match key presses in a straightforward way, such as input method editor (IME) software used by people whose scripts don’t fit on a keyboard, where multiple key strokes are combined to create characters.

+ +

To notice when something was typed, elements that you can type into, such as the <input> and <textarea> tags, fire "input" events whenever the user changes their content. To get the actual content that was typed, it is best to directly read it from the focused field. Chapter 18 will show how.

+ +

Pointer events

+ +

There are currently two widely used ways to point at things on a screen: mice (including devices that act like mice, such as touchpads and trackballs) and touchscreens. These produce different kinds of events.

+ +

Mouse clicks

+ +

Pressing a mouse button causes a number of events to fire. The "mousedown" and "mouseup" events are similar to "keydown" and "keyup" and fire when the button is pressed and released. These happen on the DOM nodes that are immediately below the mouse pointer when the event occurs.

+ +

After the "mouseup" event, a "click" event fires on the most specific node that contained both the press and the release of the button. For example, if I press down the mouse button on one paragraph and then move the pointer to another paragraph and release the button, the "click" event will happen on the element that contains both those paragraphs.

+ +

If two clicks happen close together, a "dblclick" (double-click) event also fires, after the second click event.

+ +

To get precise information about the place where a mouse event happened, you can look at its clientX and clientY properties, which contain the event’s coordinates (in pixels) relative to the top-left corner of the window, or pageX and pageY, which are relative to the top-left corner of the whole document (which may be different when the window has been scrolled).

+ +

The following implements a primitive drawing program. Every time you click the document, it adds a dot under your mouse pointer. See Chapter 19 for a less primitive drawing program.

+ +
<style>
+  body {
+    height: 200px;
+    background: beige;
+  }
+  .dot {
+    height: 8px; width: 8px;
+    border-radius: 4px; /* rounds corners */
+    background: blue;
+    position: absolute;
+  }
+</style>
+<script>
+  window.addEventListener("click", event => {
+    let dot = document.createElement("div");
+    dot.className = "dot";
+    dot.style.left = (event.pageX - 4) + "px";
+    dot.style.top = (event.pageY - 4) + "px";
+    document.body.appendChild(dot);
+  });
+</script>
+ +

Mouse motion

+ +

Every time the mouse pointer moves, a "mousemove" event is fired. This event can be used to track the position of the mouse. A common situation in which this is useful is when implementing some form of mouse-dragging functionality.

+ +

As an example, the following program displays a bar and sets up event handlers so that dragging to the left or right on this bar makes it narrower or wider:

+ +
<p>Drag the bar to change its width:</p>
+<div style="background: orange; width: 60px; height: 20px">
+</div>
+<script>
+  let lastX; // Tracks the last observed mouse X position
+  let bar = document.querySelector("div");
+  bar.addEventListener("mousedown", event => {
+    if (event.button == 0) {
+      lastX = event.clientX;
+      window.addEventListener("mousemove", moved);
+      event.preventDefault(); // Prevent selection
+    }
+  });
+
+  function moved(event) {
+    if (event.buttons == 0) {
+      window.removeEventListener("mousemove", moved);
+    } else {
+      let dist = event.clientX - lastX;
+      let newWidth = Math.max(10, bar.offsetWidth + dist);
+      bar.style.width = newWidth + "px";
+      lastX = event.clientX;
+    }
+  }
+</script>
+ +

Note that the "mousemove" handler is registered on the whole window. Even if the mouse goes outside of the bar during resizing, as long as the button is held we still want to update its size.

+ +

We must stop resizing the bar when the mouse button is released. For that, we can use the buttons property (note the plural), which tells us about the buttons that are currently held down. When this is zero, no buttons are down. When buttons are held, its value is the sum of the codes for those buttons—the left button has code 1, the right button 2, and the middle one 4. That way, you can check whether a given button is pressed by taking the remainder of the value of buttons and its code.

+ +

Note that the order of these codes is different from the one used by button, where the middle button came before the right one. As mentioned, consistency isn’t really a strong point of the browser’s programming interface.

+ +

Touch events

+ +

The style of graphical browser that we use was designed with mouse interfaces in mind, at a time where touchscreens were rare. To make the Web “work” on early touchscreen phones, browsers for those devices pretended, to a certain extent, that touch events were mouse events. If you tap your screen, you’ll get "mousedown", "mouseup", and "click" events.

+ +

But this illusion isn’t very robust. A touchscreen works differently from a mouse: it doesn’t have multiple buttons, you can’t track the finger when it isn’t on the screen (to simulate "mousemove"), and it allows multiple fingers to be on the screen at the same time.

+ +

Mouse events cover touch interaction only in straightforward cases—if you add a "click" handler to a button, touch users will still be able to use it. But something like the resizeable bar in the previous example does not work on a touchscreen.

+ +

There are specific event types fired by touch interaction. When a finger starts touching the screen, you get a "touchstart" event. When it is moved while touching, "touchmove" events fire. Finally, when it stops touching the screen, you’ll see a "touchend" event.

+ +

Because many touchscreens can detect multiple fingers at the same time, these events don’t have a single set of coordinates associated with them. Rather, their event objects have a touches property, which holds an array-like object of points, each of which has its own clientX, clientY, pageX, and pageY properties.

+ +

You could do something like this to show red circles around every touching finger:

+ +
<style>
+  dot { position: absolute; display: block;
+        border: 2px solid red; border-radius: 50px;
+        height: 100px; width: 100px; }
+</style>
+<p>Touch this page</p>
+<script>
+  function update(event) {
+    for (let dot; dot = document.querySelector("dot");) {
+      dot.remove();
+    }
+    for (let i = 0; i < event.touches.length; i++) {
+      let {pageX, pageY} = event.touches[i];
+      let dot = document.createElement("dot");
+      dot.style.left = (pageX - 50) + "px";
+      dot.style.top = (pageY - 50) + "px";
+      document.body.appendChild(dot);
+    }
+  }
+  window.addEventListener("touchstart", update);
+  window.addEventListener("touchmove", update);
+  window.addEventListener("touchend", update);
+</script>
+ +

You’ll often want to call preventDefault in touch event handlers to override the browser’s default behavior (which may include scrolling the page on swiping) and to prevent the mouse events from being fired, for which you may also have a handler.

+ +

Scroll events

+ +

Whenever an element is scrolled, a "scroll" event is fired on it. This has various uses, such as knowing what the user is currently looking at (for disabling off-screen animations or sending spy reports to your evil headquarters) or showing some indication of progress (by highlighting part of a table of contents or showing a page number).

+ +

The following example draws a progress bar above the document and updates it to fill up as you scroll down:

+ +
<style>
+  #progress {
+    border-bottom: 2px solid blue;
+    width: 0;
+    position: fixed;
+    top: 0; left: 0;
+  }
+</style>
+<div id="progress"></div>
+<script>
+  // Create some content
+  document.body.appendChild(document.createTextNode(
+    "supercalifragilisticexpialidocious ".repeat(1000)));
+
+  let bar = document.querySelector("#progress");
+  window.addEventListener("scroll", () => {
+    let max = document.body.scrollHeight - innerHeight;
+    bar.style.width = `${(pageYOffset / max) * 100}%`;
+  });
+</script>
+ +

Giving an element a position of fixed acts much like an absolute position but also prevents it from scrolling along with the rest of the document. The effect is to make our progress bar stay at the top. Its width is changed to indicate the current progress. We use %, rather than px, as a unit when setting the width so that the element is sized relative to the page width.

+ +

The global innerHeight binding gives us the height of the window, which we have to subtract from the total scrollable height—you can’t keep scrolling when you hit the bottom of the document. There’s also an innerWidth for the window width. By dividing pageYOffset, the current scroll position, by the maximum scroll position and multiplying by 100, we get the percentage for the progress bar.

+ +

Calling preventDefault on a scroll event does not prevent the scrolling from happening. In fact, the event handler is called only after the scrolling takes place.

+ +

Focus events

+ +

When an element gains focus, the browser fires a "focus" event on it. When it loses focus, the element gets a "blur" event.

+ +

Unlike the events discussed earlier, these two events do not propagate. A handler on a parent element is not notified when a child element gains or loses focus.

+ +

The following example displays help text for the text field that currently has focus:

+ +
<p>Name: <input type="text" data-help="Your full name"></p>
+<p>Age: <input type="text" data-help="Your age in years"></p>
+<p id="help"></p>
+
+<script>
+  let help = document.querySelector("#help");
+  let fields = document.querySelectorAll("input");
+  for (let field of Array.from(fields)) {
+    field.addEventListener("focus", event => {
+      let text = event.target.getAttribute("data-help");
+      help.textContent = text;
+    });
+    field.addEventListener("blur", event => {
+      help.textContent = "";
+    });
+  }
+</script>
+ +

The window object will receive "focus" and "blur" events when the user moves from or to the browser tab or window in which the document is shown.

+ +

Load event

+ +

When a page finishes loading, the "load" event fires on the window and the document body objects. This is often used to schedule initialization actions that require the whole document to have been built. Remember that the content of <script> tags is run immediately when the tag is encountered. This may be too soon, for example when the script needs to do something with parts of the document that appear after the <script> tag.

+ +

Elements such as images and script tags that load an external file also have a "load" event that indicates the files they reference were loaded. Like the focus-related events, loading events do not propagate.

+ +

When a page is closed or navigated away from (for example, by following a link), a "beforeunload" event fires. The main use of this event is to prevent the user from accidentally losing work by closing a document. If you prevent the default behavior on this event and set the returnValue property on the event object to a string, the browser will show the user a dialog asking if they really want to leave the page. That dialog might include your string, but because some malicious sites try to use these dialogs to confuse people into staying on their page to look at dodgy weight loss ads, most browsers no longer display them.

+ +

Events and the event loop

+ +

In the context of the event loop, as discussed in Chapter 11, browser event handlers behave like other asynchronous notifications. They are scheduled when the event occurs but must wait for other scripts that are running to finish before they get a chance to run.

+ +

The fact that events can be processed only when nothing else is running means that, if the event loop is tied up with other work, any interaction with the page (which happens through events) will be delayed until there’s time to process it. So if you schedule too much work, either with long-running event handlers or with lots of short-running ones, the page will become slow and cumbersome to use.

+ +

For cases where you really do want to do some time-consuming thing in the background without freezing the page, browsers provide something called web workers. A worker is a JavaScript process that runs alongside the main script, on its own timeline.

+ +

Imagine that squaring a number is a heavy, long-running computation that we want to perform in a separate thread. We could write a file called code/squareworker.js that responds to messages by computing a square and sending a message back.

+ +
addEventListener("message", event => {
+  postMessage(event.data * event.data);
+});
+ +

To avoid the problems of having multiple threads touching the same data, workers do not share their global scope or any other data with the main script’s environment. Instead, you have to communicate with them by sending messages back and forth.

+ +

This code spawns a worker running that script, sends it a few messages, and outputs the responses.

+ +
let squareWorker = new Worker("code/squareworker.js");
+squareWorker.addEventListener("message", event => {
+  console.log("The worker responded:", event.data);
+});
+squareWorker.postMessage(10);
+squareWorker.postMessage(24);
+ +

The postMessage function sends a message, which will cause a "message" event to fire in the receiver. The script that created the worker sends and receives messages through the Worker object, whereas the worker talks to the script that created it by sending and listening directly on its global scope. Only values that can be represented as JSON can be sent as messages—the other side will receive a copy of them, rather than the value itself.

+ +

Timers

+ +

We saw the setTimeout function in Chapter 11. It schedules another function to be called later, after a given number of milliseconds.

+ +

Sometimes you need to cancel a function you have scheduled. This is done by storing the value returned by setTimeout and calling clearTimeout on it.

+ +
let bombTimer = setTimeout(() => {
+  console.log("BOOM!");
+}, 500);
+
+if (Math.random() < 0.5) { // 50% chance
+  console.log("Defused.");
+  clearTimeout(bombTimer);
+}
+ +

The cancelAnimationFrame function works in the same way as clearTimeout—calling it on a value returned by requestAnimationFrame will cancel that frame (assuming it hasn’t already been called).

+ +

A similar set of functions, setInterval and clearInterval, are used to set timers that should repeat every X milliseconds.

+ +
let ticks = 0;
+let clock = setInterval(() => {
+  console.log("tick", ticks++);
+  if (ticks == 10) {
+    clearInterval(clock);
+    console.log("stop.");
+  }
+}, 200);
+ +

Debouncing

+ +

Some types of events have the potential to fire rapidly, many times in a row (the "mousemove" and "scroll" events, for example). When handling such events, you must be careful not to do anything too time-consuming or your handler will take up so much time that interaction with the document starts to feel slow.

+ +

If you do need to do something nontrivial in such a handler, you can use setTimeout to make sure you are not doing it too often. This is usually called debouncing the event. There are several slightly different approaches to this.

+ +

In the first example, we want to react when the user has typed something, but we don’t want to do it immediately for every input event. When they are typing quickly, we just want to wait until a pause occurs. Instead of immediately performing an action in the event handler, we set a timeout. We also clear the previous timeout (if any) so that when events occur close together (closer than our timeout delay), the timeout from the previous event will be canceled.

+ +
<textarea>Type something here...</textarea>
+<script>
+  let textarea = document.querySelector("textarea");
+  let timeout;
+  textarea.addEventListener("input", () => {
+    clearTimeout(timeout);
+    timeout = setTimeout(() => console.log("Typed!"), 500);
+  });
+</script>
+ +

Giving an undefined value to clearTimeout or calling it on a timeout that has already fired has no effect. Thus, we don’t have to be careful about when to call it, and we simply do so for every event.

+ +

We can use a slightly different pattern if we want to space responses so that they’re separated by at least a certain length of time but want to fire them during a series of events, not just afterward. For example, we might want to respond to "mousemove" events by showing the current coordinates of the mouse but only every 250 milliseconds.

+ +
<script>
+  let scheduled = null;
+  window.addEventListener("mousemove", event => {
+    if (!scheduled) {
+      setTimeout(() => {
+        document.body.textContent =
+          `Mouse at ${scheduled.pageX}, ${scheduled.pageY}`;
+        scheduled = null;
+      }, 250);
+    }
+    scheduled = event;
+  });
+</script>
+ +

Summary

+ +

Event handlers make it possible to detect and react to events happening in our web page. The addEventListener method is used to register such a handler.

+ +

Each event has a type ("keydown", "focus", and so on) that identifies it. Most events are called on a specific DOM element and then propagate to that element’s ancestors, allowing handlers associated with those elements to handle them.

+ +

When an event handler is called, it is passed an event object with additional information about the event. This object also has methods that allow us to stop further propagation (stopPropagation) and prevent the browser’s default handling of the event (preventDefault).

+ +

Pressing a key fires "keydown" and "keyup" events. Pressing a mouse button fires "mousedown", "mouseup", and "click" events. Moving the mouse fires "mousemove" events. Touchscreen interaction will result in "touchstart", "touchmove", and "touchend" events.

+ +

Scrolling can be detected with the "scroll" event, and focus changes can be detected with the "focus" and "blur" events. When the document finishes loading, a "load" event fires on the window.

+ +

Exercises

+ +

Balloon

+ +

Write a page that displays a balloon (using the balloon emoji, 🎈). When you press the up arrow, it should inflate (grow) 10 percent, and when you press the down arrow, it should deflate (shrink) 10 percent.

+ +

You can control the size of text (emoji are text) by setting the font-size CSS property (style.fontSize) on its parent element. Remember to include a unit in the value—for example, pixels (10px).

+ +

The key names of the arrow keys are "ArrowUp" and "ArrowDown". Make sure the keys change only the balloon, without scrolling the page.

+ +

When that works, add a feature where, if you blow up the balloon past a certain size, it explodes. In this case, exploding means that it is replaced with an 💥 emoji, and the event handler is removed (so that you can’t inflate or deflate the explosion).

+ +
<p>🎈</p>
+
+<script>
+  // Your code here
+</script>
+ +
+ +

You’ll want to register a handler for the "keydown" event and look at event.key to figure out whether the up or down arrow key was pressed.

+ +

The current size can be kept in a binding so that you can base the new size on it. It’ll be helpful to define a function that updates the size—both the binding and the style of the balloon in the DOM—so that you can call it from your event handler, and possibly also once when starting, to set the initial size.

+ +

You can change the balloon to an explosion by replacing the text node with another one (using replaceChild) or by setting the textContent property of its parent node to a new string.

+ +
+ +

Mouse trail

+ +

In JavaScript’s early days, which was the high time of gaudy home +pages with lots of animated images, people came up with some truly inspiring ways to use the language.

+ +

One of these was the mouse trail—a series of elements that would follow the mouse pointer as you moved it across the page.

+ +

In this exercise, I want you to implement a mouse trail. Use absolutely positioned <div> elements with a fixed size and background color (refer to the code in the “Mouse Clicks” section for an example). Create a bunch of such elements and, when the mouse moves, display them in the wake of the mouse pointer.

+ +

There are various possible approaches here. You can make your solution as simple or as complex as you want. A simple solution to start with is to keep a fixed number of trail elements and cycle through them, moving the next one to the mouse’s current position every time a "mousemove" event occurs.

+ +
<style>
+  .trail { /* className for the trail elements */
+    position: absolute;
+    height: 6px; width: 6px;
+    border-radius: 3px;
+    background: teal;
+  }
+  body {
+    height: 300px;
+  }
+</style>
+
+<script>
+  // Your code here.
+</script>
+ +
+ +

Creating the elements is best done with a loop. Append them to the document to make them show up. To be able to access them later to change their position, you’ll want to store the elements in an array.

+ +

Cycling through them can be done by keeping a counter variable and adding 1 to it every time the "mousemove" event fires. The remainder operator (% elements.length) can then be used to get a valid array index to pick the element you want to position during a given event.

+ +

Another interesting effect can be achieved by modeling a simple physics system. Use the "mousemove" event only to update a pair of bindings that track the mouse position. Then use requestAnimationFrame to simulate the trailing elements being attracted to the position of the mouse pointer. At every animation step, update their position based on their position relative to the pointer (and, optionally, a speed that is stored for each element). Figuring out a good way to do this is up to you.

+ +
+ +

Tabs

+ +

Tabbed panels are widely used in user interfaces. They allow you to select an interface panel by choosing from a number of tabs “sticking out” above an element.

+ +

In this exercise you must implement a simple tabbed interface. Write a function, asTabs, that takes a DOM node and creates a tabbed interface showing the child elements of that node. It should insert a list of <button> elements at the top of the node, one for each child element, containing text retrieved from the data-tabname attribute of the child. All but one of the original children should be hidden (given a display style of none). The currently visible node can be selected by clicking the buttons.

+ +

When that works, extend it to style the button for the currently selected tab differently so that it is obvious which tab is selected.

+ +
<tab-panel>
+  <div data-tabname="one">Tab one</div>
+  <div data-tabname="two">Tab two</div>
+  <div data-tabname="three">Tab three</div>
+</tab-panel>
+<script>
+  function asTabs(node) {
+    // Your code here.
+  }
+  asTabs(document.querySelector("tab-panel"));
+</script>
+ +
+ +

One pitfall you might run into is that you can’t directly use the node’s childNodes property as a collection of tab nodes. For one thing, when you add the buttons, they will also become child nodes and end up in this object because it is a live data structure. For another, the text nodes created for the whitespace between the nodes are also in childNodes but should not get their own tabs. You can use children instead of childNodes to ignore text nodes.

+ +

You could start by building up an array of tabs so that you have easy access to them. To implement the styling of the buttons, you could store objects that contain both the tab panel and its button.

+ +

I recommend writing a separate function for changing tabs. You can either store the previously selected tab and change only the styles needed to hide that and show the new one, or you can just update the style of all tabs every time a new tab is selected.

+ +

You might want to call this function immediately to make the interface start with the first tab visible.

+ +
+
diff --git a/docs/16_game.html b/docs/16_game.html new file mode 100644 index 000000000..697a1a327 --- /dev/null +++ b/docs/16_game.html @@ -0,0 +1,795 @@ + + + + + Project: A Platform Game :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 16Project: A Platform Game

+ +
+ +

All reality is a game.

+ +
Iain Banks, The Player of Games
+ +
Picture of a game character jumping over lava
+ +

Much of my initial fascination with computers, like that of many nerdy kids, had to do with computer games. I was drawn into the tiny simulated worlds that I could manipulate and in which stories (sort of) unfolded—more, I suppose, because of the way I projected my imagination into them than because of the possibilities they actually offered.

+ +

I don’t wish a career in game programming on anyone. Much like the music industry, the discrepancy between the number of eager young people wanting to work in it and the actual demand for such people creates a rather unhealthy environment. But writing games for fun is amusing.

+ +

This chapter will walk through the implementation of a small platform game. Platform games (or “jump and run” games) are games that expect the player to move a figure through a world, which is usually two-dimensional and viewed from the side, while jumping over and onto things.

+ +

The game

+ +

Our game will be roughly based on Dark Blue by Thomas Palef. I chose that game because it is both entertaining and minimalist and because it can be built without too much code. It looks like this:

The game Dark Blue
+ +

The dark box represents the player, whose task is to collect the yellow boxes (coins) while avoiding the red stuff (lava). A level is completed when all coins have been collected.

+ +

The player can walk around with the left and right arrow keys and can jump with the up arrow. Jumping is a specialty of this game character. It can reach several times its own height and can change direction in midair. This may not be entirely realistic, but it helps give the player the feeling of being in direct control of the on-screen avatar.

+ +

The game consists of a static background, laid out like a grid, with the moving elements overlaid on that background. Each field on the grid is either empty, solid, or lava. The moving elements are the player, coins, and certain pieces of lava. The positions of these elements are not constrained to the grid—their coordinates may be fractional, allowing smooth motion.

+ +

The technology

+ +

We will use the browser DOM to display the game, and we’ll read user input by handling key events.

+ +

The screen- and keyboard-related code is only a small part of the work we need to do to build this game. Since everything looks like colored boxes, drawing is uncomplicated: we create DOM elements and use styling to give them a background color, size, and position.

+ +

We can represent the background as a table since it is an unchanging grid of squares. The free-moving elements can be overlaid using absolutely positioned elements.

+ +

In games and other programs that should animate graphics and respond to user input without noticeable delay, efficiency is important. Although the DOM was not originally designed for high-performance graphics, it is actually better at this than you would expect. You saw some animations in Chapter 14. On a modern machine, a simple game like this performs well, even if we don’t worry about optimization very much.

+ +

In the next chapter, we will explore another browser technology, the <canvas> tag, which provides a more traditional way to draw graphics, working in terms of shapes and pixels rather than DOM elements.

+ +

Levels

+ +

We’ll want a human-readable, human-editable way to specify levels. Since it is okay for everything to start out on a grid, we could use big strings in which each character represents an element—either a part of the background grid or a moving element.

+ +

The plan for a small level might look like this:

+ +
let simpleLevelPlan = `
+......................
+..#................#..
+..#..............=.#..
+..#.........o.o....#..
+..#.@......#####...#..
+..#####............#..
+......#++++++++++++#..
+......##############..
+......................`;
+ +

Periods are empty space, hash (#) characters are walls, and plus signs are lava. The player’s starting position is the at sign (@). Every O character is a coin, and the equal sign (=) at the top is a block of lava that moves back and forth horizontally.

+ +

We’ll support two additional kinds of moving lava: the pipe character (|) creates vertically moving blobs, and v indicates dripping lava—vertically moving lava that doesn’t bounce back and forth but only moves down, jumping back to its start position when it hits the floor.

+ +

A whole game consists of multiple levels that the player must complete. A level is completed when all coins have been collected. If the player touches lava, the current level is restored to its starting position, and the player may try again.

+ +

Reading a level

+ +

The following class stores a level object. Its argument should be the string that defines the level.

+ +
class Level {
+  constructor(plan) {
+    let rows = plan.trim().split("\n").map(l => [...l]);
+    this.height = rows.length;
+    this.width = rows[0].length;
+    this.startActors = [];
+
+    this.rows = rows.map((row, y) => {
+      return row.map((ch, x) => {
+        let type = levelChars[ch];
+        if (typeof type == "string") return type;
+        this.startActors.push(
+          type.create(new Vec(x, y), ch));
+        return "empty";
+      });
+    });
+  }
+}
+ +

The trim method is used to remove whitespace at the start and end of the plan string. This allows our example plan to start with a newline so that all the lines are directly below each other. The remaining string is split on newline characters, and each line is spread into an array, producing arrays of characters.

+ +

So rows holds an array of arrays of characters, the rows of the plan. We can derive the level’s width and height from these. But we must still separate the moving elements from the background grid. We’ll call moving elements actors. They’ll be stored in an array of objects. The background will be an array of arrays of strings, holding field types such as "empty", "wall", or "lava".

+ +

To create these arrays, we map over the rows and then over their content. Remember that map passes the array index as a second argument to the mapping function, which tells us the x- and y-coordinates of a given character. Positions in the game will be stored as pairs of coordinates, with the top left being 0,0 and each background square being 1 unit high and wide.

+ +

To interpret the characters in the plan, the Level constructor uses the levelChars object, which maps background elements to strings and actor characters to classes. When type is an actor class, its static create method is used to create an object, which is added to startActors, and the mapping function returns "empty" for this background square.

+ +

The position of the actor is stored as a Vec object. This is a two-dimensional vector, an object with x and y properties, as seen in the exercises of Chapter 6.

+ +

As the game runs, actors will end up in different places or even disappear entirely (as coins do when collected). We’ll use a State class to track the state of a running game.

+ +
class State {
+  constructor(level, actors, status) {
+    this.level = level;
+    this.actors = actors;
+    this.status = status;
+  }
+
+  static start(level) {
+    return new State(level, level.startActors, "playing");
+  }
+
+  get player() {
+    return this.actors.find(a => a.type == "player");
+  }
+}
+ +

The status property will switch to "lost" or "won" when the game has ended.

+ +

This is again a persistent data structure—updating the game state creates a new state and leaves the old one intact.

+ +

Actors

+ +

Actor objects represent the current position and state of a given moving element in our game. All actor objects conform to the same interface. Their pos property holds the coordinates of the element’s top-left corner, and their size property holds its size.

+ +

Then they have an update method, which is used to compute their new state and position after a given time step. It simulates the thing the actor does—moving in response to the arrow keys for the player and bouncing back and forth for the lava—and returns a new, updated actor object.

+ +

A type property contains a string that identifies the type of the actor—"player", "coin", or "lava". This is useful when drawing the game—the look of the rectangle drawn for an actor is based on its type.

+ +

Actor classes have a static create method that is used by the Level constructor to create an actor from a character in the level plan. It is given the coordinates of the character and the character itself, which is needed because the Lava class handles several different characters.

+ +

This is the Vec class that we’ll use for our two-dimensional values, such as the position and size of actors.

+ +
class Vec {
+  constructor(x, y) {
+    this.x = x; this.y = y;
+  }
+  plus(other) {
+    return new Vec(this.x + other.x, this.y + other.y);
+  }
+  times(factor) {
+    return new Vec(this.x * factor, this.y * factor);
+  }
+}
+ +

The times method scales a vector by a given number. It will be useful when we need to multiply a speed vector by a time interval to get the distance traveled during that time.

+ +

The different types of actors get their own classes since their behavior is very different. Let’s define these classes. We’ll get to their update methods later.

+ +

The player class has a property speed that stores its current speed to simulate momentum and gravity.

+ +
class Player {
+  constructor(pos, speed) {
+    this.pos = pos;
+    this.speed = speed;
+  }
+
+  get type() { return "player"; }
+
+  static create(pos) {
+    return new Player(pos.plus(new Vec(0, -0.5)),
+                      new Vec(0, 0));
+  }
+}
+
+Player.prototype.size = new Vec(0.8, 1.5);
+ +

Because a player is one-and-a-half squares high, its initial position is set to be half a square above the position where the @ character appeared. This way, its bottom aligns with the bottom of the square it appeared in.

+ +

The size property is the same for all instances of Player, so we store it on the prototype rather than on the instances themselves. We could have used a getter like type, but that would create and return a new Vec object every time the property is read, which would be wasteful. (Strings, being immutable, don’t have to be re-created every time they are evaluated.)

+ +

When constructing a Lava actor, we need to initialize the object differently depending on the character it is based on. Dynamic lava moves along at its current speed until it hits an obstacle. At that point, if it has a reset property, it will jump back to its start position (dripping). If it does not, it will invert its speed and continue in the other direction (bouncing).

+ +

The create method looks at the character that the Level constructor passes and creates the appropriate lava actor.

+ +
class Lava {
+  constructor(pos, speed, reset) {
+    this.pos = pos;
+    this.speed = speed;
+    this.reset = reset;
+  }
+
+  get type() { return "lava"; }
+
+  static create(pos, ch) {
+    if (ch == "=") {
+      return new Lava(pos, new Vec(2, 0));
+    } else if (ch == "|") {
+      return new Lava(pos, new Vec(0, 2));
+    } else if (ch == "v") {
+      return new Lava(pos, new Vec(0, 3), pos);
+    }
+  }
+}
+
+Lava.prototype.size = new Vec(1, 1);
+ +

Coin actors are relatively simple. They mostly just sit in their place. But to liven up the game a little, they are given a “wobble”, a slight vertical back-and-forth motion. To track this, a coin object stores a base position as well as a wobble property that tracks the phase of the bouncing motion. Together, these determine the coin’s actual position (stored in the pos property).

+ +
class Coin {
+  constructor(pos, basePos, wobble) {
+    this.pos = pos;
+    this.basePos = basePos;
+    this.wobble = wobble;
+  }
+
+  get type() { return "coin"; }
+
+  static create(pos) {
+    let basePos = pos.plus(new Vec(0.2, 0.1));
+    return new Coin(basePos, basePos,
+                    Math.random() * Math.PI * 2);
+  }
+}
+
+Coin.prototype.size = new Vec(0.6, 0.6);
+ +

In Chapter 14, we saw that Math.sin gives us the y-coordinate of a point on a circle. That coordinate goes back and forth in a smooth waveform as we move along the circle, which makes the sine function useful for modeling a wavy motion.

+ +

To avoid a situation where all coins move up and down synchronously, the starting phase of each coin is randomized. The phase of Math.sin’s wave, the width of a wave it produces, is 2π. We multiply the value returned by Math.random by that number to give the coin a random starting position on the wave.

+ +

We can now define the levelChars object that maps plan characters to either background grid types or actor classes.

+ +
const levelChars = {
+  ".": "empty", "#": "wall", "+": "lava",
+  "@": Player, "o": Coin,
+  "=": Lava, "|": Lava, "v": Lava
+};
+ +

That gives us all the parts needed to create a Level instance.

+ +
let simpleLevel = new Level(simpleLevelPlan);
+console.log(`${simpleLevel.width} by ${simpleLevel.height}`);
+// → 22 by 9
+ +

The task ahead is to display such levels on the screen and to model time and motion inside them.

+ +

Encapsulation as a burden

+ +

Most of the code in this chapter does not worry about encapsulation very much for two reasons. First, encapsulation takes extra effort. It makes programs bigger and requires additional concepts and interfaces to be introduced. Since there is only so much code you can throw at a reader before their eyes glaze over, I’ve made an effort to keep the program small.

+ +

Second, the various elements in this game are so closely tied together that if the behavior of one of them changed, it is unlikely that any of the others would be able to stay the same. Interfaces between the elements would end up encoding a lot of assumptions about the way the game works. This makes them a lot less effective—whenever you change one part of the system, you still have to worry about the way it impacts the other parts because their interfaces wouldn’t cover the new situation.

+ +

Some cutting points in a system lend themselves well to separation through rigorous interfaces, but others don’t. Trying to encapsulate something that isn’t a suitable boundary is a sure way to waste a lot of energy. When you are making this mistake, you’ll usually notice that your interfaces are getting awkwardly large and detailed and that they need to be changed often, as the program evolves.

+ +

There is one thing that we will encapsulate, and that is the drawing subsystem. The reason for this is that we’ll display the same game in a different way in the next chapter. By putting the drawing behind an interface, we can load the same game program there and plug in a new display module.

+ +

Drawing

+ +

The encapsulation of the drawing code is done by defining a display object, which displays a given level and state. The display type we define in this chapter is called DOMDisplay because it uses DOM elements to show the level.

+ +

We’ll be using a style sheet to set the actual colors and other fixed properties of the elements that make up the game. It would also be possible to directly assign to the elements’ style property when we create them, but that would produce more verbose programs.

+ +

The following helper function provides a succinct way to create an element and give it some attributes and child nodes:

+ +
function elt(name, attrs, ...children) {
+  let dom = document.createElement(name);
+  for (let attr of Object.keys(attrs)) {
+    dom.setAttribute(attr, attrs[attr]);
+  }
+  for (let child of children) {
+    dom.appendChild(child);
+  }
+  return dom;
+}
+ +

A display is created by giving it a parent element to which it should append itself and a level object.

+ +
class DOMDisplay {
+  constructor(parent, level) {
+    this.dom = elt("div", {class: "game"}, drawGrid(level));
+    this.actorLayer = null;
+    parent.appendChild(this.dom);
+  }
+
+  clear() { this.dom.remove(); }
+}
+ +

The level’s background grid, which never changes, is drawn once. Actors are redrawn every time the display is updated with a given state. The actorLayer property will be used to track the element that holds the actors so that they can be easily removed and replaced.

+ +

Our coordinates and sizes are tracked in grid units, where a size or distance of 1 means one grid block. When setting pixel sizes, we will have to scale these coordinates up—everything in the game would be ridiculously small at a single pixel per square. The scale constant gives the number of pixels that a single unit takes up on the screen.

+ +
const scale = 20;
+
+function drawGrid(level) {
+  return elt("table", {
+    class: "background",
+    style: `width: ${level.width * scale}px`
+  }, ...level.rows.map(row =>
+    elt("tr", {style: `height: ${scale}px`},
+        ...row.map(type => elt("td", {class: type})))
+  ));
+}
+ +

As mentioned, the background is drawn as a <table> element. This nicely corresponds to the structure of the rows property of the level—each row of the grid is turned into a table row (<tr> element). The strings in the grid are used as class names for the table cell (<td>) elements. The spread (triple dot) operator is used to pass arrays of child nodes to elt as separate arguments.

+ +

The following CSS makes the table look like the background we want:

+ +
.background    { background: rgb(52, 166, 251);
+                 table-layout: fixed;
+                 border-spacing: 0;              }
+.background td { padding: 0;                     }
+.lava          { background: rgb(255, 100, 100); }
+.wall          { background: white;              }
+ +

Some of these (table-layout, border-spacing, and padding) are used to suppress unwanted default behavior. We don’t want the layout of the table to depend upon the contents of its cells, and we don’t want space between the table cells or padding inside them.

+ +

The background rule sets the background color. CSS allows colors to be specified both as words (white) or with a format such as rgb(R, G, B), where the red, green, and blue components of the color are separated into three numbers from 0 to 255. So, in rgb(52, 166, 251), the red component is 52, green is 166, and blue is 251. Since the blue component is the largest, the resulting color will be bluish. You can see that in the .lava rule, the first number (red) is the largest.

+ +

We draw each actor by creating a DOM element for it and setting that element’s position and size based on the actor’s properties. The values have to be multiplied by scale to go from game units to pixels.

+ +
function drawActors(actors) {
+  return elt("div", {}, ...actors.map(actor => {
+    let rect = elt("div", {class: `actor ${actor.type}`});
+    rect.style.width = `${actor.size.x * scale}px`;
+    rect.style.height = `${actor.size.y * scale}px`;
+    rect.style.left = `${actor.pos.x * scale}px`;
+    rect.style.top = `${actor.pos.y * scale}px`;
+    return rect;
+  }));
+}
+ +

To give an element more than one class, we separate the class names by spaces. In the CSS code shown next, the actor class gives the actors their absolute position. Their type name is used as an extra class to give them a color. We don’t have to define the lava class again because we’re reusing the class for the lava grid squares we defined earlier.

+ +
.actor  { position: absolute;            }
+.coin   { background: rgb(241, 229, 89); }
+.player { background: rgb(64, 64, 64);   }
+ +

The syncState method is used to make the display show a given state. It first removes the old actor graphics, if any, and then redraws the actors in their new positions. It may be tempting to try to reuse the DOM elements for actors, but to make that work, we would need a lot of additional bookkeeping to associate actors with DOM elements and to make sure we remove elements when their actors vanish. Since there will typically be only a handful of actors in the game, redrawing all of them is not expensive.

+ +
DOMDisplay.prototype.syncState = function(state) {
+  if (this.actorLayer) this.actorLayer.remove();
+  this.actorLayer = drawActors(state.actors);
+  this.dom.appendChild(this.actorLayer);
+  this.dom.className = `game ${state.status}`;
+  this.scrollPlayerIntoView(state);
+};
+ +

By adding the level’s current status as a class name to the wrapper, we can style the player actor slightly differently when the game is won or lost by adding a CSS rule that takes effect only when the player has an ancestor element with a given class.

+ +
.lost .player {
+  background: rgb(160, 64, 64);
+}
+.won .player {
+  box-shadow: -4px -7px 8px white, 4px -7px 8px white;
+}
+ +

After touching lava, the player’s color turns dark red, suggesting scorching. When the last coin has been collected, we add two blurred white shadows—one to the top left and one to the top right—to create a white halo effect.

+ +

We can’t assume that the level always fits in the viewport—the element into which we draw the game. That is why the scrollPlayerIntoView call is needed. It ensures that if the level is protruding outside the viewport, we scroll that viewport to make sure the player is near its center. The following CSS gives the game’s wrapping DOM element a maximum size and ensures that anything that sticks out of the element’s box is not visible. We also give it a relative position so that the actors inside it are positioned relative to the level’s top-left corner.

+ +
.game {
+  overflow: hidden;
+  max-width: 600px;
+  max-height: 450px;
+  position: relative;
+}
+ +

In the scrollPlayerIntoView method, we find the player’s position and update the wrapping element’s scroll position. We change the scroll position by manipulating that element’s scrollLeft and scrollTop properties when the player is too close to the edge.

+ +
DOMDisplay.prototype.scrollPlayerIntoView = function(state) {
+  let width = this.dom.clientWidth;
+  let height = this.dom.clientHeight;
+  let margin = width / 3;
+
+  // The viewport
+  let left = this.dom.scrollLeft, right = left + width;
+  let top = this.dom.scrollTop, bottom = top + height;
+
+  let player = state.player;
+  let center = player.pos.plus(player.size.times(0.5))
+                         .times(scale);
+
+  if (center.x < left + margin) {
+    this.dom.scrollLeft = center.x - margin;
+  } else if (center.x > right - margin) {
+    this.dom.scrollLeft = center.x + margin - width;
+  }
+  if (center.y < top + margin) {
+    this.dom.scrollTop = center.y - margin;
+  } else if (center.y > bottom - margin) {
+    this.dom.scrollTop = center.y + margin - height;
+  }
+};
+ +

The way the player’s center is found shows how the methods on our Vec type allow computations with objects to be written in a relatively readable way. To find the actor’s center, we add its position (its top-left corner) and half its size. That is the center in level coordinates, but we need it in pixel coordinates, so we then multiply the resulting vector by our display scale.

+ +

Next, a series of checks verifies that the player position isn’t outside of the allowed range. Note that sometimes this will set nonsense scroll coordinates that are below zero or beyond the element’s scrollable area. This is okay—the DOM will constrain them to acceptable values. Setting scrollLeft to -10 will cause it to become 0.

+ +

It would have been slightly simpler to always try to scroll the player to the center of the viewport. But this creates a rather jarring effect. As you are jumping, the view will constantly shift up and down. It is more pleasant to have a “neutral” area in the middle of the screen where you can move around without causing any scrolling.

+ +

We are now able to display our tiny level.

+ +
<link rel="stylesheet" href="css/game.css">
+
+<script>
+  let simpleLevel = new Level(simpleLevelPlan);
+  let display = new DOMDisplay(document.body, simpleLevel);
+  display.syncState(State.start(simpleLevel));
+</script>
+ +

The <link> tag, when used with rel="stylesheet", is a way to load a CSS file into a page. The file game.css contains the styles necessary for our game.

+ +

Motion and collision

+ +

Now we’re at the point where we can start adding motion—the most interesting aspect of the game. The basic approach, taken by most games like this, is to split time into small steps and, for each step, move the actors by a distance corresponding to their speed multiplied by the size of the time step. We’ll measure time in seconds, so speeds are expressed in units per second.

+ +

Moving things is easy. The difficult part is dealing with the interactions between the elements. When the player hits a wall or floor, they should not simply move through it. The game must notice when a given motion causes an object to hit another object and respond accordingly. For walls, the motion must be stopped. When hitting a coin, it must be collected. When touching lava, the game should be lost.

+ +

Solving this for the general case is a big task. You can find libraries, usually called physics engines, that simulate interaction between physical objects in two or three dimensions. We’ll take a more modest approach in this chapter, handling only collisions between rectangular objects and handling them in a rather simplistic way.

+ +

Before moving the player or a block of lava, we test whether the motion would take it inside of a wall. If it does, we simply cancel the motion altogether. The response to such a collision depends on the type of actor—the player will stop, whereas a lava block will bounce back.

+ +

This approach requires our time steps to be rather small since it will cause motion to stop before the objects actually touch. If the time steps (and thus the motion steps) are too big, the player would end up hovering a noticeable distance above the ground. Another approach, arguably better but more complicated, would be to find the exact collision spot and move there. We will take the simple approach and hide its problems by ensuring the animation proceeds in small steps.

+ +

This method tells us whether a rectangle (specified by a position and a size) touches a grid element of the given type.

+ +
Level.prototype.touches = function(pos, size, type) {
+  var xStart = Math.floor(pos.x);
+  var xEnd = Math.ceil(pos.x + size.x);
+  var yStart = Math.floor(pos.y);
+  var yEnd = Math.ceil(pos.y + size.y);
+
+  for (var y = yStart; y < yEnd; y++) {
+    for (var x = xStart; x < xEnd; x++) {
+      let isOutside = x < 0 || x >= this.width ||
+                      y < 0 || y >= this.height;
+      let here = isOutside ? "wall" : this.rows[y][x];
+      if (here == type) return true;
+    }
+  }
+  return false;
+};
+ +

The method computes the set of grid squares that the body overlaps with by using Math.floor and Math.ceil on its coordinates. Remember that grid squares are 1 by 1 units in size. By rounding the sides of a box up and down, we get the range of background squares that the box touches.

Finding collisions on a grid
+ +

We loop over the block of grid squares found by rounding the coordinates and return true when a matching square is found. Squares outside of the level are always treated as "wall" to ensure that the player can’t leave the world and that we won’t accidentally try to read outside of the bounds of our rows array.

+ +

The state update method uses touches to figure out whether the player is touching lava.

+ +
State.prototype.update = function(time, keys) {
+  let actors = this.actors
+    .map(actor => actor.update(time, this, keys));
+  let newState = new State(this.level, actors, this.status);
+
+  if (newState.status != "playing") return newState;
+
+  let player = newState.player;
+  if (this.level.touches(player.pos, player.size, "lava")) {
+    return new State(this.level, actors, "lost");
+  }
+
+  for (let actor of actors) {
+    if (actor != player && overlap(actor, player)) {
+      newState = actor.collide(newState);
+    }
+  }
+  return newState;
+};
+ +

The method is passed a time step and a data structure that tells it which keys are being held down. The first thing it does is call the update method on all actors, producing an array of updated actors. The actors also get the time step, the keys, and the state, so that they can base their update on those. Only the player will actually read keys, since that’s the only actor that’s controlled by the keyboard.

+ +

If the game is already over, no further processing has to be done (the game can’t be won after being lost, or vice versa). Otherwise, the method tests whether the player is touching background lava. If so, the game is lost, and we’re done. Finally, if the game really is still going on, it sees whether any other actors overlap the player.

+ +

Overlap between actors is detected with the overlap function. It takes two actor objects and returns true when they touch—which is the case when they overlap both along the x-axis and along the y-axis.

+ +
function overlap(actor1, actor2) {
+  return actor1.pos.x + actor1.size.x > actor2.pos.x &&
+         actor1.pos.x < actor2.pos.x + actor2.size.x &&
+         actor1.pos.y + actor1.size.y > actor2.pos.y &&
+         actor1.pos.y < actor2.pos.y + actor2.size.y;
+}
+ +

If any actor does overlap, its collide method gets a chance to update the state. Touching a lava actor sets the game status to "lost". Coins vanish when you touch them and set the status to "won" when they are the last coin of the level.

+ +
Lava.prototype.collide = function(state) {
+  return new State(state.level, state.actors, "lost");
+};
+
+Coin.prototype.collide = function(state) {
+  let filtered = state.actors.filter(a => a != this);
+  let status = state.status;
+  if (!filtered.some(a => a.type == "coin")) status = "won";
+  return new State(state.level, filtered, status);
+};
+ +

Actor updates

+ +

Actor objects’ update methods take as arguments the time step, the state object, and a keys object. The one for the Lava actor type ignores the keys object.

+ +
Lava.prototype.update = function(time, state) {
+  let newPos = this.pos.plus(this.speed.times(time));
+  if (!state.level.touches(newPos, this.size, "wall")) {
+    return new Lava(newPos, this.speed, this.reset);
+  } else if (this.reset) {
+    return new Lava(this.reset, this.speed, this.reset);
+  } else {
+    return new Lava(this.pos, this.speed.times(-1));
+  }
+};
+ +

This update method computes a new position by adding the product of the time step and the current speed to its old position. If no obstacle blocks that new position, it moves there. If there is an obstacle, the behavior depends on the type of the lava block—dripping lava has a reset position, to which it jumps back when it hits something. Bouncing lava inverts its speed by multiplying it by -1 so that it starts moving in the opposite direction.

+ +

Coins use their update method to wobble. They ignore collisions with the grid since they are simply wobbling around inside of their own square.

+ +
const wobbleSpeed = 8, wobbleDist = 0.07;
+
+Coin.prototype.update = function(time) {
+  let wobble = this.wobble + time * wobbleSpeed;
+  let wobblePos = Math.sin(wobble) * wobbleDist;
+  return new Coin(this.basePos.plus(new Vec(0, wobblePos)),
+                  this.basePos, wobble);
+};
+ +

The wobble property is incremented to track time and then used as an argument to Math.sin to find the new position on the wave. The coin’s current position is then computed from its base position and an offset based on this wave.

+ +

That leaves the player itself. Player motion is handled separately per axis because hitting the floor should not prevent horizontal motion, and hitting a wall should not stop falling or jumping motion.

+ +
const playerXSpeed = 7;
+const gravity = 30;
+const jumpSpeed = 17;
+
+Player.prototype.update = function(time, state, keys) {
+  let xSpeed = 0;
+  if (keys.ArrowLeft) xSpeed -= playerXSpeed;
+  if (keys.ArrowRight) xSpeed += playerXSpeed;
+  let pos = this.pos;
+  let movedX = pos.plus(new Vec(xSpeed * time, 0));
+  if (!state.level.touches(movedX, this.size, "wall")) {
+    pos = movedX;
+  }
+
+  let ySpeed = this.speed.y + time * gravity;
+  let movedY = pos.plus(new Vec(0, ySpeed * time));
+  if (!state.level.touches(movedY, this.size, "wall")) {
+    pos = movedY;
+  } else if (keys.ArrowUp && ySpeed > 0) {
+    ySpeed = -jumpSpeed;
+  } else {
+    ySpeed = 0;
+  }
+  return new Player(pos, new Vec(xSpeed, ySpeed));
+};
+ +

The horizontal motion is computed based on the state of the left and right arrow keys. When there’s no wall blocking the new position created by this motion, it is used. Otherwise, the old position is kept.

+ +

Vertical motion works in a similar way but has to simulate jumping and gravity. The player’s vertical speed (ySpeed) is first accelerated to account for gravity.

+ +

We check for walls again. If we don’t hit any, the new position is used. If there is a wall, there are two possible outcomes. When the up arrow is pressed and we are moving down (meaning the thing we hit is below us), the speed is set to a relatively large, negative value. This causes the player to jump. If that is not the case, the player simply bumped into something, and the speed is set to zero.

+ +

The gravity strength, jumping speed, and pretty much all other constants in this game have been set by trial and error. I tested values until I found a combination I liked.

+ +

Tracking keys

+ +

For a game like this, we do not want keys to take effect once per keypress. Rather, we want their effect (moving the player figure) to stay active as long as they are held.

+ +

We need to set up a key handler that stores the current state of the left, right, and up arrow keys. We will also want to call preventDefault for those keys so that they don’t end up scrolling the page.

+ +

The following function, when given an array of key names, will return an object that tracks the current position of those keys. It registers event handlers for "keydown" and "keyup" events and, when the key code in the event is present in the set of codes that it is tracking, updates the object.

+ +
function trackKeys(keys) {
+  let down = Object.create(null);
+  function track(event) {
+    if (keys.includes(event.key)) {
+      down[event.key] = event.type == "keydown";
+      event.preventDefault();
+    }
+  }
+  window.addEventListener("keydown", track);
+  window.addEventListener("keyup", track);
+  return down;
+}
+
+const arrowKeys =
+  trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]);
+ +

The same handler function is used for both event types. It looks at the event object’s type property to determine whether the key state should be updated to true ("keydown") or false ("keyup").

+ +

Running the game

+ +

The requestAnimationFrame function, which we saw in Chapter 14, provides a good way to animate a game. But its interface is quite primitive—using it requires us to track the time at which our function was called the last time around and call requestAnimationFrame again after every frame.

+ +

Let’s define a helper function that wraps those boring parts in a convenient interface and allows us to simply call runAnimation, giving it a function that expects a time difference as an argument and draws a single frame. When the frame function returns the value false, the animation stops.

+ +
function runAnimation(frameFunc) {
+  let lastTime = null;
+  function frame(time) {
+    if (lastTime != null) {
+      let timeStep = Math.min(time - lastTime, 100) / 1000;
+      if (frameFunc(timeStep) === false) return;
+    }
+    lastTime = time;
+    requestAnimationFrame(frame);
+  }
+  requestAnimationFrame(frame);
+}
+ +

I have set a maximum frame step of 100 milliseconds (one-tenth of a second). When the browser tab or window with our page is hidden, requestAnimationFrame calls will be suspended until the tab or window is shown again. In this case, the difference between lastTime and time will be the entire time in which the page was hidden. Advancing the game by that much in a single step would look silly and might cause weird side effects, such as the player falling through the floor.

+ +

The function also converts the time steps to seconds, which are an easier quantity to think about than milliseconds.

+ +

The runLevel function takes a Level object and a display constructor and returns a promise. It displays the level (in document.body) and lets the user play through it. When the level is finished (lost or won), runLevel waits one more second (to let the user see what happens) and then clears the display, stops the animation, and resolves the promise to the game’s end status.

+ +
function runLevel(level, Display) {
+  let display = new Display(document.body, level);
+  let state = State.start(level);
+  let ending = 1;
+  return new Promise(resolve => {
+    runAnimation(time => {
+      state = state.update(time, arrowKeys);
+      display.syncState(state);
+      if (state.status == "playing") {
+        return true;
+      } else if (ending > 0) {
+        ending -= time;
+        return true;
+      } else {
+        display.clear();
+        resolve(state.status);
+        return false;
+      }
+    });
+  });
+}
+ +

A game is a sequence of levels. Whenever the player dies, the current level is restarted. When a level is completed, we move on to the next level. This can be expressed by the following function, which takes an array of level plans (strings) and a display constructor:

+ +
async function runGame(plans, Display) {
+  for (let level = 0; level < plans.length;) {
+    let status = await runLevel(new Level(plans[level]),
+                                Display);
+    if (status == "won") level++;
+  }
+  console.log("You've won!");
+}
+ +

Because we made runLevel return a promise, runGame can be written using an async function, as shown in Chapter 11. It returns another promise, which resolves when the player finishes the game.

+ +

There is a set of level plans available in the GAME_LEVELS binding in this chapter’s sandbox. This page feeds them to runGame, starting an actual game.

+ +
<link rel="stylesheet" href="css/game.css">
+
+<body>
+  <script>
+    runGame(GAME_LEVELS, DOMDisplay);
+  </script>
+</body>
+ +

See if you can beat those. I had quite a lot of fun building them.

+ +

Exercises

+ +

Game over

+ +

It’s traditional for platform games to have the player start with a limited number of lives and subtract one life each time they die. When the player is out of lives, the game restarts from the beginning.

+ +

Adjust runGame to implement lives. Have the player start with three. Output the current number of lives (using console.log) every time a level starts.

+ +
<link rel="stylesheet" href="css/game.css">
+
+<body>
+<script>
+  // The old runGame function. Modify it...
+  async function runGame(plans, Display) {
+    for (let level = 0; level < plans.length;) {
+      let status = await runLevel(new Level(plans[level]),
+                                  Display);
+      if (status == "won") level++;
+    }
+    console.log("You've won!");
+  }
+  runGame(GAME_LEVELS, DOMDisplay);
+</script>
+</body>
+ +

Pausing the game

+ +

Make it possible to pause (suspend) and unpause the game by pressing the Esc key.

+ +

This can be done by changing the runLevel function to use another keyboard event handler and interrupting or resuming the animation whenever the Esc key is hit.

+ +

The runAnimation interface may not look like it is suitable for this at first glance, but it is if you rearrange the way runLevel calls it.

+ +

When you have that working, there is something else you could try. The way we have been registering keyboard event handlers is somewhat problematic. The arrowKeys object is currently a global binding, and its event handlers are kept around even when no game is running. You could say they leak out of our system. Extend trackKeys to provide a way to unregister its handlers and then change runLevel to register its handlers when it starts and unregister them again when it is finished.

+ +
<link rel="stylesheet" href="css/game.css">
+
+<body>
+<script>
+  // The old runLevel function. Modify this...
+  function runLevel(level, Display) {
+    let display = new Display(document.body, level);
+    let state = State.start(level);
+    let ending = 1;
+    return new Promise(resolve => {
+      runAnimation(time => {
+        state = state.update(time, arrowKeys);
+        display.syncState(state);
+        if (state.status == "playing") {
+          return true;
+        } else if (ending > 0) {
+          ending -= time;
+          return true;
+        } else {
+          display.clear();
+          resolve(state.status);
+          return false;
+        }
+      });
+    });
+  }
+  runGame(GAME_LEVELS, DOMDisplay);
+</script>
+</body>
+ +
+ +

An animation can be interrupted by returning false from the function given to runAnimation. It can be continued by calling runAnimation again.

+ +

So we need to communicate the fact that we are pausing the game to the function given to runAnimation. For that, you can use a binding that both the event handler and that function have access to.

+ +

When finding a way to unregister the handlers registered by trackKeys, remember that the exact same function value that was passed to addEventListener must be passed to removeEventListener to successfully remove a handler. Thus, the handler function value created in trackKeys must be available to the code that unregisters the handlers.

+ +

You can add a property to the object returned by trackKeys, containing either that function value or a method that handles the unregistering directly.

+ +
+ +

A monster

+ +

It is traditional for platform games to have enemies that you can jump on top of to defeat. This exercise asks you to add such an actor type to the game.

+ +

We’ll call it a monster. Monsters move only horizontally. You can make them move in the direction of the player, bounce back and forth like horizontal lava, or have any movement pattern you want. The class doesn’t have to handle falling, but it should make sure the monster doesn’t walk through walls.

+ +

When a monster touches the player, the effect depends on whether the player is jumping on top of them or not. You can approximate this by checking whether the player’s bottom is near the monster’s top. If this is the case, the monster disappears. If not, the game is lost.

+ +
<link rel="stylesheet" href="css/game.css">
+<style>.monster { background: purple }</style>
+
+<body>
+  <script>
+    // Complete the constructor, update, and collide methods
+    class Monster {
+      constructor(pos, /* ... */) {}
+
+      get type() { return "monster"; }
+
+      static create(pos) {
+        return new Monster(pos.plus(new Vec(0, -1)));
+      }
+
+      update(time, state) {}
+
+      collide(state) {}
+    }
+
+    Monster.prototype.size = new Vec(1.2, 2);
+
+    levelChars["M"] = Monster;
+
+    runLevel(new Level(`
+..................................
+.################################.
+.#..............................#.
+.#..............................#.
+.#..............................#.
+.#...........................o..#.
+.#..@...........................#.
+.##########..............########.
+..........#..o..o..o..o..#........
+..........#...........M..#........
+..........################........
+..................................
+`), DOMDisplay);
+  </script>
+</body>
+ +
+ +

If you want to implement a type of motion that is stateful, such as bouncing, make sure you store the necessary state in the actor object—include it as constructor argument and add it as a property.

+ +

Remember that update returns a new object, rather than changing the old one.

+ +

When handling collision, find the player in state.actors and compare its position to the monster’s position. To get the bottom of the player, you have to add its vertical size to its vertical position. The creation of an updated state will resemble either Coin’s collide method (removing the actor) or Lava’s (changing the status to "lost"), depending on the player position.

+ +
+
diff --git a/docs/17_canvas.html b/docs/17_canvas.html new file mode 100644 index 000000000..8e598a9c9 --- /dev/null +++ b/docs/17_canvas.html @@ -0,0 +1,746 @@ + + + + + Drawing on Canvas :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 17Drawing on Canvas

+ +
+ +

Drawing is deception.

+ +
M.C. Escher, cited by Bruno Ernst in The Magic Mirror of M.C. Escher
+ +
Picture of a robot arm drawing on paper
+ +

Browsers give us several ways to display graphics. The simplest way is to use styles to position and color regular DOM elements. This can get you quite far, as the game in the previous chapter showed. By adding partially transparent background images to the nodes, we can make them look exactly the way we want. It is even possible to rotate or skew nodes with the transform style.

+ +

But we’d be using the DOM for something that it wasn’t originally designed for. Some tasks, such as drawing a line between arbitrary points, are extremely awkward to do with regular HTML elements.

+ +

There are two alternatives. The first is DOM-based but utilizes Scalable Vector Graphics (SVG), rather than HTML. Think of SVG as a document-markup dialect that focuses on shapes rather than text. You can embed an SVG document directly in an HTML document or include it with an <img> tag.

+ +

The second alternative is called a canvas. A canvas is a single DOM element that encapsulates a picture. It provides a programming interface for drawing shapes onto the space taken up by the node. The main difference between a canvas and an SVG picture is that in SVG the original description of the shapes is preserved so that they can be moved or resized at any time. A canvas, on the other hand, converts the shapes to pixels (colored dots on a raster) as soon as they are drawn and does not remember what these pixels represent. The only way to move a shape on a canvas is to clear the canvas (or the part of the canvas around the shape) and redraw it with the shape in a new position.

+ +

SVG

+ +

This book will not go into SVG in detail, but I will briefly explain how it works. At the end of the chapter, I’ll come back to the trade-offs that you must consider when deciding which drawing mechanism is appropriate for a given application.

+ +

This is an HTML document with a simple SVG picture in it:

+ +
<p>Normal HTML here.</p>
+<svg xmlns="http://www.w3.org/2000/svg">
+  <circle r="50" cx="50" cy="50" fill="red"/>
+  <rect x="120" y="5" width="90" height="90"
+        stroke="blue" fill="none"/>
+</svg>
+ +

The xmlns attribute changes an element (and its children) to a different XML namespace. This namespace, identified by a URL, specifies the dialect that we are currently speaking. The <circle> and <rect> tags, which do not exist in HTML, do have a meaning in SVG—they draw shapes using the style and position specified by their attributes.

+ +

These tags create DOM elements, just like HTML tags, that scripts can interact with. For example, this changes the <circle> element to be colored cyan instead:

+ +
let circle = document.querySelector("circle");
+circle.setAttribute("fill", "cyan");
+ +

The canvas element

+ +

Canvas graphics can be drawn onto a <canvas> element. You can give such an element width and height attributes to determine its size in pixels.

+ +

A new canvas is empty, meaning it is entirely transparent and thus shows up as empty space in the document.

+ +

The <canvas> tag is intended to allow different styles of drawing. To get access to an actual drawing interface, we first need to create a context, an object whose methods provide the drawing interface. There are currently two widely supported drawing styles: "2d" for two-dimensional graphics and "webgl" for three-dimensional graphics through the OpenGL interface.

+ +

This book won’t discuss WebGL—we’ll stick to two dimensions. But if you are interested in three-dimensional graphics, I do encourage you to look into WebGL. It provides a direct interface to graphics hardware and allows you to render even complicated scenes efficiently, using JavaScript.

+ +

You create a context with the getContext method on the <canvas> DOM element.

+ +
<p>Before canvas.</p>
+<canvas width="120" height="60"></canvas>
+<p>After canvas.</p>
+<script>
+  let canvas = document.querySelector("canvas");
+  let context = canvas.getContext("2d");
+  context.fillStyle = "red";
+  context.fillRect(10, 10, 100, 50);
+</script>
+ +

After creating the context object, the example draws a red rectangle 100 pixels wide and 50 pixels high, with its top-left corner at coordinates (10,10).

+ +

Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-axis goes down from there. So (10,10) is 10 pixels below and to the right of the top-left corner.

+ +

Lines and surfaces

+ +

In the canvas interface, a shape can be filled, meaning its area is given a certain color or pattern, or it can be stroked, which means a line is drawn along its edge. The same terminology is used by SVG.

+ +

The fillRect method fills a rectangle. It takes first the x- and y-coordinates of the rectangle’s top-left corner, then its width, and then its height. A similar method, strokeRect, draws the outline of a rectangle.

+ +

Neither method takes any further parameters. The color of the fill, thickness of the stroke, and so on, are not determined by an argument to the method (as you might reasonably expect) but rather by properties of the context object.

+ +

The fillStyle property controls the way shapes are filled. It can be set to a string that specifies a color, using the color notation used by CSS.

+ +

The strokeStyle property works similarly but determines the color used for a stroked line. The width of that line is determined by the lineWidth property, which may contain any positive number.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.strokeStyle = "blue";
+  cx.strokeRect(5, 5, 50, 50);
+  cx.lineWidth = 5;
+  cx.strokeRect(135, 5, 50, 50);
+</script>
+ +

When no width or height attribute is specified, as in the example, a canvas element gets a default width of 300 pixels and height of 150 pixels.

+ +

Paths

+ +

A path is a sequence of lines. The 2D canvas interface takes a peculiar approach to describing such a path. It is done entirely through side effects. Paths are not values that can be stored and passed around. Instead, if you want to do something with a path, you make a sequence of method calls to describe its shape.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.beginPath();
+  for (let y = 10; y < 100; y += 10) {
+    cx.moveTo(10, y);
+    cx.lineTo(90, y);
+  }
+  cx.stroke();
+</script>
+ +

This example creates a path with a number of horizontal line segments and then strokes it using the stroke method. Each segment created with lineTo starts at the path’s current position. That position is usually the end of the last segment, unless moveTo was called. In that case, the next segment would start at the position passed to moveTo.

+ +

When filling a path (using the fill method), each shape is filled separately. A path can contain multiple shapes—each moveTo motion starts a new one. But the path needs to be closed (meaning its start and end are in the same position) before it can be filled. If the path is not already closed, a line is added from its end to its start, and the shape enclosed by the completed path is filled.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.beginPath();
+  cx.moveTo(50, 10);
+  cx.lineTo(10, 70);
+  cx.lineTo(90, 70);
+  cx.fill();
+</script>
+ +

This example draws a filled triangle. Note that only two of the triangle’s sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn’t be there when you stroke the path.

+ +

You could also use the closePath method to explicitly close a path by adding an actual line segment back to the path’s start. This segment is drawn when stroking the path.

+ +

Curves

+ +

A path may also contain curved lines. These are unfortunately a bit more involved to draw.

+ +

The quadraticCurveTo method draws a curve to a given point. To determine the curvature of the line, the method is given a control +point as well as a destination point. Imagine this control point as attracting the line, giving it its curve. The line won’t go through the control point, but its direction at the start and end points will be such that a straight line in that direction would point toward the control point. The following example illustrates this:

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.beginPath();
+  cx.moveTo(10, 90);
+  // control=(60,10) goal=(90,90)
+  cx.quadraticCurveTo(60, 10, 90, 90);
+  cx.lineTo(60, 10);
+  cx.closePath();
+  cx.stroke();
+</script>
+ +

We draw a quadratic curve from the left to the right, with (60,10) as control point, and then draw two line segments going through that control point and back to the start of the line. The result somewhat resembles a Star Trek insignia. You can see the effect of the control point: the lines leaving the lower corners start off in the direction of the control point and then curve toward their target.

+ +

The bezierCurveTo method draws a similar kind of curve. Instead of a single control point, this one has two—one for each of the line’s endpoints. Here is a similar sketch to illustrate the behavior of such a curve:

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.beginPath();
+  cx.moveTo(10, 90);
+  // control1=(10,10) control2=(90,10) goal=(50,90)
+  cx.bezierCurveTo(10, 10, 90, 10, 50, 90);
+  cx.lineTo(90, 10);
+  cx.lineTo(10, 10);
+  cx.closePath();
+  cx.stroke();
+</script>
+ +

The two control points specify the direction at both ends of the curve. The farther they are away from their corresponding point, the more the curve will “bulge” in that direction.

+ +

Such curves can be hard to work with—it’s not always clear how to find the control points that provide the shape you are looking for. Sometimes you can compute them, and sometimes you’ll just have to find a suitable value by trial and error.

+ +

The arc method is a way to draw a line that curves along the edge of a circle. It takes a pair of coordinates for the arc’s center, a radius, and then a start angle and end angle.

+ +

Those last two parameters make it possible to draw only part of the circle. The angles are measured in radians, not degrees. This means a full circle has an angle of 2π, or 2 * Math.PI, which is about 6.28. The angle starts counting at the point to the right of the circle’s center and goes clockwise from there. You can use a start of 0 and an end bigger than 2π (say, 7) to draw a full circle.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.beginPath();
+  // center=(50,50) radius=40 angle=0 to 7
+  cx.arc(50, 50, 40, 0, 7);
+  // center=(150,50) radius=40 angle=0 to ½π
+  cx.arc(150, 50, 40, 0, 0.5 * Math.PI);
+  cx.stroke();
+</script>
+ +

The resulting picture contains a line from the right of the full circle (first call to arc) to the right of the quarter-circle (second call). Like other path-drawing methods, a line drawn with arc is connected to the previous path segment. You can call moveTo or start a new path to avoid this.

+ +

Drawing a pie chart

+ +

Imagine you’ve just taken a job at EconomiCorp, Inc., and your first assignment is to draw a pie chart of its customer satisfaction survey results.

+ +

The results binding contains an array of objects that represent the survey responses.

+ +
const results = [
+  {name: "Satisfied", count: 1043, color: "lightblue"},
+  {name: "Neutral", count: 563, color: "lightgreen"},
+  {name: "Unsatisfied", count: 510, color: "pink"},
+  {name: "No comment", count: 175, color: "silver"}
+];
+ +

To draw a pie chart, we draw a number of pie slices, each made up of an arc and a pair of lines to the center of that arc. We can compute the angle taken up by each arc by dividing a full circle (2π) by the total number of responses and then multiplying that number (the angle per response) by the number of people who picked a given choice.

+ +
<canvas width="200" height="200"></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  let total = results
+    .reduce((sum, {count}) => sum + count, 0);
+  // Start at the top
+  let currentAngle = -0.5 * Math.PI;
+  for (let result of results) {
+    let sliceAngle = (result.count / total) * 2 * Math.PI;
+    cx.beginPath();
+    // center=100,100, radius=100
+    // from current angle, clockwise by slice's angle
+    cx.arc(100, 100, 100,
+           currentAngle, currentAngle + sliceAngle);
+    currentAngle += sliceAngle;
+    cx.lineTo(100, 100);
+    cx.fillStyle = result.color;
+    cx.fill();
+  }
+</script>
+ +

But a chart that doesn’t tell us what the slices mean isn’t very helpful. We need a way to draw text to the canvas.

+ +

Text

+ +

A 2D canvas drawing context provides the methods fillText and strokeText. The latter can be useful for outlining letters, but usually fillText is what you need. It will fill the outline of the given text with the current fillStyle.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.font = "28px Georgia";
+  cx.fillStyle = "fuchsia";
+  cx.fillText("I can draw text, too!", 10, 50);
+</script>
+ +

You can specify the size, style, and font of the text with the font property. This example just gives a font size and family name. It is also possible to add italic or bold to the start of the string to select a style.

+ +

The last two arguments to fillText and strokeText provide the position at which the font is drawn. By default, they indicate the position of the start of the text’s alphabetic baseline, which is the line that letters “stand” on, not counting hanging parts in letters such as j or p. You can change the horizontal position by setting the textAlign property to "end" or "center" and the vertical position by setting textBaseline to "top", "middle", or "bottom".

+ +

We’ll come back to our pie chart, and the problem of labeling the slices, in the exercises at the end of the chapter.

+ +

Images

+ +

In computer graphics, a distinction is often made between vector graphics and bitmap graphics. The first is what we have been doing so far in this chapter—specifying a picture by giving a logical description of shapes. Bitmap graphics, on the other hand, don’t specify actual shapes but rather work with pixel data (rasters of colored dots).

+ +

The drawImage method allows us to draw pixel data onto a canvas. This pixel data can originate from an <img> element or from another canvas. The following example creates a detached <img> element and loads an image file into it. But it cannot immediately start drawing from this picture because the browser may not have loaded it yet. To deal with this, we register a "load" event handler and do the drawing after the image has loaded.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  let img = document.createElement("img");
+  img.src = "img/hat.png";
+  img.addEventListener("load", () => {
+    for (let x = 10; x < 200; x += 30) {
+      cx.drawImage(img, x, 10);
+    }
+  });
+</script>
+ +

By default, drawImage will draw the image at its original size. You can also give it two additional arguments to set a different width and height.

+ +

When drawImage is given nine arguments, it can be used to draw only a fragment of an image. The second through fifth arguments indicate the rectangle (x, y, width, and height) in the source image that should be copied, and the sixth to ninth arguments give the rectangle (on the canvas) into which it should be copied.

+ +

This can be used to pack multiple sprites (image elements) into a single image file and then draw only the part you need. For example, we have this picture containing a game character in multiple poses:

Various poses of a game character
+ +

By alternating which pose we draw, we can show an animation that looks like a walking character.

+ +

To animate a picture on a canvas, the clearRect method is useful. It resembles fillRect, but instead of coloring the rectangle, it makes it transparent, removing the previously drawn pixels.

+ +

We know that each sprite, each subpicture, is 24 pixels wide and 30 pixels high. The following code loads the image and then sets up an interval (repeated timer) to draw the next frame:

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  let img = document.createElement("img");
+  img.src = "img/player.png";
+  let spriteW = 24, spriteH = 30;
+  img.addEventListener("load", () => {
+    let cycle = 0;
+    setInterval(() => {
+      cx.clearRect(0, 0, spriteW, spriteH);
+      cx.drawImage(img,
+                   // source rectangle
+                   cycle * spriteW, 0, spriteW, spriteH,
+                   // destination rectangle
+                   0,               0, spriteW, spriteH);
+      cycle = (cycle + 1) % 8;
+    }, 120);
+  });
+</script>
+ +

The cycle binding tracks our position in the animation. For each frame, it is incremented and then clipped back to the 0 to 7 range by using the remainder operator. This binding is then used to compute the x-coordinate that the sprite for the current pose has in the picture.

+ +

Transformation

+ +

But what if we want our character to walk to the left instead of to the right? We could draw another set of sprites, of course. But we can also instruct the canvas to draw the picture the other way round.

+ +

Calling the scale method will cause anything drawn after it to be scaled. This method takes two parameters, one to set a horizontal scale and one to set a vertical scale.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  cx.scale(3, .5);
+  cx.beginPath();
+  cx.arc(50, 50, 40, 0, 7);
+  cx.lineWidth = 3;
+  cx.stroke();
+</script>
+ +

Scaling will cause everything about the drawn image, including the line width, to be stretched out or squeezed together as specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0,0), which means it will also flip the direction of the coordinate system. When a horizontal scaling of -1 is applied, a shape drawn at x position 100 will end up at what used to be position -100.

+ +

So to turn a picture around, we can’t simply add cx.scale(-1, 1) before the call to drawImage because that would move our picture outside of the canvas, where it won’t be visible. You could adjust the coordinates given to drawImage to compensate for this by drawing the image at x position -50 instead of 0. Another solution, which doesn’t require the code that does the drawing to know about the scale change, is to adjust the axis around which the scaling happens.

+ +

There are several other methods besides scale that influence the coordinate system for a canvas. You can rotate subsequently drawn shapes with the rotate method and move them with the translate method. The interesting—and confusing—thing is that these transformations stack, meaning that each one happens relative to the previous transformations.

+ +

So if we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50,50) and then rotate by 20 degrees (about 0.1π radians), that rotation will happen around point (50,50).

Stacking transformations
+ +

But if we first rotate by 20 degrees and then translate by (50,50), the translation will happen in the rotated coordinate system and thus produce a different orientation. The order in which transformations are applied matters.

+ +

To flip a picture around the vertical line at a given x position, we can do the following:

+ +
function flipHorizontally(context, around) {
+  context.translate(around, 0);
+  context.scale(-1, 1);
+  context.translate(-around, 0);
+}
+ +

We move the y-axis to where we want our mirror to be, apply the mirroring, and finally move the y-axis back to its proper place in the mirrored universe. The following picture explains why this works:

Mirroring around a vertical line
+ +

This shows the coordinate systems before and after mirroring across the central line. The triangles are numbered to illustrate each step. If we draw a triangle at a positive x position, it would, by default, be in the place where triangle 1 is. A call to flipHorizontally first does a translation to the right, which gets us to triangle 2. It then scales, flipping the triangle over to position 3. This is not where it should be, if it were mirrored in the given line. The second translate call fixes this—it “cancels” the initial translation and makes triangle 4 appear exactly where it should.

+ +

We can now draw a mirrored character at position (100,0) by flipping the world around the character’s vertical center.

+ +
<canvas></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  let img = document.createElement("img");
+  img.src = "img/player.png";
+  let spriteW = 24, spriteH = 30;
+  img.addEventListener("load", () => {
+    flipHorizontally(cx, 100 + spriteW / 2);
+    cx.drawImage(img, 0, 0, spriteW, spriteH,
+                 100, 0, spriteW, spriteH);
+  });
+</script>
+ +

Storing and clearing transformations

+ +

Transformations stick around. Everything else we draw after drawing that mirrored character would also be mirrored. That might be inconvenient.

+ +

It is possible to save the current transformation, do some drawing and transforming, and then restore the old transformation. This is usually the proper thing to do for a function that needs to temporarily transform the coordinate system. First, we save whatever transformation the code that called the function was using. Then the function does its thing, adding more transformations on top of the current transformation. Finally, we revert to the transformation we started with.

+ +

The save and restore methods on the 2D canvas context do this transformation management. They conceptually keep a stack of transformation states. When you call save, the current state is pushed onto the stack, and when you call restore, the state on top of the stack is taken off and used as the context’s current transformation. You can also call resetTransform to fully reset the transformation.

+ +

The branch function in the following example illustrates what you can do with a function that changes the transformation and then calls a function (in this case itself), which continues drawing with the given transformation.

+ +

This function draws a treelike shape by drawing a line, moving the center of the coordinate system to the end of the line, and calling itself twice—first rotated to the left and then rotated to the right. Every call reduces the length of the branch drawn, and the recursion stops when the length drops below 8.

+ +
<canvas width="600" height="300"></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  function branch(length, angle, scale) {
+    cx.fillRect(0, 0, 1, length);
+    if (length < 8) return;
+    cx.save();
+    cx.translate(0, length);
+    cx.rotate(-angle);
+    branch(length * scale, angle, scale);
+    cx.rotate(2 * angle);
+    branch(length * scale, angle, scale);
+    cx.restore();
+  }
+  cx.translate(300, 0);
+  branch(60, 0.5, 0.8);
+</script>
+ +

If the calls to save and restore were not there, the second recursive call to branch would end up with the position and rotation created by the first call. It wouldn’t be connected to the current branch but rather to the innermost, rightmost branch drawn by the first call. The resulting shape might also be interesting, but it is definitely not a tree.

+ +

Back to the game

+ +

We now know enough about canvas drawing to start working on a canvas-based display system for the game from the previous chapter. The new display will no longer be showing just colored boxes. Instead, we’ll use drawImage to draw pictures that represent the game’s elements.

+ +

We define another display object type called CanvasDisplay, supporting the same interface as DOMDisplay from Chapter 16, namely, the methods syncState and clear.

+ +

This object keeps a little more information than DOMDisplay. Rather than using the scroll position of its DOM element, it tracks its own viewport, which tells us what part of the level we are currently looking at. Finally, it keeps a flipPlayer property so that even when the player is standing still, it keeps facing the direction it last moved in.

+ +
class CanvasDisplay {
+  constructor(parent, level) {
+    this.canvas = document.createElement("canvas");
+    this.canvas.width = Math.min(600, level.width * scale);
+    this.canvas.height = Math.min(450, level.height * scale);
+    parent.appendChild(this.canvas);
+    this.cx = this.canvas.getContext("2d");
+
+    this.flipPlayer = false;
+
+    this.viewport = {
+      left: 0,
+      top: 0,
+      width: this.canvas.width / scale,
+      height: this.canvas.height / scale
+    };
+  }
+
+  clear() {
+    this.canvas.remove();
+  }
+}
+ +

The syncState method first computes a new viewport and then draws the game scene at the appropriate position.

+ +
CanvasDisplay.prototype.syncState = function(state) {
+  this.updateViewport(state);
+  this.clearDisplay(state.status);
+  this.drawBackground(state.level);
+  this.drawActors(state.actors);
+};
+ +

Contrary to DOMDisplay, this display style does have to redraw the background on every update. Because shapes on a canvas are just pixels, after we draw them there is no good way to move them (or remove them). The only way to update the canvas display is to clear it and redraw the scene. We may also have scrolled, which requires the background to be in a different position.

+ +

The updateViewport method is similar to DOMDisplay’s scrollPlayerIntoView method. It checks whether the player is too close to the edge of the screen and moves the viewport when this is the case.

+ +
CanvasDisplay.prototype.updateViewport = function(state) {
+  let view = this.viewport, margin = view.width / 3;
+  let player = state.player;
+  let center = player.pos.plus(player.size.times(0.5));
+
+  if (center.x < view.left + margin) {
+    view.left = Math.max(center.x - margin, 0);
+  } else if (center.x > view.left + view.width - margin) {
+    view.left = Math.min(center.x + margin - view.width,
+                         state.level.width - view.width);
+  }
+  if (center.y < view.top + margin) {
+    view.top = Math.max(center.y - margin, 0);
+  } else if (center.y > view.top + view.height - margin) {
+    view.top = Math.min(center.y + margin - view.height,
+                        state.level.height - view.height);
+  }
+};
+ +

The calls to Math.max and Math.min ensure that the viewport does not end up showing space outside of the level. Math.max(x, 0) makes sure the resulting number is not less than zero. Math.min similarly guarantees that a value stays below a given bound.

+ +

When clearing the display, we’ll use a slightly different color depending on whether the game is won (brighter) or lost (darker).

+ +
CanvasDisplay.prototype.clearDisplay = function(status) {
+  if (status == "won") {
+    this.cx.fillStyle = "rgb(68, 191, 255)";
+  } else if (status == "lost") {
+    this.cx.fillStyle = "rgb(44, 136, 214)";
+  } else {
+    this.cx.fillStyle = "rgb(52, 166, 251)";
+  }
+  this.cx.fillRect(0, 0,
+                   this.canvas.width, this.canvas.height);
+};
+ +

To draw the background, we run through the tiles that are visible in the current viewport, using the same trick used in the touches method from the previous chapter.

+ +
let otherSprites = document.createElement("img");
+otherSprites.src = "img/sprites.png";
+
+CanvasDisplay.prototype.drawBackground = function(level) {
+  let {left, top, width, height} = this.viewport;
+  let xStart = Math.floor(left);
+  let xEnd = Math.ceil(left + width);
+  let yStart = Math.floor(top);
+  let yEnd = Math.ceil(top + height);
+
+  for (let y = yStart; y < yEnd; y++) {
+    for (let x = xStart; x < xEnd; x++) {
+      let tile = level.rows[y][x];
+      if (tile == "empty") continue;
+      let screenX = (x - left) * scale;
+      let screenY = (y - top) * scale;
+      let tileX = tile == "lava" ? scale : 0;
+      this.cx.drawImage(otherSprites,
+                        tileX,         0, scale, scale,
+                        screenX, screenY, scale, scale);
+    }
+  }
+};
+ +

Tiles that are not empty are drawn with drawImage. The otherSprites image contains the pictures used for elements other than the player. It contains, from left to right, the wall tile, the lava tile, and the sprite for a coin.

Sprites for our game
+ +

Background tiles are 20 by 20 pixels since we will use the same scale that we used in DOMDisplay. Thus, the offset for lava tiles is 20 (the value of the scale binding), and the offset for walls is 0.

+ +

We don’t bother waiting for the sprite image to load. Calling drawImage with an image that hasn’t been loaded yet will simply do nothing. Thus, we might fail to draw the game properly for the first few frames, while the image is still loading, but that is not a serious problem. Since we keep updating the screen, the correct scene will appear as soon as the loading finishes.

+ +

The walking character shown earlier will be used to represent the player. The code that draws it needs to pick the right sprite and direction based on the player’s current motion. The first eight sprites contain a walking animation. When the player is moving along a floor, we cycle through them based on the current time. We want to switch frames every 60 milliseconds, so the time is divided by 60 first. When the player is standing still, we draw the ninth sprite. During jumps, which are recognized by the fact that the vertical speed is not zero, we use the tenth, rightmost sprite.

+ +

Because the sprites are slightly wider than the player object—24 instead of 16 pixels to allow some space for feet and arms—the method has to adjust the x-coordinate and width by a given amount (playerXOverlap).

+ +
let playerSprites = document.createElement("img");
+playerSprites.src = "img/player.png";
+const playerXOverlap = 4;
+
+CanvasDisplay.prototype.drawPlayer = function(player, x, y,
+                                              width, height){
+  width += playerXOverlap * 2;
+  x -= playerXOverlap;
+  if (player.speed.x != 0) {
+    this.flipPlayer = player.speed.x < 0;
+  }
+
+  let tile = 8;
+  if (player.speed.y != 0) {
+    tile = 9;
+  } else if (player.speed.x != 0) {
+    tile = Math.floor(Date.now() / 60) % 8;
+  }
+
+  this.cx.save();
+  if (this.flipPlayer) {
+    flipHorizontally(this.cx, x + width / 2);
+  }
+  let tileX = tile * width;
+  this.cx.drawImage(playerSprites, tileX, 0, width, height,
+                                   x,     y, width, height);
+  this.cx.restore();
+};
+ +

The drawPlayer method is called by drawActors, which is responsible for drawing all the actors in the game.

+ +
CanvasDisplay.prototype.drawActors = function(actors) {
+  for (let actor of actors) {
+    let width = actor.size.x * scale;
+    let height = actor.size.y * scale;
+    let x = (actor.pos.x - this.viewport.left) * scale;
+    let y = (actor.pos.y - this.viewport.top) * scale;
+    if (actor.type == "player") {
+      this.drawPlayer(actor, x, y, width, height);
+    } else {
+      let tileX = (actor.type == "coin" ? 2 : 1) * scale;
+      this.cx.drawImage(otherSprites,
+                        tileX, 0, width, height,
+                        x,     y, width, height);
+    }
+  }
+};
+ +

When drawing something that is not the player, we look at its type to find the offset of the correct sprite. The lava tile is found at offset 20, and the coin sprite is found at 40 (two times scale).

+ +

We have to subtract the viewport’s position when computing the actor’s position since (0,0) on our canvas corresponds to the top left of the viewport, not the top left of the level. We could also have used translate for this. Either way works.

+ +

This document plugs the new display into runGame:

+ +
<body>
+  <script>
+    runGame(GAME_LEVELS, CanvasDisplay);
+  </script>
+</body>
+ +

Choosing a graphics interface

+ +

So when you need to generate graphics in the browser, you can choose between plain HTML, SVG, and canvas. There is no single best approach that works in all situations. Each option has strengths and weaknesses.

+ +

Plain HTML has the advantage of being simple. It also integrates well with text. Both SVG and canvas allow you to draw text, but they won’t help you position that text or wrap it when it takes up more than one line. In an HTML-based picture, it is much easier to include blocks of text.

+ +

SVG can be used to produce crisp graphics that look good at any zoom level. Unlike HTML, it is designed for drawing and is thus more suitable for that purpose.

+ +

Both SVG and HTML build up a data structure (the DOM) that represents your picture. This makes it possible to modify elements after they are drawn. If you need to repeatedly change a small part of a big picture in response to what the user is doing or as part of an animation, doing it in a canvas can be needlessly expensive. The DOM also allows us to register mouse event handlers on every element in the picture (even on shapes drawn with SVG). You can’t do that with canvas.

+ +

But canvas’s pixel-oriented approach can be an advantage when drawing a huge number of tiny elements. The fact that it does not build up a data structure but only repeatedly draws onto the same pixel surface gives canvas a lower cost per shape.

+ +

There are also effects, such as rendering a scene one pixel at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that can be realistically handled only by a pixel-based approach.

+ +

In some cases, you may want to combine several of these techniques. For example, you might draw a graph with SVG or canvas but show textual information by positioning an HTML element on top of the picture.

+ +

For nondemanding applications, it really doesn’t matter much which interface you choose. The display we built for our game in this chapter could have been implemented using any of these three graphics technologies since it does not need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements.

+ +

Summary

+ +

In this chapter we discussed techniques for drawing graphics in the browser, focusing on the <canvas> element.

+ +

A canvas node represents an area in a document that our program may draw on. This drawing is done through a drawing context object, created with the getContext method.

+ +

The 2D drawing interface allows us to fill and stroke various shapes. The context’s fillStyle property determines how shapes are filled. The strokeStyle and lineWidth properties control the way lines are drawn.

+ +

Rectangles and pieces of text can be drawn with a single method call. The fillRect and strokeRect methods draw rectangles, and the fillText and strokeText methods draw text. To create custom shapes, we must first build up a path.

+ +

Calling beginPath starts a new path. A number of other methods add lines and curves to the current path. For example, lineTo can add a straight line. When a path is finished, it can be filled with the fill method or stroked with the stroke method.

+ +

Moving pixels from an image or another canvas onto our canvas is done with the drawImage method. By default, this method draws the whole source image, but by giving it more parameters, you can copy a specific area of the image. We used this for our game by copying individual poses of the game character out of an image that contained many such poses.

+ +

Transformations allow you to draw a shape in multiple orientations. A 2D drawing context has a current transformation that can be changed with the translate, scale, and rotate methods. These will affect all subsequent drawing operations. A transformation state can be saved with the save method and restored with the restore method.

+ +

When showing an animation on a canvas, the clearRect method can be used to clear part of the canvas before redrawing it.

+ +

Exercises

+ +

Shapes

+ +

Write a program that draws the following shapes on a canvas:

+ +
    + +
  1. + +

    A trapezoid (a rectangle that is wider on one side)

  2. + +
  3. + +

    A red diamond (a rectangle rotated 45 degrees or ¼π radians)

  4. + +
  5. + +

    A zigzagging line

  6. + +
  7. + +

    A spiral made up of 100 straight line segments

  8. + +
  9. + +

    A yellow star

  10. + +
The shapes to draw
+ +

When drawing the last two, you may want to refer to the explanation of Math.cos and Math.sin in Chapter 14, which describes how to get coordinates on a circle using these functions.

+ +

I recommend creating a function for each shape. Pass the position, and optionally other properties such as the size or the number of points, as parameters. The alternative, which is to hard-code numbers all over your code, tends to make the code needlessly hard to read and modify.

+ +
<canvas width="600" height="200"></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+
+  // Your code here.
+</script>
+ +
+ +

The trapezoid (1) is easiest to draw using a path. Pick suitable center coordinates and add each of the four corners around the center.

+ +

The diamond (2) can be drawn the straightforward way, with a path, or the interesting way, with a rotate transformation. To use rotation, you will have to apply a trick similar to what we did in the flipHorizontally function. Because you want to rotate around the center of your rectangle and not around the point (0,0), you must first translate to there, then rotate, and then translate back.

+ +

Make sure you reset the transformation after drawing any shape that creates one.

+ +

For the zigzag (3) it becomes impractical to write a new call to lineTo for each line segment. Instead, you should use a loop. You can have each iteration draw either two line segments (right and then left again) or one, in which case you must use the evenness (% 2) of the loop index to determine whether to go left or right.

+ +

You’ll also need a loop for the spiral (4). If you draw a series of points, with each point moving further along a circle around the spiral’s center, you get a circle. If, during the loop, you vary the radius of the circle on which you are putting the current point and go around more than once, the result is a spiral.

+ +

The star (5) depicted is built out of quadraticCurveTo lines. You could also draw one with straight lines. Divide a circle into eight pieces for a star with eight points, or however many pieces you want. Draw lines between these points, making them curve toward the center of the star. With quadraticCurveTo, you can use the center as the control point.

+ +
+ +

The pie chart

+ +

Earlier in the chapter, we saw an example program that drew a pie chart. Modify this program so that the name of each category is shown next to the slice that represents it. Try to find a pleasing-looking way to automatically position this text that would work for other data sets as well. You may assume that categories are big enough to leave ample room for their labels.

+ +

You might need Math.sin and Math.cos again, which are described in Chapter 14.

+ +
<canvas width="600" height="300"></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+  let total = results
+    .reduce((sum, {count}) => sum + count, 0);
+  let currentAngle = -0.5 * Math.PI;
+  let centerX = 300, centerY = 150;
+
+  // Add code to draw the slice labels in this loop.
+  for (let result of results) {
+    let sliceAngle = (result.count / total) * 2 * Math.PI;
+    cx.beginPath();
+    cx.arc(centerX, centerY, 100,
+           currentAngle, currentAngle + sliceAngle);
+    currentAngle += sliceAngle;
+    cx.lineTo(centerX, centerY);
+    cx.fillStyle = result.color;
+    cx.fill();
+  }
+</script>
+ +
+ +

You will need to call fillText and set the context’s textAlign and textBaseline properties in such a way that the text ends up where you want it.

+ +

A sensible way to position the labels would be to put the text on the line going from the center of the pie through the middle of the slice. You don’t want to put the text directly against the side of the pie but rather move the text out to the side of the pie by a given number of pixels.

+ +

The angle of this line is currentAngle + 0.5 * sliceAngle. The following code finds a position on this line 120 pixels from the center:

+ +
let middleAngle = currentAngle + 0.5 * sliceAngle;
+let textX = Math.cos(middleAngle) * 120 + centerX;
+let textY = Math.sin(middleAngle) * 120 + centerY;
+ +

For textBaseline, the value "middle" is probably appropriate when using this approach. What to use for textAlign depends on which side of the circle we are on. On the left, it should be "right", and on the right, it should be "left", so that the text is positioned away from the pie.

+ +

If you are not sure how to find out which side of the circle a given angle is on, look to the explanation of Math.cos in Chapter 14. The cosine of an angle tells us which x-coordinate it corresponds to, which in turn tells us exactly which side of the circle we are on.

+ +
+ +

A bouncing ball

+ +

Use the requestAnimationFrame technique that we saw in Chapter 14 and Chapter 16 to draw a box with a bouncing ball in it. The ball moves at a constant speed and bounces off the box’s sides when it hits them.

+ +
<canvas width="400" height="400"></canvas>
+<script>
+  let cx = document.querySelector("canvas").getContext("2d");
+
+  let lastTime = null;
+  function frame(time) {
+    if (lastTime != null) {
+      updateAnimation(Math.min(100, time - lastTime) / 1000);
+    }
+    lastTime = time;
+    requestAnimationFrame(frame);
+  }
+  requestAnimationFrame(frame);
+
+  function updateAnimation(step) {
+    // Your code here.
+  }
+</script>
+ +
+ +

A box is easy to draw with strokeRect. Define a binding that holds its size or define two bindings if your box’s width and height differ. To create a round ball, start a path and call arc(x, y, radius, 0, 7), which creates an arc going from zero to more than a whole circle. Then fill the path.

+ +

To model the ball’s position and speed, you can use the Vec class from Chapter 16 (which is available on this page). Give it a starting speed, preferably one that is not purely vertical or horizontal, and for every frame multiply that speed by the amount of time that elapsed. When the ball gets too close to a vertical wall, invert the x component in its speed. Likewise, invert the y component when it hits a horizontal wall.

+ +

After finding the ball’s new position and speed, use clearRect to delete the scene and redraw it using the new position.

+ +
+ +

Precomputed mirroring

+ +

One unfortunate thing about transformations is that they slow down the drawing of bitmaps. The position and size of each pixel has to be transformed, and though it is possible that browsers will get cleverer about transformation in the future, they currently cause a measurable increase in the time it takes to draw a bitmap.

+ +

In a game like ours, where we are drawing only a single transformed sprite, this is a nonissue. But imagine that we need to draw hundreds of characters or thousands of rotating particles from an explosion.

+ +

Think of a way to allow us to draw an inverted character without loading additional image files and without having to make transformed drawImage calls every frame.

+ +
+ +

The key to the solution is the fact that we can use a canvas element as a source image when using drawImage. It is possible to create an extra <canvas> element, without adding it to the document, and draw our inverted sprites to it, once. When drawing an actual frame, we just copy the already inverted sprites to the main canvas.

+ +

Some care would be required because images do not load instantly. We do the inverted drawing only once, and if we do it before the image loads, it won’t draw anything. A "load" handler on the image can be used to draw the inverted images to the extra canvas. This canvas can be used as a drawing source immediately (it’ll simply be blank until we draw the character onto it).

+ +
+
diff --git a/docs/18_http.html b/docs/18_http.html new file mode 100644 index 000000000..2ae7edf4e --- /dev/null +++ b/docs/18_http.html @@ -0,0 +1,695 @@ + + + + + HTTP and Forms :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 18HTTP and Forms

+ +
+ +

Communication must be stateless in nature [...] such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server.

+ +
Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures
+ +
Picture of a web form on a medieval scroll
+ +

The Hypertext Transfer Protocol, already mentioned in Chapter 13, is the mechanism through which data is requested and provided on the World Wide Web. This chapter describes the protocol in more detail and explains the way browser JavaScript has access to it.

+ +

The protocol

+ +

If you type eloquentjavascript.net/18_http.html into your browser’s address bar, the browser first looks up the address of the server associated with eloquentjavascript.net and tries to open a TCP connection to it on port 80, the default port for HTTP traffic. If the server exists and accepts the connection, the browser might send something like this:

+ +
GET /18_http.html HTTP/1.1
+Host: eloquentjavascript.net
+User-Agent: Your browser's name
+ +

Then the server responds, through that same connection.

+ +
HTTP/1.1 200 OK
+Content-Length: 65585
+Content-Type: text/html
+Last-Modified: Mon, 08 Jan 2018 10:29:45 GMT
+
+<!doctype html>
+... the rest of the document
+ +

The browser takes the part of the response after the blank line, its body (not to be confused with the HTML <body> tag), and displays it as an HTML document.

+ +

The information sent by the client is called the request. It starts with this line:

+ +
GET /18_http.html HTTP/1.1
+ +

The first word is the method of the request. GET means that we want to get the specified resource. Other common methods are DELETE to delete a resource, PUT to create or replace it, and POST to send information to it. Note that the server is not obliged to carry out every request it gets. If you walk up to a random website and tell it to DELETE its main page, it’ll probably refuse.

+ +

The part after the method name is the path of the resource the request applies to. In the simplest case, a resource is simply a file on the server, but the protocol doesn’t require it to be. A resource may be anything that can be transferred as if it is a file. Many servers generate the responses they produce on the fly. For example, if you open https://github.com/marijnh, the server looks in its database for a user named “marijnh”, and if it finds one, it will generate a profile page for that user.

+ +

After the resource path, the first line of the request mentions HTTP/1.1 to indicate the version of the HTTP protocol it is using.

+ +

In practice, many sites use HTTP version 2, which supports the same concepts as version 1.1 but is a lot more complicated so that it can be faster. Browsers will automatically switch to the appropriate protocol version when talking to a given server, and the outcome of a request is the same regardless of which version is used. Because version 1.1 is more straightforward and easier to play around with, we’ll focus on that.

+ +

The server’s response will start with a version as well, followed by the status of the response, first as a three-digit status code and then as a human-readable string.

+ +
HTTP/1.1 200 OK
+ +

Status codes starting with a 2 indicate that the request succeeded. Codes starting with 4 mean there was something wrong with the request. 404 is probably the most famous HTTP status code—it means that the resource could not be found. Codes that start with 5 mean an error happened on the server and the request is not to blame.

+ +

The first line of a request or response may be followed by any number of headers. These are lines in the form name: value that specify extra information about the request or response. These headers were part of the example response:

+ +
Content-Length: 65585
+Content-Type: text/html
+Last-Modified: Thu, 04 Jan 2018 14:05:30 GMT
+ +

This tells us the size and type of the response document. In this case, it is an HTML document of 65,585 bytes. It also tells us when that document was last modified.

+ +

For most headers, the client and server are free to decide whether to include them in a request or response. But a few are required. For example, the Host header, which specifies the hostname, should be included in a request because a server might be serving multiple hostnames on a single IP address, and without that header, the server won’t know which hostname the client is trying to talk to.

+ +

After the headers, both requests and responses may include a blank line followed by a body, which contains the data being sent. GET and DELETE requests don’t send along any data, but PUT and POST requests do. Similarly, some response types, such as error responses, do not require a body.

+ +

Browsers and HTTP

+ +

As we saw in the example, a browser will make a request when we enter a URL in its address bar. When the resulting HTML page references other files, such as images and JavaScript files, those are also retrieved.

+ +

A moderately complicated website can easily include anywhere from 10 to 200 resources. To be able to fetch those quickly, browsers will make several GET requests simultaneously, rather than waiting for the responses one at a time.

+ +

HTML pages may include forms, which allow the user to fill out information and send it to the server. This is an example of a form:

+ +
<form method="GET" action="example/message.html">
+  <p>Name: <input type="text" name="name"></p>
+  <p>Message:<br><textarea name="message"></textarea></p>
+  <p><button type="submit">Send</button></p>
+</form>
+ +

This code describes a form with two fields: a small one asking for a name and a larger one to write a message in. When you click the Send button, the form is submitted, meaning that the content of its field is packed into an HTTP request and the browser navigates to the result of that request.

+ +

When the <form> element’s method attribute is GET (or is omitted), the information in the form is added to the end of the action URL as a query string. The browser might make a request to this URL:

+ +
GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1
+ +

The question mark indicates the end of the path part of the URL and the start of the query. It is followed by pairs of names and values, corresponding to the name attribute on the form field elements and the content of those elements, respectively. An ampersand character (&) is used to separate the pairs.

+ +

The actual message encoded in the URL is “Yes?”, but the question mark is replaced by a strange code. Some characters in query strings must be escaped. The question mark, represented as %3F, is one of those. There seems to be an unwritten rule that every format needs its own way of escaping characters. This one, called URL encoding, uses a percent sign followed by two hexadecimal (base 16) digits that encode the character code. In this case, 3F, which is 63 in decimal notation, is the code of a question mark character. JavaScript provides the encodeURIComponent and decodeURIComponent functions to encode and decode this format.

+ +
console.log(encodeURIComponent("Yes?"));
+// → Yes%3F
+console.log(decodeURIComponent("Yes%3F"));
+// → Yes?
+ +

If we change the method attribute of the HTML form in the example we saw earlier to POST, the HTTP request made to submit the form will use the POST method and put the query string in the body of the request, rather than adding it to the URL.

+ +
POST /example/message.html HTTP/1.1
+Content-length: 24
+Content-type: application/x-www-form-urlencoded
+
+name=Jean&message=Yes%3F
+ +

GET requests should be used for requests that do not have side +effects but simply ask for information. Requests that change something on the server, for example creating a new account or posting a message, should be expressed with other methods, such as POST. Client-side software such as a browser knows that it shouldn’t blindly make POST requests but will often implicitly make GET requests—for example to prefetch a resource it believes the user will soon need.

+ +

We’ll come back to forms and how to interact with them from JavaScript later in the chapter.

+ +

Fetch

+ +

The interface through which browser JavaScript can make HTTP requests is called fetch. Since it is relatively new, it conveniently uses promises (which is rare for browser interfaces).

+ +
fetch("example/data.txt").then(response => {
+  console.log(response.status);
+  // → 200
+  console.log(response.headers.get("Content-Type"));
+  // → text/plain
+});
+ +

Calling fetch returns a promise that resolves to a Response object holding information about the server’s response, such as its status code and its headers. The headers are wrapped in a Map-like object that treats its keys (the header names) as case insensitive because header names are not supposed to be case sensitive. This means headers.get("Content-Type") and headers.get("content-TYPE") will return the same value.

+ +

Note that the promise returned by fetch resolves successfully even if the server responded with an error code. It might also be rejected if there is a network error or if the server that the request is addressed to can’t be found.

+ +

The first argument to fetch is the URL that should be requested. When that URL doesn’t start with a protocol name (such as http:), it is treated as relative, which means it is interpreted relative to the current document. When it starts with a slash (/), it replaces the current path, which is the part after the server name. When it does not, the part of the current path up to and including its last slash character is put in front of the relative URL.

+ +

To get at the actual content of a response, you can use its text method. Because the initial promise is resolved as soon as the response’s headers have been received and because reading the response body might take a while longer, this again returns a promise.

+ +
fetch("example/data.txt")
+  .then(resp => resp.text())
+  .then(text => console.log(text));
+// → This is the content of data.txt
+ +

A similar method, called json, returns a promise that resolves to the value you get when parsing the body as JSON or rejects if it’s not valid JSON.

+ +

By default, fetch uses the GET method to make its request and does not include a request body. You can configure it differently by passing an object with extra options as a second argument. For example, this request tries to delete example/data.txt:

+ +
fetch("example/data.txt", {method: "DELETE"}).then(resp => {
+  console.log(resp.status);
+  // → 405
+});
+ +

The 405 status code means “method not allowed”, an HTTP server’s way of saying “I can’t do that”.

+ +

To add a request body, you can include a body option. To set headers, there’s the headers option. For example, this request includes a Range header, which instructs the server to return only part of a response.

+ +
fetch("example/data.txt", {headers: {Range: "bytes=8-19"}})
+  .then(resp => resp.text())
+  .then(console.log);
+// → the content
+ +

The browser will automatically add some request headers, such as “Host” and those needed for the server to figure out the size of the body. But adding your own headers is often useful to include things such as authentication information or to tell the server which file format you’d like to receive.

+ +

HTTP sandboxing

+ +

Making HTTP requests in web page scripts once again raises concerns about security. The person who controls the script might not have the same interests as the person on whose computer it is running. More specifically, if I visit themafia.org, I do not want its scripts to be able to make a request to mybank.com, using identifying information from my browser, with instructions to transfer all my money to some random account.

+ +

For this reason, browsers protect us by disallowing scripts to make HTTP requests to other domains (names such as themafia.org and mybank.com).

+ +

This can be an annoying problem when building systems that want to access several domains for legitimate reasons. Fortunately, servers can include a header like this in their response to explicitly indicate to the browser that it is okay for the request to come from another domain:

+ +
Access-Control-Allow-Origin: *
+ +

Appreciating HTTP

+ +

When building a system that requires communication between a JavaScript program running in the browser (client-side) and a program on a server (server-side), there are several different ways to model this communication.

+ +

A commonly used model is that of remote procedure calls. In this model, communication follows the patterns of normal function calls, except that the function is actually running on another machine. Calling it involves making a request to the server that includes the function’s name and arguments. The response to that request contains the returned value.

+ +

When thinking in terms of remote procedure calls, HTTP is just a vehicle for communication, and you will most likely write an abstraction layer that hides it entirely.

+ +

Another approach is to build your communication around the concept of resources and HTTP methods. Instead of a remote procedure called addUser, you use a PUT request to /users/larry. Instead of encoding that user’s properties in function arguments, you define a JSON document format (or use an existing format) that represents a user. The body of the PUT request to create a new resource is then such a document. A resource is fetched by making a GET request to the resource’s URL (for example, /user/larry), which again returns the document representing the resource.

+ +

This second approach makes it easier to use some of the features that HTTP provides, such as support for caching resources (keeping a copy on the client for fast access). The concepts used in HTTP, which are well designed, can provide a helpful set of principles to design your server interface around.

+ +

Security and HTTPS

+ +

Data traveling over the Internet tends to follow a long, dangerous road. To get to its destination, it must hop through anything from coffee shop Wi-Fi hotspots to networks controlled by various companies and states. At any point along its route it may be inspected or even modified.

+ +

If it is important that something remain secret, such as the password to your email account, or that it arrive at its destination unmodified, such as the account number you transfer money to via your bank’s website, plain HTTP is not good enough.

+ +

The secure HTTP protocol, used for URLs starting with https://, wraps HTTP traffic in a way that makes it harder to read and tamper with. Before exchanging data, the client verifies that the server is who it claims to be by asking it to prove that it has a cryptographic certificate issued by a certificate authority that the browser recognizes. Next, all data going over the connection is encrypted in a way that should prevent eavesdropping and tampering.

+ +

Thus, when it works right, HTTPS prevents other people from impersonating the website you are trying to talk to and from snooping on your communication. It is not perfect, and there have been various incidents where HTTPS failed because of forged or stolen certificates and broken software, but it is a lot safer than plain HTTP.

+ +

Form fields

+ +

Forms were originally designed for the pre-JavaScript Web to allow web sites to send user-submitted information in an HTTP request. This design assumes that interaction with the server always happens by navigating to a new page.

+ +

But their elements are part of the DOM like the rest of the page, and the DOM elements that represent form fields support a number of properties and events that are not present on other elements. These make it possible to inspect and control such input fields with JavaScript programs and do things such as adding new functionality to a form or using forms and fields as building blocks in a JavaScript application.

+ +

A web form consists of any number of input fields grouped in a <form> tag. HTML allows several different styles of fields, ranging from simple on/off checkboxes to drop-down menus and fields for text input. This book won’t try to comprehensively discuss all field types, but we’ll start with a rough overview.

+ +

A lot of field types use the <input> tag. This tag’s type attribute is used to select the field’s style. These are some commonly used <input> types:

+ + + + + + + + + + + + + + + + + + + + + + + +
textA single-line text field
passwordSame as text but hides the text that is typed
checkboxAn on/off switch
radio(Part of) a multiple-choice field
fileAllows the user to choose a file from their computer
+ +

Form fields do not necessarily have to appear in a <form> tag. You can put them anywhere in a page. Such form-less fields cannot be submitted (only a form as a whole can), but when responding to input with JavaScript, we often don’t want to submit our fields normally anyway.

+ +
<p><input type="text" value="abc"> (text)</p>
+<p><input type="password" value="abc"> (password)</p>
+<p><input type="checkbox" checked> (checkbox)</p>
+<p><input type="radio" value="A" name="choice">
+   <input type="radio" value="B" name="choice" checked>
+   <input type="radio" value="C" name="choice"> (radio)</p>
+<p><input type="file"> (file)</p>
+ +

The JavaScript interface for such elements differs with the type of the element.

+ +

Multiline text fields have their own tag, <textarea>, mostly because using an attribute to specify a multiline starting value would be awkward. The <textarea> tag requires a matching </textarea> closing tag and uses the text between those two, instead of the value attribute, as starting text.

+ +
<textarea>
+one
+two
+three
+</textarea>
+ +

Finally, the <select> tag is used to create a field that allows the user to select from a number of predefined options.

+ +
<select>
+  <option>Pancakes</option>
+  <option>Pudding</option>
+  <option>Ice cream</option>
+</select>
+ +

Whenever the value of a form field changes, it will fire a "change" event.

+ +

Focus

+ +

Unlike most elements in HTML documents, form fields can get keyboard focus. When clicked or activated in some other way, they become the currently active element and the recipient of keyboard input.

+ +

Thus, you can type into a text field only when it is focused. Other fields respond differently to keyboard events. For example, a <select> menu tries to move to the option that contains the text the user typed and responds to the arrow keys by moving its selection up and down.

+ +

We can control focus from JavaScript with the focus and blur methods. The first moves focus to the DOM element it is called on, and the second removes focus. The value in document.activeElement corresponds to the currently focused element.

+ +
<input type="text">
+<script>
+  document.querySelector("input").focus();
+  console.log(document.activeElement.tagName);
+  // → INPUT
+  document.querySelector("input").blur();
+  console.log(document.activeElement.tagName);
+  // → BODY
+</script>
+ +

For some pages, the user is expected to want to interact with a form field immediately. JavaScript can be used to focus this field when the document is loaded, but HTML also provides the autofocus attribute, which produces the same effect while letting the browser know what we are trying to achieve. This gives the browser the option to disable the behavior when it is not appropriate, such as when the user has put the focus on something else.

+ +

Browsers traditionally also allow the user to move the focus through the document by pressing the tab key. We can influence the order in which elements receive focus with the tabindex attribute. The following example document will let the focus jump from the text input to the OK button, rather than going through the help link first:

+ +
<input type="text" tabindex=1> <a href=".">(help)</a>
+<button onclick="console.log('ok')" tabindex=2>OK</button>
+ +

By default, most types of HTML elements cannot be focused. But you can add a tabindex attribute to any element that will make it focusable. A tabindex of -1 makes tabbing skip over an element, even if it is normally focusable.

+ +

Disabled fields

+ +

All form fields can be disabled through their disabled attribute. It is an attribute that can be specified without value—the fact that it is present at all disables the element.

+ +
<button>I'm all right</button>
+<button disabled>I'm out</button>
+ +

Disabled fields cannot be focused or changed, and browsers make them look gray and faded.

+ +

When a program is in the process of handling an action caused by some button or other control that might require communication with the server and thus take a while, it can be a good idea to disable the control until the action finishes. That way, when the user gets impatient and clicks it again, they don’t accidentally repeat their action.

+ +

The form as a whole

+ +

When a field is contained in a <form> element, its DOM element will have a form property linking back to the form’s DOM element. The <form> element, in turn, has a property called elements that contains an array-like collection of the fields inside it.

+ +

The name attribute of a form field determines the way its value will be identified when the form is submitted. It can also be used as a property name when accessing the form’s elements property, which acts both as an array-like object (accessible by number) and a map (accessible by name).

+ +
<form action="example/submit.html">
+  Name: <input type="text" name="name"><br>
+  Password: <input type="password" name="password"><br>
+  <button type="submit">Log in</button>
+</form>
+<script>
+  let form = document.querySelector("form");
+  console.log(form.elements[1].type);
+  // → password
+  console.log(form.elements.password.type);
+  // → password
+  console.log(form.elements.name.form == form);
+  // → true
+</script>
+ +

A button with a type attribute of submit will, when pressed, cause the form to be submitted. Pressing enter when a form field is focused has the same effect.

+ +

Submitting a form normally means that the browser navigates to the page indicated by the form’s action attribute, using either a GET or a POST request. But before that happens, a "submit" event is fired. You can handle this event with JavaScript and prevent this default behavior by calling preventDefault on the event object.

+ +
<form action="example/submit.html">
+  Value: <input type="text" name="value">
+  <button type="submit">Save</button>
+</form>
+<script>
+  let form = document.querySelector("form");
+  form.addEventListener("submit", event => {
+    console.log("Saving value", form.elements.value.value);
+    event.preventDefault();
+  });
+</script>
+ +

Intercepting "submit" events in JavaScript has various uses. We can write code to verify that the values the user entered make sense and immediately show an error message instead of submitting the form. Or we can disable the regular way of submitting the form entirely, as in the example, and have our program handle the input, possibly using fetch to send it to a server without reloading the page.

+ +

Text fields

+ +

Fields created by <textarea> tags, or <input> tags with a type of text or password, share a common interface. Their DOM elements have a value property that holds their current content as a string value. Setting this property to another string changes the field’s content.

+ +

The selectionStart and selectionEnd properties of text fields give us information about the cursor and selection in the text. When nothing is selected, these two properties hold the same number, indicating the position of the cursor. For example, 0 indicates the start of the text, and 10 indicates the cursor is after the 10th character. When part of the field is selected, the two properties will differ, giving us the start and end of the selected text. Like value, these properties may also be written to.

+ +

Imagine you are writing an article about Khasekhemwy but have some trouble spelling his name. The following code wires up a <textarea> tag with an event handler that, when you press F2, inserts the string “Khasekhemwy” for you.

+ +
<textarea></textarea>
+<script>
+  let textarea = document.querySelector("textarea");
+  textarea.addEventListener("keydown", event => {
+    // The key code for F2 happens to be 113
+    if (event.keyCode == 113) {
+      replaceSelection(textarea, "Khasekhemwy");
+      event.preventDefault();
+    }
+  });
+  function replaceSelection(field, word) {
+    let from = field.selectionStart, to = field.selectionEnd;
+    field.value = field.value.slice(0, from) + word +
+                  field.value.slice(to);
+    // Put the cursor after the word
+    field.selectionStart = from + word.length;
+    field.selectionEnd = from + word.length;
+  }
+</script>
+ +

The replaceSelection function replaces the currently selected part of a text field’s content with the given word and then moves the cursor after that word so that the user can continue typing.

+ +

The "change" event for a text +field does not fire every time something is typed. Rather, it fires when the field loses focus after its content was changed. To respond immediately to changes in a text field, you should register a handler for the "input" event instead, which fires for every time the user types a character, deletes text, or otherwise manipulates the field’s content.

+ +

The following example shows a text field and a counter displaying the current length of the text in the field:

+ +
<input type="text"> length: <span id="length">0</span>
+<script>
+  let text = document.querySelector("input");
+  let output = document.querySelector("#length");
+  text.addEventListener("input", () => {
+    output.textContent = text.value.length;
+  });
+</script>
+ +

Checkboxes and radio buttons

+ +

A checkbox field is a binary toggle. Its value can be extracted or changed through its checked property, which holds a Boolean value.

+ +
<label>
+  <input type="checkbox" id="purple"> Make this page purple
+</label>
+<script>
+  let checkbox = document.querySelector("#purple");
+  checkbox.addEventListener("change", () => {
+    document.body.style.background =
+      checkbox.checked ? "mediumpurple" : "";
+  });
+</script>
+ +

The <label> tag associates a piece of document with an input field. Clicking anywhere on the label will activate the field, which focuses it and toggles its value when it is a checkbox or radio button.

+ +

A radio button is similar to a checkbox, but it’s implicitly linked to other radio buttons with the same name attribute so that only one of them can be active at any time.

+ +
Color:
+<label>
+  <input type="radio" name="color" value="orange"> Orange
+</label>
+<label>
+  <input type="radio" name="color" value="lightgreen"> Green
+</label>
+<label>
+  <input type="radio" name="color" value="lightblue"> Blue
+</label>
+<script>
+  let buttons = document.querySelectorAll("[name=color]");
+  for (let button of Array.from(buttons)) {
+    button.addEventListener("change", () => {
+      document.body.style.background = button.value;
+    });
+  }
+</script>
+ +

The square brackets in the CSS query given to querySelectorAll are used to match attributes. It selects elements whose name attribute is "color".

+ +

Select fields

+ +

Select fields are conceptually similar to radio buttons—they also allow the user to choose from a set of options. But where a radio button puts the layout of the options under our control, the appearance of a <select> tag is determined by the browser.

+ +

Select fields also have a variant that is more akin to a list of checkboxes, rather than radio boxes. When given the multiple attribute, a <select> tag will allow the user to select any number of options, rather than just a single option. This will, in most browsers, show up differently than a normal select field, which is typically drawn as a drop-down control that shows the options only when you open it.

+ +

Each <option> tag has a value. This value can be defined with a value attribute. When that is not given, the text inside the option will count as its value. The value property of a <select> element reflects the currently selected option. For a multiple field, though, this property doesn’t mean much since it will give the value of only one of the currently selected options.

+ +

The <option> tags for a <select> field can be accessed as an array-like object through the field’s options property. Each option has a property called selected, which indicates whether that option is currently selected. The property can also be written to select or deselect an option.

+ +

This example extracts the selected values from a multiple select field and uses them to compose a binary number from individual bits. Hold control (or command on a Mac) to select multiple options.

+ +
<select multiple>
+  <option value="1">0001</option>
+  <option value="2">0010</option>
+  <option value="4">0100</option>
+  <option value="8">1000</option>
+</select> = <span id="output">0</span>
+<script>
+  let select = document.querySelector("select");
+  let output = document.querySelector("#output");
+  select.addEventListener("change", () => {
+    let number = 0;
+    for (let option of Array.from(select.options)) {
+      if (option.selected) {
+        number += Number(option.value);
+      }
+    }
+    output.textContent = number;
+  });
+</script>
+ +

File fields

+ +

File fields were originally designed as a way to upload files from the user’s machine through a form. In modern browsers, they also provide a way to read such files from JavaScript programs. The field acts as a kind of gatekeeper. The script cannot simply start reading private files from the user’s computer, but if the user selects a file in such a field, the browser interprets that action to mean that the script may read the file.

+ +

A file field usually looks like a button labeled with something like “choose file” or “browse”, with information about the chosen file next to it.

+ +
<input type="file">
+<script>
+  let input = document.querySelector("input");
+  input.addEventListener("change", () => {
+    if (input.files.length > 0) {
+      let file = input.files[0];
+      console.log("You chose", file.name);
+      if (file.type) console.log("It has type", file.type);
+    }
+  });
+</script>
+ +

The files property of a file field element is an array-like object (again, not a real array) containing the files chosen in the field. It is initially empty. The reason there isn’t simply a file property is that file fields also support a multiple attribute, which makes it possible to select multiple files at the same time.

+ +

Objects in the files object have properties such as name (the filename), size (the file’s size in bytes, which are chunks of 8 bits), and type (the media type of the file, such as text/plain or image/jpeg).

+ +

What it does not have is a property that contains the content of the file. Getting at that is a little more involved. Since reading a file from disk can take time, the interface must be asynchronous to avoid freezing the document.

+ +
<input type="file" multiple>
+<script>
+  let input = document.querySelector("input");
+  input.addEventListener("change", () => {
+    for (let file of Array.from(input.files)) {
+      let reader = new FileReader();
+      reader.addEventListener("load", () => {
+        console.log("File", file.name, "starts with",
+                    reader.result.slice(0, 20));
+      });
+      reader.readAsText(file);
+    }
+  });
+</script>
+ +

Reading a file is done by creating a FileReader object, registering a "load" event handler for it, and calling its readAsText method, giving it the file we want to read. Once loading finishes, the reader’s result property contains the file’s content.

+ +

FileReaders also fire an "error" event when reading the file fails for any reason. The error object itself will end up in the reader’s error property. This interface was designed before promises became part of the language. You could wrap it in a promise like this:

+ +
function readFileText(file) {
+  return new Promise((resolve, reject) => {
+    let reader = new FileReader();
+    reader.addEventListener(
+      "load", () => resolve(reader.result));
+    reader.addEventListener(
+      "error", () => reject(reader.error));
+    reader.readAsText(file);
+  });
+}
+ +

Storing data client-side

+ +

Simple HTML pages with a bit of JavaScript can be a great format for “mini applications”—small helper programs that automate basic tasks. By connecting a few form fields with event handlers, you can do anything from converting between centimeters and inches to computing passwords from a master password and a website name.

+ +

When such an application needs to remember something between sessions, you cannot use JavaScript bindings—those are thrown away every time the page is closed. You could set up a server, connect it to the Internet, and have your application store something there. We will see how to do that in Chapter 20. But that’s a lot of extra work and complexity. Sometimes it is enough to just keep the data in the browser.

+ +

The localStorage object can be used to store data in a way that survives page reloads. This object allows you to file string values under names.

+ +
localStorage.setItem("username", "marijn");
+console.log(localStorage.getItem("username"));
+// → marijn
+localStorage.removeItem("username");
+ +

A value in localStorage sticks around until it is overwritten, it is removed with removeItem, or the user clears their local data.

+ +

Sites from different domains get different storage compartments. That means data stored in localStorage by a given website can, in principle, be read (and overwritten) only by scripts on that same site.

+ +

Browsers do enforce a limit on the size of the data a site can store in localStorage. That restriction, along with the fact that filling up people’s hard drives with junk is not really profitable, prevents the feature from eating up too much space.

+ +

The following code implements a crude note-taking application. It keeps a set of named notes and allows the user to edit notes and create new ones.

+ +
Notes: <select></select> <button>Add</button><br>
+<textarea style="width: 100%"></textarea>
+
+<script>
+  let list = document.querySelector("select");
+  let note = document.querySelector("textarea");
+
+  let state;
+  function setState(newState) {
+    list.textContent = "";
+    for (let name of Object.keys(newState.notes)) {
+      let option = document.createElement("option");
+      option.textContent = name;
+      if (newState.selected == name) option.selected = true;
+      list.appendChild(option);
+    }
+    note.value = newState.notes[newState.selected];
+
+    localStorage.setItem("Notes", JSON.stringify(newState));
+    state = newState;
+  }
+  setState(JSON.parse(localStorage.getItem("Notes")) || {
+    notes: {"shopping list": "Carrots\nRaisins"},
+    selected: "shopping list"
+  });
+
+  list.addEventListener("change", () => {
+    setState({notes: state.notes, selected: list.value});
+  });
+  note.addEventListener("change", () => {
+    setState({
+      notes: Object.assign({}, state.notes,
+                           {[state.selected]: note.value}),
+      selected: state.selected
+    });
+  });
+  document.querySelector("button")
+    .addEventListener("click", () => {
+      let name = prompt("Note name");
+      if (name) setState({
+        notes: Object.assign({}, state.notes, {[name]: ""}),
+        selected: name
+      });
+    });
+</script>
+ +

The script gets its starting state from the "Notes" value stored in localStorage or, if that is missing, creates an example state that has only a shopping list in it. Reading a field that does not exist from localStorage will yield null. Passing null to JSON.parse will make it parse the string "null" and return null. Thus, the || operator can be used to provide a default value in a situation like this.

+ +

The setState method makes sure the DOM is showing a given state and stores the new state to localStorage. Event handlers call this function to move to a new state.

+ +

The use of Object.assign in the example is intended to create a new object that is a clone of the old state.notes, but with one property added or overwritten. Object.assign takes its first argument and adds all properties from any further arguments to it. Thus, giving it an empty object will cause it to fill a fresh object. The square +brackets notation in the third argument is used to create a property whose name is based on some dynamic value.

+ +

There is another object, similar to localStorage, called sessionStorage. The difference between the two is that the content of sessionStorage is forgotten at the end of each session, which for most browsers means whenever the browser is closed.

+ +

Summary

+ +

In this chapter, we discussed how the HTTP protocol works. A client sends a request, which contains a method (usually GET) and a path that identifies a resource. The server then decides what to do with the request and responds with a status code and a response body. Both requests and responses may contain headers that provide additional information.

+ +

The interface through which browser JavaScript can make HTTP requests is called fetch. Making a request looks like this:

+ +
fetch("/18_http.html").then(r => r.text()).then(text => {
+  console.log(`The page starts with ${text.slice(0, 15)}`);
+});
+ +

Browsers make GET requests to fetch the resources needed to display a web page. A page may also contain forms, which allow information entered by the user to be sent as a request for a new page when the form is submitted.

+ +

HTML can represent various types of form fields, such as text fields, checkboxes, multiple-choice fields, and file pickers.

+ +

Such fields can be inspected and manipulated with JavaScript. They fire the "change" event when changed, fire the "input" event when text is typed, and receive keyboard events when they have keyboard focus. Properties like value (for text and select fields) or checked (for checkboxes and radio buttons) are used to read or set the field’s content.

+ +

When a form is submitted, a "submit" event is fired on it. A JavaScript handler can call preventDefault on that event to disable the browser’s default behavior. Form field elements may also occur outside of a form tag.

+ +

When the user has selected a file from their local file system in a file picker field, the FileReader interface can be used to access the content of this file from a JavaScript program.

+ +

The localStorage and sessionStorage objects can be used to save information in a way that survives page reloads. The first object saves the data forever (or until the user decides to clear it), and the second saves it until the browser is closed.

+ +

Exercises

+ +

Content negotiation

+ +

One of the things HTTP can do is called content negotiation. The Accept request header is used to tell the server what type of document the client would like to get. Many servers ignore this header, but when a server knows of various ways to encode a resource, it can look at this header and send the one that the client prefers.

+ +

The URL https://eloquentjavascript.net/author is configured to respond with either plaintext, HTML, or JSON, depending on what the client asks for. These formats are identified by the standardized media types text/plain, text/html, and application/json.

+ +

Send requests to fetch all three formats of this resource. Use the headers property in the options object passed to fetch to set the header named Accept to the desired media type.

+ +

Finally, try asking for the media type application/rainbows+unicorns and see which status code that produces.

+ +
// Your code here.
+ +
+ +

Base your code on the fetch examples earlier in the chapter.

+ +

Asking for a bogus media type will return a response with code 406, “Not acceptable”, which is the code a server should return when it can’t fulfill the Accept header.

+ +
+ +

A JavaScript workbench

+ +

Build an interface that allows people to type and run pieces of JavaScript code.

+ +

Put a button next to a <textarea> field that, when pressed, uses the Function constructor we saw in Chapter 10 to wrap the text in a function and call it. Convert the return value of the function, or any error it raises, to a string and display it below the text field.

+ +
<textarea id="code">return "hi";</textarea>
+<button id="button">Run</button>
+<pre id="output"></pre>
+
+<script>
+  // Your code here.
+</script>
+ +
+ +

Use document.querySelector or document.getElementById to get access to the elements defined in your HTML. An event handler for "click" or "mousedown" events on the button can get the value property of the text field and call Function on it.

+ +

Make sure you wrap both the call to Function and the call to its result in a try block so you can catch the exceptions it produces. In this case, we really don’t know what type of exception we are looking for, so catch everything.

+ +

The textContent property of the output element can be used to fill it with a string message. Or, if you want to keep the old content around, create a new text node using document.createTextNode and append it to the element. Remember to add a newline character to the end so that not all output appears on a single line.

+ +
+ +

Conway’s Game of Life

+ +

Conway’s Game of Life is a simple simulation that creates artificial “life” on a grid, each cell of which is either alive or not. Each generation (turn), the following rules are applied:

+ +
    + +
  • + +

    Any live cell with fewer than two or more than three live neighbors dies.

  • + +
  • + +

    Any live cell with two or three live neighbors lives on to the next generation.

  • + +
  • + +

    Any dead cell with exactly three live neighbors becomes a live cell.

+ +

A neighbor is defined as any adjacent cell, including diagonally adjacent ones.

+ +

Note that these rules are applied to the whole grid at once, not one square at a time. That means the counting of neighbors is based on the situation at the start of the generation, and changes happening to neighbor cells during this generation should not influence the new state of a given cell.

+ +

Implement this game using whichever data structure you find appropriate. Use Math.random to populate the grid with a random pattern initially. Display it as a grid of checkbox fields, with a button next to it to advance to the next generation. When the user checks or unchecks the checkboxes, their changes should be included when computing the next generation.

+ +
<div id="grid"></div>
+<button id="next">Next generation</button>
+
+<script>
+  // Your code here.
+</script>
+ +
+ +

To solve the problem of having the changes conceptually happen at the same time, try to see the computation of a generation as a pure +function, which takes one grid and produces a new grid that represents the next turn.

+ +

Representing the matrix can be done in the way shown in Chapter 6. You can count live neighbors with two nested loops, looping over adjacent coordinates in both dimensions. Take care not to count cells outside of the field and to ignore the cell in the center, whose neighbors we are counting.

+ +

Ensuring that changes to checkboxes take effect on the next generation can be done in two ways. An event handler could notice these changes and update the current grid to reflect them, or you could generate a fresh grid from the values in the checkboxes before computing the next turn.

+ +

If you choose to go with event handlers, you might want to attach attributes that identify the position that each checkbox corresponds to so that it is easy to find out which cell to change.

+ +

To draw the grid of checkboxes, you can either use a <table> element (see Chapter 14) or simply put them all in the same element and put <br> (line break) elements between the rows.

+ +
+
diff --git a/docs/19_paint.html b/docs/19_paint.html new file mode 100644 index 000000000..6cae4a0b5 --- /dev/null +++ b/docs/19_paint.html @@ -0,0 +1,767 @@ + + + + + Project: A Pixel Art Editor :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 19Project: A Pixel Art Editor

+ +
+ +

I look at the many colors before me. I look at my blank canvas. Then, I try to apply colors like words that shape poems, like notes that shape music.

+ +
Joan Miro
+ +
Picture of a tiled mosaic
+ +

The material from the previous chapters gives you all the elements you need to build a basic web application. In this chapter, we will do just that.

+ +

Our application will be a pixel drawing program, where you can modify a picture pixel by pixel by manipulating a zoomed-in view of it, shown as a grid of colored squares. You can use the program to open image files, scribble on them with your mouse or other pointer device, and save them. This is what it will look like:

The pixel editor interface, with colored pixels at the top and a number of controls below that
+ +

Painting on a computer is great. You don’t need to worry about materials, skill, or talent. You just start smearing.

+ +

Components

+ +

The interface for the application shows a big <canvas> element on top, with a number of form fields below it. The user draws on the picture by selecting a tool from a <select> field and then clicking, touching, or dragging across the canvas. There are tools for drawing single pixels or rectangles, for filling an area, and for picking a color from the picture.

+ +

We will structure the editor interface as a number of components, objects that are responsible for a piece of the DOM and that may contain other components inside them.

+ +

The state of the application consists of the current picture, the selected tool, and the selected color. We’ll set things up so that the state lives in a single value, and the interface components always base the way they look on the current state.

+ +

To see why this is important, let’s consider the alternative—distributing pieces of state throughout the interface. Up to a certain point, this is easier to program. We can just put in a color field and read its value when we need to know the current color.

+ +

But then we add the color picker—a tool that lets you click the picture to select the color of a given pixel. To keep the color field showing the correct color, that tool would have to know that it exists and update it whenever it picks a new color. If you ever add another place that makes the color visible (maybe the mouse cursor could show it), you have to update your color-changing code to keep that synchronized.

+ +

In effect, this creates a problem where each part of the interface needs to know about all other parts, which is not very modular. For small applications like the one in this chapter, that may not be a problem. For bigger projects, it can turn into a real nightmare.

+ +

To avoid this nightmare on principle, we’re going to be strict about data flow. There is a state, and the interface is drawn based on that state. An interface component may respond to user actions by updating the state, at which point the components get a chance to synchronize themselves with this new state.

+ +

In practice, each component is set up so that when it is given a new state, it also notifies its child components, insofar as those need to be updated. Setting this up is a bit of a hassle. Making this more convenient is the main selling point of many browser programming libraries. But for a small application like this, we can do it without such infrastructure.

+ +

Updates to the state are represented as objects, which we’ll call actions. Components may create such actions and dispatch them—give them to a central state management function. That function computes the next state, after which the interface components update themselves to this new state.

+ +

We’re taking the messy task of running a user interface and applying some structure to it. Though the DOM-related pieces are still full of side effects, they are held up by a conceptually simple backbone: the state update cycle. The state determines what the DOM looks like, and the only way DOM events can change the state is by dispatching actions to the state.

+ +

There are many variants of this approach, each with its own benefits and problems, but their central idea is the same: state changes should go through a single well-defined channel, not happen all over the place.

+ +

Our components will be classes conforming to an interface. Their constructor is given a state—which may be the whole application state or some smaller value if it doesn’t need access to everything—and uses that to build up a dom property. This is the DOM element that represents the component. Most constructors will also take some other values that won’t change over time, such as the function they can use to dispatch an action.

+ +

Each component has a syncState method that is used to synchronize it to a new state value. The method takes one argument, the state, which is of the same type as the first argument to its constructor.

+ +

The state

+ +

The application state will be an object with picture, tool, and color properties. The picture is itself an object that stores the width, height, and pixel content of the picture. The pixels are stored in an array, in the same way as the matrix class from Chapter 6—row by row, from top to bottom.

+ +
class Picture {
+  constructor(width, height, pixels) {
+    this.width = width;
+    this.height = height;
+    this.pixels = pixels;
+  }
+  static empty(width, height, color) {
+    let pixels = new Array(width * height).fill(color);
+    return new Picture(width, height, pixels);
+  }
+  pixel(x, y) {
+    return this.pixels[x + y * this.width];
+  }
+  draw(pixels) {
+    let copy = this.pixels.slice();
+    for (let {x, y, color} of pixels) {
+      copy[x + y * this.width] = color;
+    }
+    return new Picture(this.width, this.height, copy);
+  }
+}
+ +

We want to be able to treat a picture as an immutable value, for reasons that we’ll get back to later in the chapter. But we also sometimes need to update a whole bunch of pixels at a time. To be able to do that, the class has a draw method that expects an array of updated pixels—objects with x, y, and color properties—and creates a new picture with those pixels overwritten. This method uses slice without arguments to copy the entire pixel array—the start of the slice defaults to 0, and the end defaults to the array’s length.

+ +

The empty method uses two pieces of array functionality that we haven’t seen before. The Array constructor can be called with a number to create an empty array of the given length. The fill method can then be used to fill this array with a given value. These are used to create an array in which all pixels have the same color.

+ +

Colors are stored as strings containing traditional CSS color +codes made up of a hash sign (#) followed by six hexadecimal (base-16) digits—two for the red component, two for the green component, and two for the blue component. This is a somewhat cryptic and inconvenient way to write colors, but it is the format the HTML color input field uses, and it can be used in the fillColor property of a canvas drawing context, so for the ways we’ll use colors in this program, it is practical enough.

+ +

Black, where all components are zero, is written "#000000", and bright pink looks like "#ff00ff", where the red and blue components have the maximum value of 255, written ff in hexadecimal digits (which use a to f to represent digits 10 to 15).

+ +

We’ll allow the interface to dispatch actions as objects whose properties overwrite the properties of the previous state. The color field, when the user changes it, could dispatch an object like {color: field.value}, from which this update function can compute a new state.

+ +
function updateState(state, action) {
+  return Object.assign({}, state, action);
+}
+ +

This rather cumbersome pattern, in which Object.assign is used to first add the properties of state to an empty object and then overwrite some of those with the properties from action, is common in JavaScript code that uses immutable objects. A more convenient notation for this, in which the triple-dot operator is used to include all properties from another object in an object expression, is in the final stages of being standardized. With that addition, you could write {...state, ...action} instead. At the time of writing, this doesn’t yet work in all browsers.

+ +

DOM building

+ +

One of the main things that interface components do is creating DOM structure. We again don’t want to directly use the verbose DOM methods for that, so here’s a slightly expanded version of the elt function:

+ +
function elt(type, props, ...children) {
+  let dom = document.createElement(type);
+  if (props) Object.assign(dom, props);
+  for (let child of children) {
+    if (typeof child != "string") dom.appendChild(child);
+    else dom.appendChild(document.createTextNode(child));
+  }
+  return dom;
+}
+ +

The main difference between this version and the one we used in Chapter 16 is that it assigns properties to DOM nodes, not attributes. This means we can’t use it to set arbitrary attributes, but we can use it to set properties whose value isn’t a string, such as onclick, which can be set to a function to register a click event handler.

+ +

This allows the following style of registering event handlers:

+ +
<body>
+  <script>
+    document.body.appendChild(elt("button", {
+      onclick: () => console.log("click")
+    }, "The button"));
+  </script>
+</body>
+ +

The canvas

+ +

The first component we’ll define is the part of the interface that displays the picture as a grid of colored boxes. This component is responsible for two things: showing a picture and communicating pointer events on that picture to the rest of the application.

+ +

As such, we can define it as a component that knows about only the current picture, not the whole application state. Because it doesn’t know how the application as a whole works, it cannot directly dispatch actions. Rather, when responding to pointer events, it calls a callback function provided by the code that created it, which will handle the application-specific parts.

+ +
const scale = 10;
+
+class PictureCanvas {
+  constructor(picture, pointerDown) {
+    this.dom = elt("canvas", {
+      onmousedown: event => this.mouse(event, pointerDown),
+      ontouchstart: event => this.touch(event, pointerDown)
+    });
+    this.syncState(picture);
+  }
+  syncState(picture) {
+    if (this.picture == picture) return;
+    this.picture = picture;
+    drawPicture(this.picture, this.dom, scale);
+  }
+}
+ +

We draw each pixel as a 10-by-10 square, as determined by the scale constant. To avoid unnecessary work, the component keeps track of its current picture and does a redraw only when syncState is given a new picture.

+ +

The actual drawing function sets the size of the canvas based on the scale and picture size and fills it with a series of squares, one for each pixel.

+ +
function drawPicture(picture, canvas, scale) {
+  canvas.width = picture.width * scale;
+  canvas.height = picture.height * scale;
+  let cx = canvas.getContext("2d");
+
+  for (let y = 0; y < picture.height; y++) {
+    for (let x = 0; x < picture.width; x++) {
+      cx.fillStyle = picture.pixel(x, y);
+      cx.fillRect(x * scale, y * scale, scale, scale);
+    }
+  }
+}
+ +

When the left mouse button is pressed while the mouse is over the picture canvas, the component calls the pointerDown callback, giving it the position of the pixel that was clicked—in picture coordinates. This will be used to implement mouse interaction with the picture. The callback may return another callback function to be notified when the pointer is moved to a different pixel while the button is held down.

+ +
PictureCanvas.prototype.mouse = function(downEvent, onDown) {
+  if (downEvent.button != 0) return;
+  let pos = pointerPosition(downEvent, this.dom);
+  let onMove = onDown(pos);
+  if (!onMove) return;
+  let move = moveEvent => {
+    if (moveEvent.buttons == 0) {
+      this.dom.removeEventListener("mousemove", move);
+    } else {
+      let newPos = pointerPosition(moveEvent, this.dom);
+      if (newPos.x == pos.x && newPos.y == pos.y) return;
+      pos = newPos;
+      onMove(newPos);
+    }
+  };
+  this.dom.addEventListener("mousemove", move);
+};
+
+function pointerPosition(pos, domNode) {
+  let rect = domNode.getBoundingClientRect();
+  return {x: Math.floor((pos.clientX - rect.left) / scale),
+          y: Math.floor((pos.clientY - rect.top) / scale)};
+}
+ +

Since we know the size of the pixels and we can use getBoundingClientRect to find the position of the canvas on the screen, it is possible to go from mouse event coordinates (clientX and clientY) to picture coordinates. These are always rounded down so that they refer to a specific pixel.

+ +

With touch events, we have to do something similar, but using different events and making sure we call preventDefault on the "touchstart" event to prevent panning.

+ +
PictureCanvas.prototype.touch = function(startEvent,
+                                         onDown) {
+  let pos = pointerPosition(startEvent.touches[0], this.dom);
+  let onMove = onDown(pos);
+  startEvent.preventDefault();
+  if (!onMove) return;
+  let move = moveEvent => {
+    let newPos = pointerPosition(moveEvent.touches[0],
+                                 this.dom);
+    if (newPos.x == pos.x && newPos.y == pos.y) return;
+    pos = newPos;
+    onMove(newPos);
+  };
+  let end = () => {
+    this.dom.removeEventListener("touchmove", move);
+    this.dom.removeEventListener("touchend", end);
+  };
+  this.dom.addEventListener("touchmove", move);
+  this.dom.addEventListener("touchend", end);
+};
+ +

For touch events, clientX and clientY aren’t available directly on the event object, but we can use the coordinates of the first touch object in the touches property.

+ +

The application

+ +

To make it possible to build the application piece by piece, we’ll implement the main component as a shell around a picture canvas and a dynamic set of tools and controls that we pass to its constructor.

+ +

The controls are the interface elements that appear below the picture. They’ll be provided as an array of component constructors.

+ +

The tools do things like drawing pixels or filling in an area. The application shows the set of available tools as a <select> field. The currently selected tool determines what happens when the user interacts with the picture with a pointer device. The set of available tools is provided as an object that maps the names that appear in the drop-down field to functions that implement the tools. Such functions get a picture position, a current application state, and a dispatch function as arguments. They may return a move handler function that gets called with a new position and a current state when the pointer moves to a different pixel.

+ +
class PixelEditor {
+  constructor(state, config) {
+    let {tools, controls, dispatch} = config;
+    this.state = state;
+
+    this.canvas = new PictureCanvas(state.picture, pos => {
+      let tool = tools[this.state.tool];
+      let onMove = tool(pos, this.state, dispatch);
+      if (onMove) return pos => onMove(pos, this.state);
+    });
+    this.controls = controls.map(
+      Control => new Control(state, config));
+    this.dom = elt("div", {}, this.canvas.dom, elt("br"),
+                   ...this.controls.reduce(
+                     (a, c) => a.concat(" ", c.dom), []));
+  }
+  syncState(state) {
+    this.state = state;
+    this.canvas.syncState(state.picture);
+    for (let ctrl of this.controls) ctrl.syncState(state);
+  }
+}
+ +

The pointer handler given to PictureCanvas calls the currently selected tool with the appropriate arguments and, if that returns a move handler, adapts it to also receive the state.

+ +

All controls are constructed and stored in this.controls so that they can be updated when the application state changes. The call to reduce introduces spaces between the controls’ DOM elements. That way they don’t look so pressed together.

+ +

The first control is the tool selection menu. It creates a <select> element with an option for each tool and sets up a "change" event handler that updates the application state when the user selects a different tool.

+ +
class ToolSelect {
+  constructor(state, {tools, dispatch}) {
+    this.select = elt("select", {
+      onchange: () => dispatch({tool: this.select.value})
+    }, ...Object.keys(tools).map(name => elt("option", {
+      selected: name == state.tool
+    }, name)));
+    this.dom = elt("label", null, "🖌 Tool: ", this.select);
+  }
+  syncState(state) { this.select.value = state.tool; }
+}
+ +

By wrapping the label text and the field in a <label> element, we tell the browser that the label belongs to that field so that you can, for example, click the label to focus the field.

+ +

We also need to be able to change the color, so let’s add a control for that. An HTML <input> element with a type attribute of color gives us a form field that is specialized for selecting colors. Such a field’s value is always a CSS color code in "#RRGGBB" format (red, green, and blue components, two digits per color). The browser will show a color picker interface when the user interacts with it.

+ +

This control creates such a field and wires it up to stay synchronized with the application state’s color property.

+ +
class ColorSelect {
+  constructor(state, {dispatch}) {
+    this.input = elt("input", {
+      type: "color",
+      value: state.color,
+      onchange: () => dispatch({color: this.input.value})
+    });
+    this.dom = elt("label", null, "🎨 Color: ", this.input);
+  }
+  syncState(state) { this.input.value = state.color; }
+}
+ +

Drawing tools

+ +

Before we can draw anything, we need to implement the tools that will control the functionality of mouse or touch events on the canvas.

+ +

The most basic tool is the draw tool, which changes any pixel you click or tap to the currently selected color. It dispatches an action that updates the picture to a version in which the pointed-at pixel is given the currently selected color.

+ +
function draw(pos, state, dispatch) {
+  function drawPixel({x, y}, state) {
+    let drawn = {x, y, color: state.color};
+    dispatch({picture: state.picture.draw([drawn])});
+  }
+  drawPixel(pos, state);
+  return drawPixel;
+}
+ +

The function immediately calls the drawPixel function but then also returns it so that it is called again for newly touched pixels when the user drags or swipes over the picture.

+ +

To draw larger shapes, it can be useful to quickly create rectangles. The rectangle tool draws a rectangle between the point where you start dragging and the point that you drag to.

+ +
function rectangle(start, state, dispatch) {
+  function drawRectangle(pos) {
+    let xStart = Math.min(start.x, pos.x);
+    let yStart = Math.min(start.y, pos.y);
+    let xEnd = Math.max(start.x, pos.x);
+    let yEnd = Math.max(start.y, pos.y);
+    let drawn = [];
+    for (let y = yStart; y <= yEnd; y++) {
+      for (let x = xStart; x <= xEnd; x++) {
+        drawn.push({x, y, color: state.color});
+      }
+    }
+    dispatch({picture: state.picture.draw(drawn)});
+  }
+  drawRectangle(start);
+  return drawRectangle;
+}
+ +

An important detail in this implementation is that when dragging, the rectangle is redrawn on the picture from the original state. That way, you can make the rectangle larger and smaller again while creating it, without the intermediate rectangles sticking around in the final picture. This is one of the reasons why immutable picture objects are useful—we’ll see another reason later.

+ +

Implementing flood fill is somewhat more involved. This is a tool that fills the pixel under the pointer and all adjacent pixels that have the same color. “Adjacent” means directly horizontally or vertically adjacent, not diagonally. This picture illustrates the set of pixels colored when the flood fill tool is used at the marked pixel:

A pixel grid showing the area filled by a flood fill operation
+ +

Interestingly, the way we’ll do this looks a bit like the pathfinding code from Chapter 7. Whereas that code searched through a graph to find a route, this code searches through a grid to find all “connected” pixels. The problem of keeping track of a branching set of possible routes is similar.

+ +
const around = [{dx: -1, dy: 0}, {dx: 1, dy: 0},
+                {dx: 0, dy: -1}, {dx: 0, dy: 1}];
+
+function fill({x, y}, state, dispatch) {
+  let targetColor = state.picture.pixel(x, y);
+  let drawn = [{x, y, color: state.color}];
+  for (let done = 0; done < drawn.length; done++) {
+    for (let {dx, dy} of around) {
+      let x = drawn[done].x + dx, y = drawn[done].y + dy;
+      if (x >= 0 && x < state.picture.width &&
+          y >= 0 && y < state.picture.height &&
+          state.picture.pixel(x, y) == targetColor &&
+          !drawn.some(p => p.x == x && p.y == y)) {
+        drawn.push({x, y, color: state.color});
+      }
+    }
+  }
+  dispatch({picture: state.picture.draw(drawn)});
+}
+ +

The array of drawn pixels doubles as the function’s work list. For each pixel reached, we have to see whether any adjacent pixels have the same color and haven’t already been painted over. The loop counter lags behind the length of the drawn array as new pixels are added. Any pixels ahead of it still need to be explored. When it catches up with the length, no unexplored pixels remain, and the function is done.

+ +

The final tool is a color picker, which allows you to point at a color in the picture to use it as the current drawing color.

+ +
function pick(pos, state, dispatch) {
+  dispatch({color: state.picture.pixel(pos.x, pos.y)});
+}
+ +

We can now test our application!

+ +
<div></div>
+<script>
+  let state = {
+    tool: "draw",
+    color: "#000000",
+    picture: Picture.empty(60, 30, "#f0f0f0")
+  };
+  let app = new PixelEditor(state, {
+    tools: {draw, fill, rectangle, pick},
+    controls: [ToolSelect, ColorSelect],
+    dispatch(action) {
+      state = updateState(state, action);
+      app.syncState(state);
+    }
+  });
+  document.querySelector("div").appendChild(app.dom);
+</script>
+ +

Saving and loading

+ +

When we’ve drawn our masterpiece, we’ll want to save it for later. We should add a button for downloading the current picture as an image file. This control provides that button:

+ +
class SaveButton {
+  constructor(state) {
+    this.picture = state.picture;
+    this.dom = elt("button", {
+      onclick: () => this.save()
+    }, "💾 Save");
+  }
+  save() {
+    let canvas = elt("canvas");
+    drawPicture(this.picture, canvas, 1);
+    let link = elt("a", {
+      href: canvas.toDataURL(),
+      download: "pixelart.png"
+    });
+    document.body.appendChild(link);
+    link.click();
+    link.remove();
+  }
+  syncState(state) { this.picture = state.picture; }
+}
+ +

The component keeps track of the current picture so that it can access it when saving. To create the image file, it uses a <canvas> element that it draws the picture on (at a scale of one pixel per pixel).

+ +

The toDataURL method on a canvas element creates a URL that starts with data:. Unlike http: and https: URLs, data URLs contain the whole resource in the URL. They are usually very long, but they allow us to create working links to arbitrary pictures, right here in the browser.

+ +

To actually get the browser to download the picture, we then create a link element that points at this URL and has a download attribute. Such links, when clicked, make the browser show a file save dialog. We add that link to the document, simulate a click on it, and remove it again.

+ +

You can do a lot with browser technology, but sometimes the way to do it is rather odd.

+ +

And it gets worse. We’ll also want to be able to load existing image files into our application. To do that, we again define a button component.

+ +
class LoadButton {
+  constructor(_, {dispatch}) {
+    this.dom = elt("button", {
+      onclick: () => startLoad(dispatch)
+    }, "📁 Load");
+  }
+  syncState() {}
+}
+
+function startLoad(dispatch) {
+  let input = elt("input", {
+    type: "file",
+    onchange: () => finishLoad(input.files[0], dispatch)
+  });
+  document.body.appendChild(input);
+  input.click();
+  input.remove();
+}
+ +

To get access to a file on the user’s computer, we need the user to select the file through a file input field. But I don’t want the load button to look like a file input field, so we create the file input when the button is clicked and then pretend that this file input itself was clicked.

+ +

When the user has selected a file, we can use FileReader to get access to its contents, again as a data URL. That URL can be used to create an <img> element, but because we can’t get direct access to the pixels in such an image, we can’t create a Picture object from that.

+ +
function finishLoad(file, dispatch) {
+  if (file == null) return;
+  let reader = new FileReader();
+  reader.addEventListener("load", () => {
+    let image = elt("img", {
+      onload: () => dispatch({
+        picture: pictureFromImage(image)
+      }),
+      src: reader.result
+    });
+  });
+  reader.readAsDataURL(file);
+}
+ +

To get access to the pixels, we must first draw the picture to a <canvas> element. The canvas context has a getImageData method that allows a script to read its pixels. So, once the picture is on the canvas, we can access it and construct a Picture object.

+ +
function pictureFromImage(image) {
+  let width = Math.min(100, image.width);
+  let height = Math.min(100, image.height);
+  let canvas = elt("canvas", {width, height});
+  let cx = canvas.getContext("2d");
+  cx.drawImage(image, 0, 0);
+  let pixels = [];
+  let {data} = cx.getImageData(0, 0, width, height);
+
+  function hex(n) {
+    return n.toString(16).padStart(2, "0");
+  }
+  for (let i = 0; i < data.length; i += 4) {
+    let [r, g, b] = data.slice(i, i + 3);
+    pixels.push("#" + hex(r) + hex(g) + hex(b));
+  }
+  return new Picture(width, height, pixels);
+}
+ +

We’ll limit the size of images to 100 by 100 pixels since anything bigger will look huge on our display and might slow down the interface.

+ +

The data property of the object returned by getImageData is an array of color components. For each pixel in the rectangle specified by the arguments, it contains four values, which represent the red, green, blue, and alpha components of the pixel’s color, as numbers between 0 and 255. The alpha part represents opacity—when it is zero, the pixel is fully transparent, and when it is 255, it is fully opaque. For our purpose, we can ignore it.

+ +

The two hexadecimal digits per component, as used in our color notation, correspond precisely to the 0 to 255 range—two base-16 digits can express 162 = 256 different numbers. The toString method of numbers can be given a base as argument, so n.toString(16) will produce a string representation in base 16. We have to make sure that each number takes up two digits, so the hex helper function calls padStart to add a leading zero when necessary.

+ +

We can load and save now! That leaves one more feature before we’re done.

+ +

Undo history

+ +

Half of the process of editing is making little mistakes and correcting them. So an important feature in a drawing program is an undo history.

+ +

To be able to undo changes, we need to store previous versions of the picture. Since it’s an immutable value, that is easy. But it does require an additional field in the application state.

+ +

We’ll add a done array to keep previous versions of the picture. Maintaining this property requires a more complicated state update function that adds pictures to the array.

+ +

But we don’t want to store every change, only changes a certain amount of time apart. To be able to do that, we’ll need a second property, doneAt, tracking the time at which we last stored a picture in the history.

+ +
function historyUpdateState(state, action) {
+  if (action.undo == true) {
+    if (state.done.length == 0) return state;
+    return Object.assign({}, state, {
+      picture: state.done[0],
+      done: state.done.slice(1),
+      doneAt: 0
+    });
+  } else if (action.picture &&
+             state.doneAt < Date.now() - 1000) {
+    return Object.assign({}, state, action, {
+      done: [state.picture, ...state.done],
+      doneAt: Date.now()
+    });
+  } else {
+    return Object.assign({}, state, action);
+  }
+}
+ +

When the action is an undo action, the function takes the most recent picture from the history and makes that the current picture. It sets doneAt to zero so that the next change is guaranteed to store the picture back in the history, allowing you to revert to it another time if you want.

+ +

Otherwise, if the action contains a new picture and the last time we stored something is more than a second (1000 milliseconds) ago, the done and doneAt properties are updated to store the previous picture.

+ +

The undo button component doesn’t do much. It dispatches undo actions when clicked and disables itself when there is nothing to undo.

+ +
class UndoButton {
+  constructor(state, {dispatch}) {
+    this.dom = elt("button", {
+      onclick: () => dispatch({undo: true}),
+      disabled: state.done.length == 0
+    }, "⮪ Undo");
+  }
+  syncState(state) {
+    this.dom.disabled = state.done.length == 0;
+  }
+}
+ +

Let’s draw

+ +

To set up the application, we need to create a state, a set of tools, a set of controls, and a dispatch function. We can pass them to the PixelEditor constructor to create the main component. Since we’ll need to create several editors in the exercises, we first define some bindings.

+ +
const startState = {
+  tool: "draw",
+  color: "#000000",
+  picture: Picture.empty(60, 30, "#f0f0f0"),
+  done: [],
+  doneAt: 0
+};
+
+const baseTools = {draw, fill, rectangle, pick};
+
+const baseControls = [
+  ToolSelect, ColorSelect, SaveButton, LoadButton, UndoButton
+];
+
+function startPixelEditor({state = startState,
+                           tools = baseTools,
+                           controls = baseControls}) {
+  let app = new PixelEditor(state, {
+    tools,
+    controls,
+    dispatch(action) {
+      state = historyUpdateState(state, action);
+      app.syncState(state);
+    }
+  });
+  return app.dom;
+}
+ +

When destructuring an object or array, you can use = after a binding name to give the binding a default value, which is used when the property is missing or holds undefined. The startPixelEditor function makes use of this to accept an object with a number of optional properties as an argument. If you don’t provide a tools property, for example, tools will be bound to baseTools.

+ +

This is how we get an actual editor on the screen:

+ +
<div></div>
+<script>
+  document.querySelector("div")
+    .appendChild(startPixelEditor({}));
+</script>
+ +

Go ahead and draw something. I’ll wait.

+ +

Why is this so hard?

+ +

Browser technology is amazing. It provides a powerful set of interface building blocks, ways to style and manipulate them, and tools to inspect and debug your applications. The software you write for the browser can be run on almost every computer and phone on the planet.

+ +

At the same time, browser technology is ridiculous. You have to learn a large number of silly tricks and obscure facts to master it, and the default programming model it provides is so problematic that most programmers prefer to cover it in several layers of abstraction rather than deal with it directly.

+ +

And though the situation is definitely improving, it mostly does so in the form of more elements being added to address shortcomings—creating even more complexity. A feature used by a million websites can’t really be replaced. Even if it could, it would be hard to decide what it should be replaced with.

+ +

Technology never exists in a vacuum—we’re constrained by our tools and the social, economic, and historical factors that produced them. This can be annoying, but it is generally more productive to try to build a good understanding of how the existing technical reality works—and why it is the way it is—than to rage against it or hold out for another reality.

+ +

New abstractions can be helpful. The component model and data +flow convention I used in this chapter is a crude form of that. As mentioned, there are libraries that try to make user interface programming more pleasant. At the time of writing, React and Angular are popular choices, but there’s a whole cottage industry of such frameworks. If you’re interested in programming web applications, I recommend investigating a few of them to understand how they work and what benefits they provide.

+ +

Exercises

+ +

There is still room for improvement in our program. Let’s add a few more features as exercises.

+ +

Keyboard bindings

+ +

Add keyboard shortcuts to the application. The first letter of a tool’s name selects the tool, and control-Z or command-Z activates undo.

+ +

Do this by modifying the PixelEditor component. Add a tabIndex property of 0 to the wrapping <div> element so that it can receive keyboard focus. Note that the property corresponding to the tabindex attribute is called tabIndex, with a capital I, and our elt function expects property names. Register the key event handlers directly on that element. This means you have to click, touch, or tab to the application before you can interact with it with the keyboard.

+ +

Remember that keyboard events have ctrlKey and metaKey (for the command key on Mac) properties that you can use to see whether those keys are held down.

+ +
<div></div>
+<script>
+  // The original PixelEditor class. Extend the constructor.
+  class PixelEditor {
+    constructor(state, config) {
+      let {tools, controls, dispatch} = config;
+      this.state = state;
+
+      this.canvas = new PictureCanvas(state.picture, pos => {
+        let tool = tools[this.state.tool];
+        let onMove = tool(pos, this.state, dispatch);
+        if (onMove) {
+          return pos => onMove(pos, this.state, dispatch);
+        }
+      });
+      this.controls = controls.map(
+        Control => new Control(state, config));
+      this.dom = elt("div", {}, this.canvas.dom, elt("br"),
+                     ...this.controls.reduce(
+                       (a, c) => a.concat(" ", c.dom), []));
+    }
+    syncState(state) {
+      this.state = state;
+      this.canvas.syncState(state.picture);
+      for (let ctrl of this.controls) ctrl.syncState(state);
+    }
+  }
+
+  document.querySelector("div")
+    .appendChild(startPixelEditor({}));
+</script>
+ +
+ +

The key property of events for letter keys will be the lowercase letter itself, if shift isn’t being held. We’re not interested in key events with shift here.

+ +

A "keydown" handler can inspect its event object to see whether it matches any of the shortcuts. You can automatically get the list of first letters from the tools object so that you don’t have to write them out.

+ +

When the key event matches a shortcut, call preventDefault on it and dispatch the appropriate action.

+ +
+ +

Efficient drawing

+ +

During drawing, the majority of work that our application does happens in drawPicture. Creating a new state and updating the rest of the DOM isn’t very expensive, but repainting all the pixels on the canvas is quite a bit of work.

+ +

Find a way to make the syncState method of PictureCanvas faster by redrawing only the pixels that actually changed.

+ +

Remember that drawPicture is also used by the save button, so if you change it, either make sure the changes don’t break the old use or create a new version with a different name.

+ +

Also note that changing the size of a <canvas> element, by setting its width or height properties, clears it, making it entirely transparent again.

+ +
<div></div>
+<script>
+  // Change this method
+  PictureCanvas.prototype.syncState = function(picture) {
+    if (this.picture == picture) return;
+    this.picture = picture;
+    drawPicture(this.picture, this.dom, scale);
+  };
+
+  // You may want to use or change this as well
+  function drawPicture(picture, canvas, scale) {
+    canvas.width = picture.width * scale;
+    canvas.height = picture.height * scale;
+    let cx = canvas.getContext("2d");
+
+    for (let y = 0; y < picture.height; y++) {
+      for (let x = 0; x < picture.width; x++) {
+        cx.fillStyle = picture.pixel(x, y);
+        cx.fillRect(x * scale, y * scale, scale, scale);
+      }
+    }
+  }
+
+  document.querySelector("div")
+    .appendChild(startPixelEditor({}));
+</script>
+ +
+ +

This exercise is a good example of how immutable data structures can make code faster. Because we have both the old and the new picture, we can compare them and redraw only the pixels that changed color, saving more than 99 percent of the drawing work in most cases.

+ +

You can either write a new function updatePicture or have drawPicture take an extra argument, which may be undefined or the previous picture. For each pixel, the function checks whether a previous picture was passed with the same color at this position and skips the pixel when that is the case.

+ +

Because the canvas gets cleared when we change its size, you should also avoid touching its width and height properties when the old picture and the new picture have the same size. If they are different, which will happen when a new picture has been loaded, you can set the binding holding the old picture to null after changing the canvas size because you shouldn’t skip any pixels after you’ve changed the canvas size.

+ +
+ +

Circles

+ +

Define a tool called circle that draws a filled circle when you drag. The center of the circle lies at the point where the drag or touch gesture starts, and its radius is determined by the distance dragged.

+ +
<div></div>
+<script>
+  function circle(pos, state, dispatch) {
+    // Your code here
+  }
+
+  let dom = startPixelEditor({
+    tools: Object.assign({}, baseTools, {circle})
+  });
+  document.querySelector("div").appendChild(dom);
+</script>
+ +
+ +

You can take some inspiration from the rectangle tool. Like that tool, you’ll want to keep drawing on the starting picture, rather than the current picture, when the pointer moves.

+ +

To figure out which pixels to color, you can use the Pythagorean +theorem. First figure out the distance between the current pointer position and the start position by taking the square root (Math.sqrt) of the sum of the square (Math.pow(x, 2)) of the difference in x-coordinates and the square of the difference in y-coordinates. Then loop over a square of pixels around the start position, whose sides are at least twice the radius, and color those that are within the circle’s radius, again using the Pythagorean formula to figure out their distance from the center.

+ +

Make sure you don’t try to color pixels that are outside of the picture’s boundaries.

+ +
+ +

Proper lines

+ +

This is a more advanced exercise than the preceding two, and it will require you to design a solution to a nontrivial problem. Make sure you have plenty of time and patience before starting to work on this exercise, and do not get discouraged by initial failures.

+ +

On most browsers, when you select the draw tool and quickly drag across the picture, you don’t get a closed line. Rather, you get dots with gaps between them because the "mousemove" or "touchmove" events did not fire quickly enough to hit every pixel.

+ +

Improve the draw tool to make it draw a full line. This means you have to make the motion handler function remember the previous position and connect that to the current one.

+ +

To do this, since the pixels can be an arbitrary distance apart, you’ll have to write a general line drawing function.

+ +

A line between two pixels is a connected chain of pixels, as straight as possible, going from the start to the end. Diagonally adjacent pixels count as a connected. So a slanted line should look like the picture on the left, not the picture on the right.

Two pixelated lines, one light, skipping across pixels diagonally, and one heavy, with all pixels connected horizontally or vertically
+ +

Finally, if we have code that draws a line between two arbitrary points, we might as well use it to also define a line tool, which draws a straight line between the start and end of a drag.

+ +
<div></div>
+<script>
+  // The old draw tool. Rewrite this.
+  function draw(pos, state, dispatch) {
+    function drawPixel({x, y}, state) {
+      let drawn = {x, y, color: state.color};
+      dispatch({picture: state.picture.draw([drawn])});
+    }
+    drawPixel(pos, state);
+    return drawPixel;
+  }
+
+  function line(pos, state, dispatch) {
+    // Your code here
+  }
+
+  let dom = startPixelEditor({
+    tools: {draw, line, fill, rectangle, pick}
+  });
+  document.querySelector("div").appendChild(dom);
+</script>
+ +
+ +

The thing about the problem of drawing a pixelated line is that it is really four similar but slightly different problems. Drawing a horizontal line from the left to the right is easy—you loop over the x-coordinates and color a pixel at every step. If the line has a slight slope (less than 45 degrees or ¼π radians), you can interpolate the y-coordinate along the slope. You still need one pixel per x position, with the y position of those pixels determined by the slope.

+ +

But as soon as your slope goes across 45 degrees, you need to switch the way you treat the coordinates. You now need one pixel per y position since the line goes up more than it goes left. And then, when you cross 135 degrees, you have to go back to looping over the x-coordinates, but from right to left.

+ +

You don’t actually have to write four loops. Since drawing a line from A to B is the same as drawing a line from B to A, you can swap the start and end positions for lines going from right to left and treat them as going left to right.

+ +

So you need two different loops. The first thing your line drawing function should do is check whether the difference between the x-coordinates is larger than the difference between the y-coordinates. If it is, this is a horizontal-ish line, and if not, a vertical-ish one.

+ +

Make sure you compare the absolute values of the x and y difference, which you can get with Math.abs.

+ +

Once you know along which axis you will be looping, you can check whether the start point has a higher coordinate along that axis than the endpoint and swap them if necessary. A succinct way to swap the values of two bindings in JavaScript uses destructuring assignment like this:

+ +
[start, end] = [end, start];
+ +

Then you can compute the slope of the line, which determines the amount the coordinate on the other axis changes for each step you take along your main axis. With that, you can run a loop along the main axis while also tracking the corresponding position on the other axis, and you can draw pixels on every iteration. Make sure you round the non-main axis coordinates since they are likely to be fractional and the draw method doesn’t respond well to fractional coordinates.

+ +
+
diff --git a/docs/20_node.html b/docs/20_node.html new file mode 100644 index 000000000..3e748eda1 --- /dev/null +++ b/docs/20_node.html @@ -0,0 +1,548 @@ + + + + + Node.js :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 20Node.js

+ +
+ +

A student asked, ‘The programmers of old used only simple machines and no programming languages, yet they made beautiful programs. Why do we use complicated machines and programming languages?’. Fu-Tzu replied, ‘The builders of old used only sticks and clay, yet they made beautiful huts.’

+ +
Master Yuan-Ma, The Book of Programming
+ +
Picture of a telephone pole
+ +

So far, we have used the JavaScript language in a single environment: the browser. This chapter and the next one will briefly introduce Node.js, a program that allows you to apply your JavaScript skills outside of the browser. With it, you can build anything from small command line tools to HTTP servers that power dynamic websites.

+ +

These chapters aim to teach you the main concepts that Node.js uses and to give you enough information to write useful programs for it. They do not try to be a complete, or even a thorough, treatment of the platform.

+ +

Whereas you could run the code in previous chapters directly on these pages, because it was either raw JavaScript or written for the browser, the code samples in this chapter are written for Node and often won’t run in the browser.

+ +

If you want to follow along and run the code in this chapter, you’ll need to install Node.js version 10.1 or higher. To do so, go to https://nodejs.org and follow the installation instructions for your operating system. You can also find further documentation for Node.js there.

+ +

Background

+ +

One of the more difficult problems with writing systems that communicate over the network is managing input and output—that is, the reading and writing of data to and from the network and hard +drive. Moving data around takes time, and scheduling it cleverly can make a big difference in how quickly a system responds to the user or to network requests.

+ +

In such programs, asynchronous programming is often helpful. It allows the program to send and receive data from and to multiple devices at the same time without complicated thread management and synchronization.

+ +

Node was initially conceived for the purpose of making asynchronous programming easy and convenient. JavaScript lends itself well to a system like Node. It is one of the few programming languages that does not have a built-in way to do in- and output. Thus, JavaScript could be fit onto Node’s rather eccentric approach to in- and output without ending up with two inconsistent interfaces. In 2009, when Node was being designed, people were already doing callback-based programming in the browser, so the community around the language was used to an asynchronous programming style.

+ +

The node command

+ +

When Node.js is installed on a system, it provides a program called node, which is used to run JavaScript files. Say you have a file hello.js, containing this code:

+ +
let message = "Hello world";
+console.log(message);
+ +

You can then run node from the command line like this to execute the program:

+ +
$ node hello.js
+Hello world
+ +

The console.log method in Node does something similar to what it does in the browser. It prints out a piece of text. But in Node, the text will go to the process’s standard output stream, rather than to a browser’s JavaScript console. When running node from the command line, that means you see the logged values in your terminal.

+ +

If you run node without giving it a file, it provides you with a prompt at which you can type JavaScript code and immediately see the result.

+ +
$ node
+> 1 + 1
+2
+> [-1, -2, -3].map(Math.abs)
+[1, 2, 3]
+> process.exit(0)
+$
+ +

The process binding, just like the console binding, is available globally in Node. It provides various ways to inspect and manipulate the current program. The exit method ends the process and can be given an exit status code, which tells the program that started node (in this case, the command line shell) whether the program completed successfully (code zero) or encountered an error (any other code).

+ +

To find the command line arguments given to your script, you can read process.argv, which is an array of strings. Note that it also includes the name of the node command and your script name, so the actual arguments start at index 2. If showargv.js contains the statement console.log(process.argv), you could run it like this:

+ +
$ node showargv.js one --and two
+["node", "/tmp/showargv.js", "one", "--and", "two"]
+ +

All the standard JavaScript global bindings, such as Array, Math, and JSON, are also present in Node’s environment. Browser-related functionality, such as document or prompt, is not.

+ +

Modules

+ +

Beyond the bindings I mentioned, such as console and process, Node puts few additional bindings in the global scope. If you want to access built-in functionality, you have to ask the module system for it.

+ +

The CommonJS module system, based on the require function, was described in Chapter 10. This system is built into Node and is used to load anything from built-in modules to downloaded packages to files that are part of your own program.

+ +

When require is called, Node has to resolve the given string to an actual file that it can load. Pathnames that start with /, ./, or ../ are resolved relative to the current module’s path, where . stands for the current directory, ../ for one directory up, and / for the root of the file system. So if you ask for "./graph" from the file /tmp/robot/robot.js, Node will try to load the file /tmp/robot/graph.js.

+ +

The .js extension may be omitted, and Node will add it if such a file exists. If the required path refers to a directory, Node will try to load the file named index.js in that directory.

+ +

When a string that does not look like a relative or absolute path is given to require, it is assumed to refer to either a built-in module or a module installed in a node_modules directory. For example, require("fs") will give you Node’s built-in file system module. And require("robot") might try to load the library found in node_modules/robot/. A common way to install such libraries is by using NPM, which we’ll come back to in a moment.

+ +

Let’s set up a small project consisting of two files. The first one, called main.js, defines a script that can be called from the command line to reverse a string.

+ +
const {reverse} = require("./reverse");
+
+// Index 2 holds the first actual command line argument
+let argument = process.argv[2];
+
+console.log(reverse(argument));
+ +

The file reverse.js defines a library for reversing strings, which can be used both by this command line tool and by other scripts that need direct access to a string-reversing function.

+ +
exports.reverse = function(string) {
+  return Array.from(string).reverse().join("");
+};
+ +

Remember that adding properties to exports adds them to the interface of the module. Since Node.js treats files as CommonJS modules, main.js can take the exported reverse function from reverse.js.

+ +

We can now call our tool like this:

+ +
$ node main.js JavaScript
+tpircSavaJ
+ +

Installing with NPM

+ +

NPM, which was introduced in Chapter 10, is an online repository of JavaScript modules, many of which are specifically written for Node. When you install Node on your computer, you also get the npm command, which you can use to interact with this repository.

+ +

NPM’s main use is downloading packages. We saw the ini package in Chapter 10. We can use NPM to fetch and install that package on our computer.

+ +
$ npm install ini
+npm WARN enoent ENOENT: no such file or directory,
+         open '/tmp/package.json'
++ ini@1.3.5
+added 1 package in 0.552s
+
+$ node
+> const {parse} = require("ini");
+> parse("x = 1\ny = 2");
+{ x: '1', y: '2' }
+ +

After running npm install, NPM will have created a directory called node_modules. Inside that directory will be an ini directory that contains the library. You can open it and look at the code. When we call require("ini"), this library is loaded, and we can call its parse property to parse a configuration file.

+ +

By default NPM installs packages under the current directory, rather than in a central place. If you are used to other package managers, this may seem unusual, but it has advantages—it puts each application in full control of the packages it installs and makes it easier to manage versions and clean up when removing an application.

+ +

Package files

+ +

In the npm install example, you could see a warning about the fact that the package.json file did not exist. It is recommended to create such a file for each project, either manually or by running npm init. It contains some information about the project, such as its name and version, and lists its dependencies.

+ +

The robot simulation from Chapter 7, as modularized in the exercise in Chapter 10, might have a package.json file like this:

+ +
{
+  "author": "Marijn Haverbeke",
+  "name": "eloquent-javascript-robot",
+  "description": "Simulation of a package-delivery robot",
+  "version": "1.0.0",
+  "main": "run.js",
+  "dependencies": {
+    "dijkstrajs": "^1.0.1",
+    "random-item": "^1.0.0"
+  },
+  "license": "ISC"
+}
+ +

When you run npm install without naming a package to install, NPM will install the dependencies listed in package.json. When you install a specific package that is not already listed as a dependency, NPM will add it to package.json.

+ +

Versions

+ +

A package.json file lists both the program’s own version and versions for its dependencies. Versions are a way to deal with the fact that packages evolve separately, and code written to work with a package as it existed at one point may not work with a later, modified version of the package.

+ +

NPM demands that its packages follow a schema called semantic +versioning, which encodes some information about which versions are compatible (don’t break the old interface) in the version number. A semantic version consists of three numbers, separated by periods, such as 2.3.0. Every time new functionality is added, the middle number has to be incremented. Every time compatibility is broken, so that existing code that uses the package might not work with the new version, the first number has to be incremented.

+ +

A caret character (^) in front of the version number for a dependency in package.json indicates that any version compatible with the given number may be installed. So, for example, "^2.3.0" would mean that any version greater than or equal to 2.3.0 and less than 3.0.0 is allowed.

+ +

The npm command is also used to publish new packages or new versions of packages. If you run npm publish in a directory that has a package.json file, it will publish a package with the name and version listed in the JSON file to the registry. Anyone can publish packages to NPM—though only under a package name that isn’t in use yet since it would be somewhat scary if random people could update existing packages.

+ +

Since the npm program is a piece of software that talks to an open system—the package registry—there is nothing unique about what it does. Another program, yarn, which can be installed from the NPM registry, fills the same role as npm using a somewhat different interface and installation strategy.

+ +

This book won’t delve further into the details of NPM usage. Refer to https://npmjs.org for further documentation and a way to search for packages.

+ +

The file system module

+ +

One of the most commonly used built-in modules in Node is the fs module, which stands for file system. It exports functions for working with files and directories.

+ +

For example, the function called readFile reads a file and then calls a callback with the file’s contents.

+ +
let {readFile} = require("fs");
+readFile("file.txt", "utf8", (error, text) => {
+  if (error) throw error;
+  console.log("The file contains:", text);
+});
+ +

The second argument to readFile indicates the character +encoding used to decode the file into a string. There are several ways in which text can be encoded to binary data, but most modern systems use UTF-8. So unless you have reasons to believe another encoding is used, pass "utf8" when reading a text file. If you do not pass an encoding, Node will assume you are interested in the binary data and will give you a Buffer object instead of a string. This is an array-like object that contains numbers representing the bytes (8-bit chunks of data) in the files.

+ +
const {readFile} = require("fs");
+readFile("file.txt", (error, buffer) => {
+  if (error) throw error;
+  console.log("The file contained", buffer.length, "bytes.",
+              "The first byte is:", buffer[0]);
+});
+ +

A similar function, writeFile, is used to write a file to disk.

+ +
const {writeFile} = require("fs");
+writeFile("graffiti.txt", "Node was here", err => {
+  if (err) console.log(`Failed to write file: ${err}`);
+  else console.log("File written.");
+});
+ +

Here it was not necessary to specify the encoding—writeFile will assume that when it is given a string to write, rather than a Buffer object, it should write it out as text using its default character encoding, which is UTF-8.

+ +

The fs module contains many other useful functions: readdir will return the files in a directory as an array of strings, stat will retrieve information about a file, rename will rename a file, unlink will remove one, and so on. See the documentation at https://nodejs.org for specifics.

+ +

Most of these take a callback function as the last parameter, which they call either with an error (the first argument) or with a successful result (the second). As we saw in Chapter 11, there are downsides to this style of programming—the biggest one being that error handling becomes verbose and error-prone.

+ +

Though promises have been part of JavaScript for a while, at the time of writing their integration into Node.js is still a work in progress. There is an object promises exported from the fs package since version 10.1 that contains most of the same functions as fs but uses promises rather than callback functions.

+ +
const {readFile} = require("fs").promises;
+readFile("file.txt", "utf8")
+  .then(text => console.log("The file contains:", text));
+ +

Sometimes you don’t need asynchronicity, and it just gets in the way. Many of the functions in fs also have a synchronous variant, which has the same name with Sync added to the end. For example, the synchronous version of readFile is called readFileSync.

+ +
const {readFileSync} = require("fs");
+console.log("The file contains:",
+            readFileSync("file.txt", "utf8"));
+ +

Do note that while such a synchronous operation is being performed, your program is stopped entirely. If it should be responding to the user or to other machines on the network, being stuck on a synchronous action might produce annoying delays.

+ +

The HTTP module

+ +

Another central module is called http. It provides functionality for running HTTP servers and making HTTP requests.

+ +

This is all it takes to start an HTTP server:

+ +
const {createServer} = require("http");
+let server = createServer((request, response) => {
+  response.writeHead(200, {"Content-Type": "text/html"});
+  response.write(`
+    <h1>Hello!</h1>
+    <p>You asked for <code>${request.url}</code></p>`);
+  response.end();
+});
+server.listen(8000);
+console.log("Listening! (port 8000)");
+ +

If you run this script on your own machine, you can point your web browser at http://localhost:8000/hello to make a request to your server. It will respond with a small HTML page.

+ +

The function passed as argument to createServer is called every time a client connects to the server. The request and response bindings are objects representing the incoming and outgoing data. The first contains information about the request, such as its url property, which tells us to what URL the request was made.

+ +

So, when you open that page in your browser, it sends a request to your own computer. This causes the server function to run and send back a response, which you can then see in the browser.

+ +

To send something back, you call methods on the response object. The first, writeHead, will write out the response headers (see Chapter 18). You give it the status code (200 for “OK” in this case) and an object that contains header values. The example sets the Content-Type header to inform the client that we’ll be sending back an HTML document.

+ +

Next, the actual response body (the document itself) is sent with response.write. You are allowed to call this method multiple times if you want to send the response piece by piece, for example to stream data to the client as it becomes available. Finally, response.end signals the end of the response.

+ +

The call to server.listen causes the server to start waiting for connections on port 8000. This is why you have to connect to localhost:8000 to speak to this server, rather than just localhost, which would use the default port 80.

+ +

When you run this script, the process just sits there and waits. When a script is listening for events—in this case, network connections—node will not automatically exit when it reaches the end of the script. To close it, press control-C.

+ +

A real web server usually does more than the one in the example—it looks at the request’s method (the method property) to see what action the client is trying to perform and looks at the request’s URL to find out which resource this action is being performed on. We’ll see a more advanced server later in this chapter.

+ +

To act as an HTTP client, we can use the request function in the http module.

+ +
const {request} = require("http");
+let requestStream = request({
+  hostname: "eloquentjavascript.net",
+  path: "/20_node.html",
+  method: "GET",
+  headers: {Accept: "text/html"}
+}, response => {
+  console.log("Server responded with status code",
+              response.statusCode);
+});
+requestStream.end();
+ +

The first argument to request configures the request, telling Node what server to talk to, what path to request from that server, which method to use, and so on. The second argument is the function that should be called when a response comes in. It is given an object that allows us to inspect the response, for example to find out its status code.

+ +

Just like the response object we saw in the server, the object returned by request allows us to stream data into the request with the write method and finish the request with the end method. The example does not use write because GET requests should not contain data in their request body.

+ +

There’s a similar request function in the https module that can be used to make requests to https: URLs.

+ +

Making requests with Node’s raw functionality is rather verbose. There are much more convenient wrapper packages available on NPM. For example, node-fetch provides the promise-based fetch interface that we know from the browser.

+ +

Streams

+ +

We have seen two instances of writable streams in the HTTP examples—namely, the response object that the server could write to and the request object that was returned from request.

+ +

Writable streams are a widely used concept in Node. Such objects have a write method that can be passed a string or a Buffer object to write something to the stream. Their end method closes the stream and optionally takes a value to write to the stream before closing. Both of these methods can also be given a callback as an additional argument, which they will call when the writing or closing has finished.

+ +

It is possible to create a writable stream that points at a file with the createWriteStream function from the fs module. Then you can use the write method on the resulting object to write the file one piece at a time, rather than in one shot as with writeFile.

+ +

Readable streams are a little more involved. Both the request binding that was passed to the HTTP server’s callback and the response binding passed to the HTTP client’s callback are readable streams—a server reads requests and then writes responses, whereas a client first writes a request and then reads a response. Reading from a stream is done using event handlers, rather than methods.

+ +

Objects that emit events in Node have a method called on that is similar to the addEventListener method in the browser. You give it an event name and then a function, and it will register that function to be called whenever the given event occurs.

+ +

Readable streams have "data" and "end" events. The first is fired every time data comes in, and the second is called whenever the stream is at its end. This model is most suited for streaming data that can be immediately processed, even when the whole document isn’t available yet. A file can be read as a readable stream by using the createReadStream function from fs.

+ +

This code creates a server that reads request bodies and streams them back to the client as all-uppercase text:

+ +
const {createServer} = require("http");
+createServer((request, response) => {
+  response.writeHead(200, {"Content-Type": "text/plain"});
+  request.on("data", chunk =>
+    response.write(chunk.toString().toUpperCase()));
+  request.on("end", () => response.end());
+}).listen(8000);
+ +

The chunk value passed to the data handler will be a binary Buffer. We can convert this to a string by decoding it as UTF-8 encoded characters with its toString method.

+ +

The following piece of code, when run with the uppercasing server active, will send a request to that server and write out the response it gets:

+ +
const {request} = require("http");
+request({
+  hostname: "localhost",
+  port: 8000,
+  method: "POST"
+}, response => {
+  response.on("data", chunk =>
+    process.stdout.write(chunk.toString()));
+}).end("Hello server");
+// → HELLO SERVER
+ +

The example writes to process.stdout (the process’s standard output, which is a writable stream) instead of using console.log. We can’t use console.log because it adds an extra newline character after each piece of text that it writes, which isn’t appropriate here since the response may come in as multiple chunks.

+ +

A file server

+ +

Let’s combine our newfound knowledge about HTTP servers and working with the file system to create a bridge between the two: an HTTP server that allows remote access to a file system. Such a server has all kinds of uses—it allows web applications to store and share data, or it can give a group of people shared access to a bunch of files.

+ +

When we treat files as HTTP resources, the HTTP methods GET, PUT, and DELETE can be used to read, write, and delete the files, respectively. We will interpret the path in the request as the path of the file that the request refers to.

+ +

We probably don’t want to share our whole file system, so we’ll interpret these paths as starting in the server’s working directory, which is the directory in which it was started. If I ran the server from /tmp/public/ (or C:\tmp\public\ on Windows), then a request for /file.txt should refer to /tmp/public/file.txt (or C:\tmp\public\file.txt).

+ +

We’ll build the program piece by piece, using an object called methods to store the functions that handle the various HTTP methods. Method handlers are async functions that get the request object as argument and return a promise that resolves to an object that describes the response.

+ +
const {createServer} = require("http");
+
+const methods = Object.create(null);
+
+createServer((request, response) => {
+  let handler = methods[request.method] || notAllowed;
+  handler(request)
+    .catch(error => {
+      if (error.status != null) return error;
+      return {body: String(error), status: 500};
+    })
+    .then(({body, status = 200, type = "text/plain"}) => {
+       response.writeHead(status, {"Content-Type": type});
+       if (body && body.pipe) body.pipe(response);
+       else response.end(body);
+    });
+}).listen(8000);
+
+async function notAllowed(request) {
+  return {
+    status: 405,
+    body: `Method ${request.method} not allowed.`
+  };
+}
+ +

This starts a server that just returns 405 error responses, which is the code used to indicate that the server refuses to handle a given method.

+ +

When a request handler’s promise is rejected, the catch call translates the error into a response object, if it isn’t one already, so that the server can send back an error response to inform the client that it failed to handle the request.

+ +

The status field of the response description may be omitted, in which case it defaults to 200 (OK). The content type, in the type property, can also be left off, in which case the response is assumed to be plain text.

+ +

When the value of body is a readable stream, it will have a pipe method that is used to forward all content from a readable stream to a writable stream. If not, it is assumed to be either null (no body), a string, or a buffer, and it is passed directly to the response’s end method.

+ +

To figure out which file path corresponds to a request URL, the urlPath function uses Node’s built-in url module to parse the URL. It takes its pathname, which will be something like "/file.txt", decodes that to get rid of the %20-style escape codes, and resolves it relative to the program’s working directory.

+ +
const {parse} = require("url");
+const {resolve, sep} = require("path");
+
+const baseDirectory = process.cwd();
+
+function urlPath(url) {
+  let {pathname} = parse(url);
+  let path = resolve(decodeURIComponent(pathname).slice(1));
+  if (path != baseDirectory &&
+      !path.startsWith(baseDirectory + sep)) {
+    throw {status: 403, body: "Forbidden"};
+  }
+  return path;
+}
+ +

As soon as you set up a program to accept network requests, you have to start worrying about security. In this case, if we aren’t careful, it is likely that we’ll accidentally expose our whole file +system to the network.

+ +

File paths are strings in Node. To map such a string to an actual file, there is a nontrivial amount of interpretation going on. Paths may, for example, include ../ to refer to a parent directory. So one obvious source of problems would be requests for paths like /../secret_file.

+ +

To avoid such problems, urlPath uses the resolve function from the path module, which resolves relative paths. It then verifies that the result is below the working directory. The process.cwd function (where cwd stands for “current working directory”) can be used to find this working directory. The sep binding from the path package is the system’s path separator—a backslash on Windows and a forward slash on most other systems. When the path doesn’t start with the base directory, the function throws an error response object, using the HTTP status code indicating that access to the resource is forbidden.

+ +

We’ll set up the GET method to return a list of files when reading a directory and to return the file’s content when reading a regular file.

+ +

One tricky question is what kind of Content-Type header we should set when returning a file’s content. Since these files could be anything, our server can’t simply return the same content type for all of them. NPM can help us again here. The mime package (content type indicators like text/plain are also called MIME types) knows the correct type for a large number of file extensions.

+ +

The following npm command, in the directory where the server script lives, installs a specific version of mime:

+ +
$ npm install mime@2.2.0
+ +

When a requested file does not exist, the correct HTTP status code to return is 404. We’ll use the stat function, which looks up information about a file, to find out both whether the file exists and whether it is a directory.

+ +
const {createReadStream} = require("fs");
+const {stat, readdir} = require("fs").promises;
+const mime = require("mime");
+
+methods.GET = async function(request) {
+  let path = urlPath(request.url);
+  let stats;
+  try {
+    stats = await stat(path);
+  } catch (error) {
+    if (error.code != "ENOENT") throw error;
+    else return {status: 404, body: "File not found"};
+  }
+  if (stats.isDirectory()) {
+    return {body: (await readdir(path)).join("\n")};
+  } else {
+    return {body: createReadStream(path),
+            type: mime.getType(path)};
+  }
+};
+ +

Because it has to touch the disk and thus might take a while, stat is asynchronous. Since we’re using promises rather than callback style, it has to be imported from promises instead of directly from fs.

+ +

When the file does not exist, stat will throw an error object with a code property of "ENOENT". These somewhat obscure, Unix-inspired codes are how you recognize error types in Node.

+ +

The stats object returned by stat tells us a number of things about a file, such as its size (size property) and its modification date (mtime property). Here we are interested in the question of whether it is a directory or a regular file, which the isDirectory method tells us.

+ +

We use readdir to read the array of files in a directory and return it to the client. For normal files, we create a readable stream with createReadStream and return that as the body, along with the content type that the mime package gives us for the file’s name.

+ +

The code to handle DELETE requests is slightly simpler.

+ +
const {rmdir, unlink} = require("fs").promises;
+
+methods.DELETE = async function(request) {
+  let path = urlPath(request.url);
+  let stats;
+  try {
+    stats = await stat(path);
+  } catch (error) {
+    if (error.code != "ENOENT") throw error;
+    else return {status: 204};
+  }
+  if (stats.isDirectory()) await rmdir(path);
+  else await unlink(path);
+  return {status: 204};
+};
+ +

When an HTTP response does not contain any data, the status code 204 (“no content”) can be used to indicate this. Since the response to deletion doesn’t need to transmit any information beyond whether the operation succeeded, that is a sensible thing to return here.

+ +

You may be wondering why trying to delete a nonexistent file returns a success status code, rather than an error. When the file that is being deleted is not there, you could say that the request’s objective is already fulfilled. The HTTP standard encourages us to make requests idempotent, which means that making the same request multiple times produces the same result as making it once. In a way, if you try to delete something that’s already gone, the effect you were trying to do has been achieved—the thing is no longer there.

+ +

This is the handler for PUT requests:

+ +
const {createWriteStream} = require("fs");
+
+function pipeStream(from, to) {
+  return new Promise((resolve, reject) => {
+    from.on("error", reject);
+    to.on("error", reject);
+    to.on("finish", resolve);
+    from.pipe(to);
+  });
+}
+
+methods.PUT = async function(request) {
+  let path = urlPath(request.url);
+  await pipeStream(request, createWriteStream(path));
+  return {status: 204};
+};
+ +

We don’t need to check whether the file exists this time—if it does, we’ll just overwrite it. We again use pipe to move data from a readable stream to a writable one, in this case from the request to the file. But since pipe isn’t written to return a promise, we have to write a wrapper, pipeStream, that creates a promise around the outcome of calling pipe.

+ +

When something goes wrong when opening the file, createWriteStream will still return a stream, but that stream will fire an "error" event. The output stream to the request may also fail, for example if the network goes down. So we wire up both streams’ "error" events to reject the promise. When pipe is done, it will close the output stream, which causes it to fire a "finish" event. That’s the point where we can successfully resolve the promise (returning nothing).

+ +

The full script for the server is available at https://eloquentjavascript.net/code/file_server.js. You can download that and, after installing its dependencies, run it with Node to start your own file server. And, of course, you can modify and extend it to solve this chapter’s exercises or to experiment.

+ +

The command line tool curl, widely available on Unix-like systems (such as macOS and Linux), can be used to make HTTP requests. The following session briefly tests our server. The -X option is used to set the request’s method, and -d is used to include a request body.

+ +
$ curl http://localhost:8000/file.txt
+File not found
+$ curl -X PUT -d hello http://localhost:8000/file.txt
+$ curl http://localhost:8000/file.txt
+hello
+$ curl -X DELETE http://localhost:8000/file.txt
+$ curl http://localhost:8000/file.txt
+File not found
+ +

The first request for file.txt fails since the file does not exist yet. The PUT request creates the file, and behold, the next request successfully retrieves it. After deleting it with a DELETE request, the file is again missing.

+ +

Summary

+ +

Node is a nice, small system that lets us run JavaScript in a nonbrowser context. It was originally designed for network tasks to play the role of a node in a network. But it lends itself to all kinds of scripting tasks, and if writing JavaScript is something you enjoy, automating tasks with Node works well.

+ +

NPM provides packages for everything you can think of (and quite a few things you’d probably never think of), and it allows you to fetch and install those packages with the npm program. Node comes with a number of built-in modules, including the fs module for working with the file system and the http module for running HTTP servers and making HTTP requests.

+ +

All input and output in Node is done asynchronously, unless you explicitly use a synchronous variant of a function, such as readFileSync. When calling such asynchronous functions, you provide callback functions, and Node will call them with an error value and (if available) a result when it is ready.

+ +

Exercises

+ +

Search tool

+ +

On Unix systems, there is a command line tool called grep that can be used to quickly search files for a regular expression.

+ +

Write a Node script that can be run from the command line and acts somewhat like grep. It treats its first command line argument as a regular expression and treats any further arguments as files to search. It should output the names of any file whose content matches the regular expression.

+ +

When that works, extend it so that when one of the arguments is a directory, it searches through all files in that directory and its subdirectories.

+ +

Use asynchronous or synchronous file system functions as you see fit. Setting things up so that multiple asynchronous actions are requested at the same time might speed things up a little, but not a huge amount, since most file systems can read only one thing at a time.

+ +
+ +

Your first command line argument, the regular expression, can be found in process.argv[2]. The input files come after that. You can use the RegExp constructor to go from a string to a regular expression object.

+ +

Doing this synchronously, with readFileSync, is more straightforward, but if you use fs.promises again to get promise-returning functions and write an async function, the code looks similar.

+ +

To figure out whether something is a directory, you can again use stat (or statSync) and the stats object’s isDirectory method.

+ +

Exploring a directory is a branching process. You can do it either by using a recursive function or by keeping an array of work (files that still need to be explored). To find the files in a directory, you can call readdir or readdirSync. The strange capitalization—Node’s file system function naming is loosely based on standard Unix functions, such as readdir, that are all lowercase, but then it adds Sync with a capital letter.

+ +

To go from a filename read with readdir to a full path name, you have to combine it with the name of the directory, putting a slash +character (/) between them.

+ +
+ +

Directory creation

+ +

Though the DELETE method in our file server is able to delete directories (using rmdir), the server currently does not provide any way to create a directory.

+ +

Add support for the MKCOL method (“make collection”), which should create a directory by calling mkdir from the fs module. MKCOL is not a widely used HTTP method, but it does exist for this same purpose in the WebDAV standard, which specifies a set of conventions on top of HTTP that make it suitable for creating documents.

+ +
+ +

You can use the function that implements the DELETE method as a blueprint for the MKCOL method. When no file is found, try to create a directory with mkdir. When a directory exists at that path, you can return a 204 response so that directory creation requests are idempotent. If a nondirectory file exists here, return an error code. Code 400 (“bad request”) would be appropriate.

+ +
+ +

A public space on the web

+ +

Since the file server serves up any kind of file and even includes the right Content-Type header, you can use it to serve a website. Since it allows everybody to delete and replace files, it would be an interesting kind of website: one that can be modified, improved, and vandalized by everybody who takes the time to create the right HTTP request.

+ +

Write a basic HTML page that includes a simple JavaScript file. Put the files in a directory served by the file server and open them in your browser.

+ +

Next, as an advanced exercise or even a weekend project, combine all the knowledge you gained from this book to build a more user-friendly interface for modifying the website—from inside the website.

+ +

Use an HTML form to edit the content of the files that make up the website, allowing the user to update them on the server by using HTTP requests, as described in Chapter 18.

+ +

Start by making only a single file editable. Then make it so that the user can select which file to edit. Use the fact that our file server returns lists of files when reading a directory.

+ +

Don’t work directly in the code exposed by the file server since if you make a mistake, you are likely to damage the files there. Instead, keep your work outside of the publicly accessible directory and copy it there when testing.

+ +
+ +

You can create a <textarea> element to hold the content of the file that is being edited. A GET request, using fetch, can retrieve the current content of the file. You can use relative URLs like index.html, instead of http://localhost:8000/index.html, to refer to files on the same server as the running script.

+ +

Then, when the user clicks a button (you can use a <form> element and "submit" event), make a PUT request to the same URL, with the content of the <textarea> as request body, to save the file.

+ +

You can then add a <select> element that contains all the files in the server’s top directory by adding <option> elements containing the lines returned by a GET request to the URL /. When the user selects another file (a "change" event on the field), the script must fetch and display that file. When saving a file, use the currently selected filename.

+ +
+
diff --git a/docs/21_skillsharing.html b/docs/21_skillsharing.html new file mode 100644 index 000000000..08f987908 --- /dev/null +++ b/docs/21_skillsharing.html @@ -0,0 +1,636 @@ + + + + + Project: Skill-Sharing Website :: Eloquent JavaScript + + + + + + +
+ + +

Chapter 21Project: Skill-Sharing Website

+ +
+ +

If you have knowledge, let others light their candles at it.

+ +
Margaret Fuller
+ +
Picture of two unicycles
+ +

A skill-sharing meeting is an event where people with a shared interest come together and give small, informal presentations about things they know. At a gardening skill-sharing meeting, someone might explain how to cultivate celery. Or in a programming skill-sharing group, you could drop by and tell people about Node.js.

+ +

Such meetups—also often called users’ groups when they are about computers—are a great way to broaden your horizon, learn about new developments, or simply meet people with similar interests. Many larger cities have JavaScript meetups. They are typically free to attend, and I’ve found the ones I’ve visited to be friendly and welcoming.

+ +

In this final project chapter, our goal is to set up a website for managing talks given at a skill-sharing meeting. Imagine a small group of people meeting up regularly in the office of one of the members to talk about unicycling. The previous organizer of the meetings moved to another town, and nobody stepped forward to take over this task. We want a system that will let the participants propose and discuss talks among themselves, without a central organizer.

+ +

Just like in the previous chapter, some of the code in this chapter is written for Node.js, and running it directly in the HTML page that you are looking at is unlikely to work. The full code for the project can be downloaded from https://eloquentjavascript.net/code/skillsharing.zip.

+ +

Design

+ +

There is a server part to this project, written for Node.js, and a client part, written for the browser. The server stores the system’s data and provides it to the client. It also serves the files that implement the client-side system.

+ +

The server keeps the list of talks proposed for the next meeting, and the client shows this list. Each talk has a presenter name, a title, a summary, and an array of comments associated with it. The client allows users to propose new talks (adding them to the list), delete talks, and comment on existing talks. Whenever the user makes such a change, the client makes an HTTP request to tell the server about it.

Screenshot of the skill-sharing website
+ +

The application will be set up to show a live view of the current proposed talks and their comments. Whenever someone, somewhere, submits a new talk or adds a comment, all people who have the page open in their browsers should immediately see the change. This poses a bit of a challenge—there is no way for a web server to open a connection to a client, nor is there a good way to know which clients are currently looking at a given website.

+ +

A common solution to this problem is called long polling, which happens to be one of the motivations for Node’s design.

+ +

Long polling

+ +

To be able to immediately notify a client that something changed, we need a connection to that client. Since web browsers do not traditionally accept connections and clients are often behind routers that would block such connections anyway, having the server initiate this connection is not practical.

+ +

We can arrange for the client to open the connection and keep it around so that the server can use it to send information when it needs to do so.

+ +

But an HTTP request allows only a simple flow of information: the client sends a request, the server comes back with a single response, and that is it. There is a technology called WebSockets, supported by modern browsers, that makes it possible to open connections for arbitrary data exchange. But using them properly is somewhat tricky.

+ +

In this chapter, we use a simpler technique—long polling—where clients continuously ask the server for new information using regular HTTP requests, and the server stalls its answer when it has nothing new to report.

+ +

As long as the client makes sure it constantly has a polling request open, it will receive information from the server quickly after it becomes available. For example, if Fatma has our skill-sharing application open in her browser, that browser will have made a request for updates and will be waiting for a response to that request. When Iman submits a talk on Extreme Downhill Unicycling, the server will notice that Fatma is waiting for updates and send a response containing the new talk to her pending request. Fatma’s browser will receive the data and update the screen to show the talk.

+ +

To prevent connections from timing out (being aborted because of a lack of activity), long polling techniques usually set a maximum time for each request, after which the server will respond anyway, even though it has nothing to report, after which the client will start a new request. Periodically restarting the request also makes the technique more robust, allowing clients to recover from temporary connection failures or server problems.

+ +

A busy server that is using long polling may have thousands of waiting requests, and thus TCP connections, open. Node, which makes it easy to manage many connections without creating a separate thread of control for each one, is a good fit for such a system.

+ +

HTTP interface

+ +

Before we start designing either the server or the client, let’s think about the point where they touch: the HTTP interface over which they communicate.

+ +

We will use JSON as the format of our request and response body. Like in the file server from Chapter 20, we’ll try to make good use of HTTP methods and headers. The interface is centered around the /talks path. Paths that do not start with /talks will be used for serving static files—the HTML and JavaScript code for the client-side system.

+ +

A GET request to /talks returns a JSON document like this:

+ +
[{"title": "Unituning",
+  "presenter": "Jamal",
+  "summary": "Modifying your cycle for extra style",
+  "comments": []}]
+ +

Creating a new talk is done by making a PUT request to a URL like /talks/Unituning, where the part after the second slash is the title of the talk. The PUT request’s body should contain a JSON object that has presenter and summary properties.

+ +

Since talk titles may contain spaces and other characters that may not appear normally in a URL, title strings must be encoded with the encodeURIComponent function when building up such a URL.

+ +
console.log("/talks/" + encodeURIComponent("How to Idle"));
+// → /talks/How%20to%20Idle
+ +

A request to create a talk about idling might look something like this:

+ +
PUT /talks/How%20to%20Idle HTTP/1.1
+Content-Type: application/json
+Content-Length: 92
+
+{"presenter": "Maureen",
+ "summary": "Standing still on a unicycle"}
+ +

Such URLs also support GET requests to retrieve the JSON representation of a talk and DELETE requests to delete a talk.

+ +

Adding a comment to a talk is done with a POST request to a URL like /talks/Unituning/comments, with a JSON body that has author and message properties.

+ +
POST /talks/Unituning/comments HTTP/1.1
+Content-Type: application/json
+Content-Length: 72
+
+{"author": "Iman",
+ "message": "Will you talk about raising a cycle?"}
+ +

To support long polling, GET requests to /talks may include extra headers that inform the server to delay the response if no new information is available. We’ll use a pair of headers normally intended to manage caching: ETag and If-None-Match.

+ +

Servers may include an ETag (“entity tag”) header in a response. Its value is a string that identifies the current version of the resource. Clients, when they later request that resource again, may make a conditional request by including an If-None-Match header whose value holds that same string. If the resource hasn’t changed, the server will respond with status code 304, which means “not modified”, telling the client that its cached version is still current. When the tag does not match, the server responds as normal.

+ +

We need something like this, where the client can tell the server which version of the list of talks it has, and the server responds only when that list has changed. But instead of immediately returning a 304 response, the server should stall the response and return only when something new is available or a given amount of time has elapsed. To distinguish long polling requests from normal conditional requests, we give them another header, Prefer: wait=90, which tells the server that the client is willing to wait up to 90 seconds for the response.

+ +

The server will keep a version number that it updates every time the talks change and will use that as the ETag value. Clients can make requests like this to be notified when the talks change:

+ +
GET /talks HTTP/1.1
+If-None-Match: "4"
+Prefer: wait=90
+
+(time passes)
+
+HTTP/1.1 200 OK
+Content-Type: application/json
+ETag: "5"
+Content-Length: 295
+
+[....]
+ +

The protocol described here does not do any access control. Everybody can comment, modify talks, and even delete them. (Since the Internet is full of hooligans, putting such a system online without further protection probably wouldn’t end well.)

+ +

The server

+ +

Let’s start by building the server-side part of the program. The code in this section runs on Node.js.

+ +

Routing

+ +

Our server will use createServer to start an HTTP server. In the function that handles a new request, we must distinguish between the various kinds of requests (as determined by the method and the path) that we support. This can be done with a long chain of if statements, but there is a nicer way.

+ +

A router is a component that helps dispatch a request to the function that can handle it. You can tell the router, for example, that PUT requests with a path that matches the regular expression /^\/talks\/([^\/]+)$/ (/talks/ followed by a talk title) can be handled by a given function. In addition, it can help extract the meaningful parts of the path (in this case the talk title), wrapped in parentheses in the regular expression, and pass them to the handler function.

+ +

There are a number of good router packages on NPM, but here we’ll write one ourselves to illustrate the principle.

+ +

This is router.js, which we will later require from our server module:

+ +
const {parse} = require("url");
+
+module.exports = class Router {
+  constructor() {
+    this.routes = [];
+  }
+  add(method, url, handler) {
+    this.routes.push({method, url, handler});
+  }
+  resolve(context, request) {
+    let path = parse(request.url).pathname;
+
+    for (let {method, url, handler} of this.routes) {
+      let match = url.exec(path);
+      if (!match || request.method != method) continue;
+      let urlParts = match.slice(1).map(decodeURIComponent);
+      return handler(context, ...urlParts, request);
+    }
+    return null;
+  }
+};
+ +

The module exports the Router class. A router object allows new handlers to be registered with the add method and can resolve requests with its resolve method.

+ +

The latter will return a response when a handler was found, and null otherwise. It tries the routes one at a time (in the order in which they were defined) until a matching one is found.

+ +

The handler functions are called with the context value (which will be the server instance in our case), match strings for any groups they defined in their regular expression, and the request object. The strings have to be URL-decoded since the raw URL may contain %20-style codes.

+ +

Serving files

+ +

When a request matches none of the request types defined in our router, the server must interpret it as a request for a file in the public directory. It would be possible to use the file server defined in Chapter 20 to serve such files, but we neither need nor want to support PUT and DELETE requests on files, and we would like to have advanced features such as support for caching. So let’s use a solid, well-tested static file server from NPM instead.

+ +

I opted for ecstatic. This isn’t the only such server on NPM, but it works well and fits our purposes. The ecstatic package exports a function that can be called with a configuration object to produce a request handler function. We use the root option to tell the server where it should look for files. The handler function accepts request and response parameters and can be passed directly to createServer to create a server that serves only files. We want to first check for requests that we should handle specially, though, so we wrap it in another function.

+ +
const {createServer} = require("http");
+const Router = require("./router");
+const ecstatic = require("ecstatic");
+
+const router = new Router();
+const defaultHeaders = {"Content-Type": "text/plain"};
+
+class SkillShareServer {
+  constructor(talks) {
+    this.talks = talks;
+    this.version = 0;
+    this.waiting = [];
+
+    let fileServer = ecstatic({root: "./public"});
+    this.server = createServer((request, response) => {
+      let resolved = router.resolve(this, request);
+      if (resolved) {
+        resolved.catch(error => {
+          if (error.status != null) return error;
+          return {body: String(error), status: 500};
+        }).then(({body,
+                  status = 200,
+                  headers = defaultHeaders}) => {
+          response.writeHead(status, headers);
+          response.end(body);
+        });
+      } else {
+        fileServer(request, response);
+      }
+    });
+  }
+  start(port) {
+    this.server.listen(port);
+  }
+  stop() {
+    this.server.close();
+  }
+}
+ +

This uses a similar convention as the file server from the previous chapter for responses—handlers return promises that resolve to objects describing the response. It wraps the server in an object that also holds its state.

+ +

Talks as resources

+ +

The talks that have been proposed are stored in the talks property of the server, an object whose property names are the talk titles. These will be exposed as HTTP resources under /talks/[title], so we need to add handlers to our router that implement the various methods that clients can use to work with them.

+ +

The handler for requests that GET a single talk must look up the talk and respond either with the talk’s JSON data or with a 404 error response.

+ +
const talkPath = /^\/talks\/([^\/]+)$/;
+
+router.add("GET", talkPath, async (server, title) => {
+  if (title in server.talks) {
+    return {body: JSON.stringify(server.talks[title]),
+            headers: {"Content-Type": "application/json"}};
+  } else {
+    return {status: 404, body: `No talk '${title}' found`};
+  }
+});
+ +

Deleting a talk is done by removing it from the talks object.

+ +
router.add("DELETE", talkPath, async (server, title) => {
+  if (title in server.talks) {
+    delete server.talks[title];
+    server.updated();
+  }
+  return {status: 204};
+});
+ +

The updated method, which we will define later, notifies waiting long polling requests about the change.

+ +

To retrieve the content of a request body, we define a function called readStream, which reads all content from a readable stream and returns a promise that resolves to a string.

+ +
function readStream(stream) {
+  return new Promise((resolve, reject) => {
+    let data = "";
+    stream.on("error", reject);
+    stream.on("data", chunk => data += chunk.toString());
+    stream.on("end", () => resolve(data));
+  });
+}
+ +

One handler that needs to read request bodies is the PUT handler, which is used to create new talks. It has to check whether the data it was given has presenter and summary properties, which are strings. Any data coming from outside the system might be nonsense, and we don’t want to corrupt our internal data model or crash when bad requests come in.

+ +

If the data looks valid, the handler stores an object that represents the new talk in the talks object, possibly overwriting an existing talk with this title, and again calls updated.

+ +
router.add("PUT", talkPath,
+           async (server, title, request) => {
+  let requestBody = await readStream(request);
+  let talk;
+  try { talk = JSON.parse(requestBody); }
+  catch (_) { return {status: 400, body: "Invalid JSON"}; }
+
+  if (!talk ||
+      typeof talk.presenter != "string" ||
+      typeof talk.summary != "string") {
+    return {status: 400, body: "Bad talk data"};
+  }
+  server.talks[title] = {title,
+                         presenter: talk.presenter,
+                         summary: talk.summary,
+                         comments: []};
+  server.updated();
+  return {status: 204};
+});
+ +

Adding a comment to a talk works similarly. We use readStream to get the content of the request, validate the resulting data, and store it as a comment when it looks valid.

+ +
router.add("POST", /^\/talks\/([^\/]+)\/comments$/,
+           async (server, title, request) => {
+  let requestBody = await readStream(request);
+  let comment;
+  try { comment = JSON.parse(requestBody); }
+  catch (_) { return {status: 400, body: "Invalid JSON"}; }
+
+  if (!comment ||
+      typeof comment.author != "string" ||
+      typeof comment.message != "string") {
+    return {status: 400, body: "Bad comment data"};
+  } else if (title in server.talks) {
+    server.talks[title].comments.push(comment);
+    server.updated();
+    return {status: 204};
+  } else {
+    return {status: 404, body: `No talk '${title}' found`};
+  }
+});
+ +

Trying to add a comment to a nonexistent talk returns a 404 error.

+ +

Long polling support

+ +

The most interesting aspect of the server is the part that handles long polling. When a GET request comes in for /talks, it may be either a regular request or a long polling request.

+ +

There will be multiple places in which we have to send an array of talks to the client, so we first define a helper method that builds up such an array and includes an ETag header in the response.

+ +
SkillShareServer.prototype.talkResponse = function() {
+  let talks = [];
+  for (let title of Object.keys(this.talks)) {
+    talks.push(this.talks[title]);
+  }
+  return {
+    body: JSON.stringify(talks),
+    headers: {"Content-Type": "application/json",
+              "ETag": `"${this.version}"`}
+  };
+};
+ +

The handler itself needs to look at the request headers to see whether If-None-Match and Prefer headers are present. Node stores headers, whose names are specified to be case insensitive, under their lowercase names.

+ +
router.add("GET", /^\/talks$/, async (server, request) => {
+  let tag = /"(.*)"/.exec(request.headers["if-none-match"]);
+  let wait = /\bwait=(\d+)/.exec(request.headers["prefer"]);
+  if (!tag || tag[1] != server.version) {
+    return server.talkResponse();
+  } else if (!wait) {
+    return {status: 304};
+  } else {
+    return server.waitForChanges(Number(wait[1]));
+  }
+});
+ +

If no tag was given or a tag was given that doesn’t match the server’s current version, the handler responds with the list of talks. If the request is conditional and the talks did not change, we consult the Prefer header to see whether we should delay the response or respond right away.

+ +

Callback functions for delayed requests are stored in the server’s waiting array so that they can be notified when something happens. The waitForChanges method also immediately sets a timer to respond with a 304 status when the request has waited long enough.

+ +
SkillShareServer.prototype.waitForChanges = function(time) {
+  return new Promise(resolve => {
+    this.waiting.push(resolve);
+    setTimeout(() => {
+      if (!this.waiting.includes(resolve)) return;
+      this.waiting = this.waiting.filter(r => r != resolve);
+      resolve({status: 304});
+    }, time * 1000);
+  });
+};
+ +

Registering a change with updated increases the version property and wakes up all waiting requests.

+ +
SkillShareServer.prototype.updated = function() {
+  this.version++;
+  let response = this.talkResponse();
+  this.waiting.forEach(resolve => resolve(response));
+  this.waiting = [];
+};
+ +

That concludes the server code. If we create an instance of SkillShareServer and start it on port 8000, the resulting HTTP server serves files from the public subdirectory alongside a talk-managing interface under the /talks URL.

+ +
new SkillShareServer(Object.create(null)).start(8000);
+ +

The client

+ +

The client-side part of the skill-sharing website consists of three files: a tiny HTML page, a style sheet, and a JavaScript file.

+ +

HTML

+ +

It is a widely used convention for web servers to try to serve a file named index.html when a request is made directly to a path that corresponds to a directory. The file server module we use, ecstatic, supports this convention. When a request is made to the path /, the server looks for the file ./public/index.html (./public being the root we gave it) and returns that file if found.

+ +

Thus, if we want a page to show up when a browser is pointed at our server, we should put it in public/index.html. This is our index file:

+ +
<!doctype html>
+<meta charset="utf-8">
+<title>Skill Sharing</title>
+<link rel="stylesheet" href="skillsharing.css">
+
+<h1>Skill Sharing</h1>
+
+<script src="skillsharing_client.js"></script>
+ +

It defines the document title and includes a style sheet, which defines a few styles to, among other things, make sure there is some space between talks.

+ +

At the bottom, it adds a heading at the top of the page and loads the script that contains the client-side application.

+ +

Actions

+ +

The application state consists of the list of talks and the name of the user, and we’ll store it in a {talks, user} object. We don’t allow the user interface to directly manipulate the state or send off HTTP requests. Rather, it may emit actions that describe what the user is trying to do.

+ +

The handleAction function takes such an action and makes it happen. Because our state updates are so simple, state changes are handled in the same function.

+ +
function handleAction(state, action) {
+  if (action.type == "setUser") {
+    localStorage.setItem("userName", action.user);
+    return Object.assign({}, state, {user: action.user});
+  } else if (action.type == "setTalks") {
+    return Object.assign({}, state, {talks: action.talks});
+  } else if (action.type == "newTalk") {
+    fetchOK(talkURL(action.title), {
+      method: "PUT",
+      headers: {"Content-Type": "application/json"},
+      body: JSON.stringify({
+        presenter: state.user,
+        summary: action.summary
+      })
+    }).catch(reportError);
+  } else if (action.type == "deleteTalk") {
+    fetchOK(talkURL(action.talk), {method: "DELETE"})
+      .catch(reportError);
+  } else if (action.type == "newComment") {
+    fetchOK(talkURL(action.talk) + "/comments", {
+      method: "POST",
+      headers: {"Content-Type": "application/json"},
+      body: JSON.stringify({
+        author: state.user,
+        message: action.message
+      })
+    }).catch(reportError);
+  }
+  return state;
+}
+ +

We’ll store the user’s name in localStorage so that it can be restored when the page is loaded.

+ +

The actions that need to involve the server make network requests, using fetch, to the HTTP interface described earlier. We use a wrapper function, fetchOK, which makes sure the returned promise is rejected when the server returns an error code.

+ +
function fetchOK(url, options) {
+  return fetch(url, options).then(response => {
+    if (response.status < 400) return response;
+    else throw new Error(response.statusText);
+  });
+}
+ +

This helper function is used to build up a URL for a talk with a given title.

+ +
function talkURL(title) {
+  return "talks/" + encodeURIComponent(title);
+}
+ +

When the request fails, we don’t want to have our page just sit there, doing nothing without explanation. So we define a function called reportError, which at least shows the user a dialog that tells them something went wrong.

+ +
function reportError(error) {
+  alert(String(error));
+}
+ +

Rendering components

+ +

We’ll use an approach similar to the one we saw in Chapter 19, splitting the application into components. But since some of the components either never need to update or are always fully redrawn when updated, we’ll define those not as classes but as functions that directly return a DOM node. For example, here is a component that shows the field where the user can enter their name:

+ +
function renderUserField(name, dispatch) {
+  return elt("label", {}, "Your name: ", elt("input", {
+    type: "text",
+    value: name,
+    onchange(event) {
+      dispatch({type: "setUser", user: event.target.value});
+    }
+  }));
+}
+ +

The elt function used to construct DOM elements is the one we used in Chapter 19.

+ +

A similar function is used to render talks, which include a list of comments and a form for adding a new comment.

+ +
function renderTalk(talk, dispatch) {
+  return elt(
+    "section", {className: "talk"},
+    elt("h2", null, talk.title, " ", elt("button", {
+      type: "button",
+      onclick() {
+        dispatch({type: "deleteTalk", talk: talk.title});
+      }
+    }, "Delete")),
+    elt("div", null, "by ",
+        elt("strong", null, talk.presenter)),
+    elt("p", null, talk.summary),
+    ...talk.comments.map(renderComment),
+    elt("form", {
+      onsubmit(event) {
+        event.preventDefault();
+        let form = event.target;
+        dispatch({type: "newComment",
+                  talk: talk.title,
+                  message: form.elements.comment.value});
+        form.reset();
+      }
+    }, elt("input", {type: "text", name: "comment"}), " ",
+       elt("button", {type: "submit"}, "Add comment")));
+}
+ +

The "submit" event handler calls form.reset to clear the form’s content after creating a "newComment" action.

+ +

When creating moderately complex pieces of DOM, this style of programming starts to look rather messy. There’s a widely used (non-standard) JavaScript extension called JSX that lets you write HTML directly in your scripts, which can make such code prettier (depending on what you consider pretty). Before you can actually run such code, you have to run a program on your script to convert the pseudo-HTML into JavaScript function calls much like the ones we use here.

+ +

Comments are simpler to render.

+ +
function renderComment(comment) {
+  return elt("p", {className: "comment"},
+             elt("strong", null, comment.author),
+             ": ", comment.message);
+}
+ +

Finally, the form that the user can use to create a new talk is rendered like this:

+ +
function renderTalkForm(dispatch) {
+  let title = elt("input", {type: "text"});
+  let summary = elt("input", {type: "text"});
+  return elt("form", {
+    onsubmit(event) {
+      event.preventDefault();
+      dispatch({type: "newTalk",
+                title: title.value,
+                summary: summary.value});
+      event.target.reset();
+    }
+  }, elt("h3", null, "Submit a Talk"),
+     elt("label", null, "Title: ", title),
+     elt("label", null, "Summary: ", summary),
+     elt("button", {type: "submit"}, "Submit"));
+}
+ +

Polling

+ +

To start the app we need the current list of talks. Since the initial load is closely related to the long polling process—the ETag from the load must be used when polling—we’ll write a function that keeps polling the server for /talks and calls a callback function when a new set of talks is available.

+ +
async function pollTalks(update) {
+  let tag = undefined;
+  for (;;) {
+    let response;
+    try {
+      response = await fetchOK("/talks", {
+        headers: tag && {"If-None-Match": tag,
+                         "Prefer": "wait=90"}
+      });
+    } catch (e) {
+      console.log("Request failed: " + e);
+      await new Promise(resolve => setTimeout(resolve, 500));
+      continue;
+    }
+    if (response.status == 304) continue;
+    tag = response.headers.get("ETag");
+    update(await response.json());
+  }
+}
+ +

This is an async function so that looping and waiting for the request is easier. It runs an infinite loop that, on each iteration, retrieves the list of talks—either normally or, if this isn’t the first request, with the headers included that make it a long polling request.

+ +

When a request fails, the function waits a moment and then tries again. This way, if your network connection goes away for a while and then comes back, the application can recover and continue updating. The promise resolved via setTimeout is a way to force the async function to wait.

+ +

When the server gives back a 304 response, that means a long polling request timed out, so the function should just immediately start the next request. If the response is a normal 200 response, its body is read as JSON and passed to the callback, and its ETag header value is stored for the next iteration.

+ +

The application

+ +

The following component ties the whole user interface together:

+ +
class SkillShareApp {
+  constructor(state, dispatch) {
+    this.dispatch = dispatch;
+    this.talkDOM = elt("div", {className: "talks"});
+    this.dom = elt("div", null,
+                   renderUserField(state.user, dispatch),
+                   this.talkDOM,
+                   renderTalkForm(dispatch));
+    this.syncState(state);
+  }
+
+  syncState(state) {
+    if (state.talks != this.talks) {
+      this.talkDOM.textContent = "";
+      for (let talk of state.talks) {
+        this.talkDOM.appendChild(
+          renderTalk(talk, this.dispatch));
+      }
+      this.talks = state.talks;
+    }
+  }
+}
+ +

When the talks change, this component redraws all of them. This is simple but also wasteful. We’ll get back to that in the exercises.

+ +

We can start the application like this:

+ +
function runApp() {
+  let user = localStorage.getItem("userName") || "Anon";
+  let state, app;
+  function dispatch(action) {
+    state = handleAction(state, action);
+    app.syncState(state);
+  }
+
+  pollTalks(talks => {
+    if (!app) {
+      state = {user, talks};
+      app = new SkillShareApp(state, dispatch);
+      document.body.appendChild(app.dom);
+    } else {
+      dispatch({type: "setTalks", talks});
+    }
+  }).catch(reportError);
+}
+
+runApp();
+ +

If you run the server and open two browser windows for http://localhost:8000 next to each other, you can see that the actions you perform in one window are immediately visible in the other.

+ +

Exercises

+ +

The following exercises will involve modifying the system defined in this chapter. To work on them, make sure you download the code first (https://eloquentjavascript.net/code/skillsharing.zip), have Node installed https://nodejs.org, and have installed the project’s dependency with npm install.

+ +

Disk persistence

+ +

The skill-sharing server keeps its data purely in memory. This means that when it crashes or is restarted for any reason, all talks and comments are lost.

+ +

Extend the server so that it stores the talk data to disk and automatically reloads the data when it is restarted. Do not worry about efficiency—do the simplest thing that works.

+ +
+ +

The simplest solution I can come up with is to encode the whole talks object as JSON and dump it to a file with writeFile. There is already a method (updated) that is called every time the server’s data changes. It can be extended to write the new data to disk.

+ +

Pick a filename, for example ./talks.json. When the server starts, it can try to read that file with readFile, and if that succeeds, the server can use the file’s contents as its starting data.

+ +

Beware, though. The talks object started as a prototype-less object so that the in operator could reliably be used. JSON.parse will return regular objects with Object.prototype as their prototype. If you use JSON as your file format, you’ll have to copy the properties of the object returned by JSON.parse into a new, prototype-less object.

+ +
+ +

Comment field resets

+ +

The wholesale redrawing of talks works pretty well because you usually can’t tell the difference between a DOM node and its identical replacement. But there are exceptions. If you start typing something in the comment field for a talk in one browser window and then, in another, add a comment to that talk, the field in the first window will be redrawn, removing both its content and its focus.

+ +

In a heated discussion, where multiple people are adding comments at the same time, this would be annoying. Can you come up with a way to solve it?

+ +
+ +

The best way to do this is probably to make talks component objects, with a syncState method, so that they can be updated to show a modified version of the talk. During normal operation, the only way a talk can be changed is by adding more comments, so the syncState method can be relatively simple.

+ +

The difficult part is that, when a changed list of talks comes in, we have to reconcile the existing list of DOM components with the talks on the new list—deleting components whose talk was deleted and updating components whose talk changed.

+ +

To do this, it might be helpful to keep a data structure that stores the talk components under the talk titles so that you can easily figure out whether a component exists for a given talk. You can then loop over the new array of talks, and for each of them, either synchronize an existing component or create a new one. To delete components for deleted talks, you’ll have to also loop over the components and check whether the corresponding talks still exist.

+ +
+
diff --git a/docs/Eloquent_JavaScript.epub b/docs/Eloquent_JavaScript.epub new file mode 100644 index 000000000..e69de29bb diff --git a/docs/Eloquent_JavaScript.mobi b/docs/Eloquent_JavaScript.mobi new file mode 100644 index 000000000..e69de29bb diff --git a/docs/author.html b/docs/author.html new file mode 100644 index 000000000..3e287ea46 --- /dev/null +++ b/docs/author.html @@ -0,0 +1,10 @@ + + +
+

Marijn Haverbeke, Programmer

+ +

You can reach me at marijn@haverbeke.nl, or visit my web page, marijnhaverbeke.nl.

+
diff --git a/docs/author.json b/docs/author.json new file mode 100644 index 000000000..24be3c131 --- /dev/null +++ b/docs/author.json @@ -0,0 +1,5 @@ +{ + "name": "Marijn Haverbeke", + "email": "marijn@haverbeke.nl", + "website": "https://marijnhaverbeke.nl/" +} diff --git a/docs/author.txt b/docs/author.txt new file mode 100644 index 000000000..6cf92433c --- /dev/null +++ b/docs/author.txt @@ -0,0 +1 @@ +My name is Marijn Haverbeke. You can email me at marijn@haverbeke.nl, or visit my website, https://marijnhaverbeke.nl/ . diff --git a/docs/backers.html b/docs/backers.html new file mode 100644 index 000000000..5fbc2bf40 --- /dev/null +++ b/docs/backers.html @@ -0,0 +1,3687 @@ + + + + + Eloquent JavaScript :: 2nd Edition Backers + + + + + +
+

List of Backers
Eloquent JavaScript, 2nd Edition

+ +

These are the kind souls who have contributed towards making the +second edition of Eloquent JavaScript +possible.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + 10,000 $
+ + 7000 €
+ Magnus Skog + 1000 €
+ + 1000 $Donation from dev team at Ghostery! Thanks for the awesome work!
+ Anonymous + 1000 $
+ erin lee + 200 $
+ Alexander Shendi + 100 €
+ Anonymous + 100 €
+ Anonymous + 100 €I love your book. Thank you very much!
+ Morgan Roderick + 100 €We need a second edition of the best, free JavaScript book
+ Stelian Ionescu + 100 €
+ Anonymous + 100 $
+ Anton Kovalyov + 100 $
+ david karapetyan + 100 $I fully support this effort. We need more decentralized sources of education for technology related matters.
+ Joshua Waheed + 100 $
+ Kevin Layer, Franz Inc. + 100 $
+ Nathan Youngman + 100 $
+ Raynos + 100 $
+ Tim Caswell + 100 $I especially liked the intro chapter in which you describe programming as a creative art more than a cold technical task.
+ Stan Yamane + 75 $
+ tiffon + 75 $
+ Anonymous + 56 €
+ [o]_O + 50 €I am a T-SQL monkey with the occasional C# app or python script. Much to my dismay, I was assigned the task of fixing some Javascript bugs in an internal deployment tool. All I knew at the time about Javascript was that it sucked, so I tried reading the 1st edition of Eloquent Javascript. Unfortunately, it worked too well: not only did I fix the problems, I now think Javascript is cool :-(
+ David Owens + 50 €
+ Dominique Poulain + 50 €
+ Jans Aasman + 50 €looking forward to see the new edition...
+ Kai Elvin + 50 €Not too interested in the book itself, donating to promote the funding model. I would advise to keep the bitcoins as Bitpay provides a very bad exchange rate.
+ Koen Steenbrink + 50 €First edition helped me a lot. Looking forward for the next one.
+ Mihai Bazon + 50 €
+ Siltaar + 50 €
+ tmk + 50 €
+ Tom Weber + 50 €
+ Vaidas Gaurilcikas + 50 €Thanks for the great work! I found the 1st edition of Eloquent Javascript to be one of the most pleasant to read and useful programming books I've ever come across. Eagerly awaiting the 2nd edition!
+ Vincent + 50 €
+ Iain Bryson + 40 €
+ Adam Hyland + 50 $
+ Amanda Brown + 50 $
+ Anonymous + 50 $
+ Anonymous + 50 $
+ Anonymous + 50 $
+ Anonymous + 50 $
+ Anonymous + 50 $
+ Mato Ilic + 50 $This is one of the best JavaScript books around. Would love to see an updated version-
+ Peter deHaan + 50 $
+ Pierce Lopez + 50 $
+ Trevor Blackwell + 50 $
+ Zach W + 50 $I loved the first edition. Looking forward to another :)
+ Anonymous + 40 $
+ Henry Allen-Tilford + 40 $
+ Kory Mathis + 40 $
+ Tom Kelley + 40 $Keep up the good work. I brag about your current book, and I can't wait to see the new one.
+ Anonymous + 30 €
+ Anonymous + 30 €
+ Anonymous + 30 €
+ Daniel Harrington + 30 €
+ Onno Schwanen + 30 €
+ Anonymous + 25 €
+ Chris Smith + 25 €Marijn's CodeMirror project is the foundation upon which I've been realizing my own vision of a better way to learn programming, and Eloquent JavaScript was truly a pioneering work. I feel it is extremely important to provide Marijn with the encouragement and financial support he needs.
+ Eric Allam + 25 €
+ fronx + 25 €
+ Octavian Nita + 25 €One of the best books on programming ever, if you ask me; even if you know your way around programming, it is still worth it to read even the beginner chapters. + +Can hardly wait to buy the new edition! Best of luck, man!
+ Orde Saunders + 25 €
+ Rob Crowther + 25 €
+ Tom + 25 €More of this sort of thing.
+ Alan Fothergill + 30 $
+ Ben Handzo + 30 $Eloquent JS was my intro to programming as a way of thinking. I've gone back to it a bunch of times and learned new things every time. A beautiful updated version is worth every penyn.
+ Carlos Iriarte + 30 $Your first book is amazing. This is my humble way of saying thanks for creating amazing things such as CodeMirror and the first edition of this book.
+ Dav Clark + 30 $
+ Isak Dalström + 30 $
+ Richard Yeh + 30 $
+ @fwg + 20 €Eloquent Javascript has been an invaluable resource both to my learning and teaching of JS over the years.
+ Anonymous + 20 €The only useful JavaScript Tutorial ;-)
+ Anonymous + 20 €
+ Anonymous + 20 €
+ Anonymous + 20 €The first edition is a wonderful book, something I recommend to my employees as a must-read. Thanks for all the hard work!
+ Anonymous + 20 €
+ Anonymous + 20 €
+ Anonymous + 20 €
+ Anonymous + 20 €
+ Anonymous + 20 €
+ Bundyo + 20 €
+ d4kris + 20 €
+ Florian Heinisch + 20 €
+ Frank Taillandier + 20 €The Web need this. Every good JS developer I have met said he had read your book. An updated edition will continue help everyone writing good Javascript.
+ Jason Smith + 20 €My most preferred task "author's discretion"; but a Node.js section will be great. Remember, the API documentation indicates the permanence ("stability") of the API.
+ Jonas Bredenfeldt + 20 €
+ Karl Westin + 20 €It's the #1 i recommend to people wanting to learn JS
+ larz + 20 €
+ Martin S. + 20 €
+ Massimiliano Filacchioni + 20 €
+ Michał Laskus + 20 €
+ nerdess + 20 €
+ Oliver Anan + 20 €
+ Owen Densmore + 20 €My preferred task really is "all"!
+ Panos Astithas + 20 €
+ Sonny Piers + 20 €Eloquent JavaScript is one of the best resource to learn programming and JavaScript. I'd love to read a new edition.
+ Tiago Rodrigues + 20 €I recommend Eloquent JavaScript to everyone wanting to learn JS as the first book they look at and people usually love it. Hopefully a new edition will get even more people to learn JS!
+ Tom Alterman + 20 €
+ Victor Hooi + 20 €Loved the first book. + +Go hard mate! =)
+ Vit Brunner + 20 €Whenever someone tells me they'd like to learn programming, I point them to Eloquent Javascript!
+ Adrian Schaedle + 25 $Eloquent JS is not only the greatest introduction to Javascript that exists, it's one of the most illuminating books about how to reason about your programs and how to start thinking functionally. I think it's the book responsible for most developers making the jump from curious HTML prodders to full-stop intelligent programmers.
+ Andrew de Andrade + 25 $Awesome work. I recommend your book to lots of people who are new to programming. I think it is easily one of the best books out there. Thank you and thank you on behalf of everyone I've recommended it to. + +Also, thank you for Tern.js, CodeMirror and the Haskell code I have perused. I love it when people like you Brian Lonsdorf, Substack, Fogus and others help build bridges between JavaScript and functional programming programming languages like Haskell and Clojure, and grow the body of work in JavaScript code that use functional paradigms and idioms. + +Best, +Andrew +engineer @ famo.us
+ Anonymous + 25 $
+ Anonymous + 25 $
+ Anonymous + 25 $
+ Anonymous + 25 $
+ Anonymous + 25 $
+ Brian Kung + 25 $
+ Dina Lamdany + 25 $
+ G. Jason Head + 25 $
+ Greg Lind + 25 $The original is such a wonderful resource. Thank you very much. Please take $20 for the project and spend the other $5 on a pint of something refreshing. You've earned it again and again.
+ Hartley + 25 $Despite its age, this book provides a wonderful introduction into Javascript. Would love to see it updated for a new generation of developers.
+ John Goodleaf + 25 $
+ Kashif Jabbar + 25 $EJS is by far the best JavaScript book I have come across. Really looking forward to the 2nd Edition.
+ Les Dougherty + 25 $I'm just past being a beginner to JavaScript, but do have older experience in Perl. I think this is a great project. Best wishes for success. I'm retired now, but will try to donate again later.
+ Matt Sahr + 25 $
+ Paul C + 25 $I've been exposed to Javascript for 10 years and thought I had a reasonable grasp of things but today your book confused me. This is a good thing because I realised there is an awful lot I don't know and am working through your book to start again with a better foundation.
+ Sean + 25 $
+ Thorin Messer + 25 $
+ Tom Gamble + 25 $
+ Trae + 25 $
+ Udi Wertheimer + 25 $
+ Yojan Shrestha + 25 $This is how education should happen to begin with. Also, love this book. Keep doing what you do.
+ Yusuf Abdi + 25 $
+ Anonymous + 18 €
+ Anonymous + 20 $When I use eloquent javascript people hardly notice my disfigurement.
+ Aaron Ackerman + 20 $
+ Aaron Moodie + 20 $
+ Alex Gill + 20 $
+ Alvin Ashcraft + 20 $Thanks! Love the first edition.
+ Andrey Fedorov + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $I'm going through the first edition right now. It's excellent. Can't wait for the second edition!
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $
+ Anonymous + 20 $Thanks for your great works! +The practice-based introduction is what distinguishes this book from other JS books, so I think more practical chapters would be great.
+ Ariel Kirkwood + 20 $
+ Chris Kottom + 20 $
+ Cody Lindley + 20 $
+ David Sprague + 20 $
+ Dethe Elza + 20 $Hi Marijn, I can't tell you how many people I have steered to your book and online version. I still think it's the best introduction to JavaScript around, and am enthusiastic about it becoming even better.
+ Drew Bell + 20 $
+ Eric Baer + 20 $Reading this book helped me understand functional programming for the first time!
+ Erik Swan + 20 $
+ Gary Lucas + 20 $
+ Griffin Alberti + 20 $
+ Jeremy Schlatter + 20 $
+ Jerzy Batalinski + 20 $I love the work you have done, I continue to try to improve my knowledge of objects, loops as I rushed through the book to grasp the concepts as fast as possible. Now working with backbone.js I often refer to the Javascript fundamentals in your book. Plus the Aunt with Cats email is epic. Great work, continue doing awesome stuff :)
+ Jesus Alvarez + 20 $The first edition was amazing. Can't wait for the second edition!
+ John DeHope + 20 $I appreciate your work, Marijn, and the way you're funding and licensing it.
+ John M + 20 $Javascript is the world's most popular language -- I'd love to write it as gracefully and minimally as possible -- thanks!
+ Jorge L Garcia + 20 $Awesome book, awesome legacy, let's keep it alive.
+ Joseph Clay + 20 $
+ Justin Lowery + 20 $
+ Klemen Slavic + 20 $This guy.
+ Kyle Alexander Thompson + 20 $
+ LadyMartel + 20 $
+ Mauricio Mercado + 20 $
+ Millard Ellingsworth + 20 $I paid about $20 for the paper version and I'm happy to kick in another $20 to see an updated, generally available version. It's excellent work that I have referred others to multiple times.
+ Patrick Taylor + 20 $
+ Patrick Teglia + 20 $Loved the first book, really hope you make your goal!
+ Pierre-Francois Laquerre + 20 $
+ pmThompson + 20 $I initially wanted to mark task:None, but I was worried that un-targeted funds could become un-directed work. Besides, *EVERYONE* needs to hire an artist ;-) +
+ RicheTheBuddha + 20 $Thank you for working to update this excellent work and putting it out there in a free way. Thank you for the first edition!
+ Sanket Patel + 20 $I am always in a hurry and first time when I read this book very quick I felt kind of satisfaction and enlightenment with this book more than with any other book...I knew that I had to read this book again and I did...The title Eloquent is not an overstatement...Really the modern introduction on javascript...The definite way to go for writing a book...not only best javascript beginner book but best programming book for any programmer wanna be...Thanks for this book!
+ Scott Carpenter + 20 $Thank you!
+ Scott Lesser + 20 $
+ Sean Diamond + 20 $
+ Sergii + 20 $
+ TehShrike + 20 $Chapter 6 helped me wrap my head around functional programming. I am in your debt!
+ Timur + 20 $
+ Tom Keeler + 20 $
+ Tyler Cipriani + 20 $
+ Vish Jiawon + 20 $
+ Zev Averbach + 20 $
+ Alex Gyoshev + 15 €Rock on!
+ Alun Davey + 15 €
+ Anonymous + 15 €
+ Anonymous + 15 €
+ Anonymous + 15 €
+ Anonymous + 15 €
+ Anonymous + 15 €
+ Anonymous + 15 €It would be incredibly great to be able to not only read the book, but use the book sandbox on iPhone. +With v1 AFAIK it was not practical and/or not working. +An offline manifest would also be great for smartphones, in particular the (upcoming) Firefox OS based ones.
+ Anonymous + 15 €
+ Jérémy Ozog + 15 €
+ Karl Inglis + 15 €
+ manichord + 15 €
+ Pedro Figueiredo + 15 €
+ Peter Zuidhoek + 15 €
+ Tchesko + 15 €Great job. It's better than all the other books i have bought far away... Signed : A french reader.
+ Victor Cazacov + 15 €
+ Stefan Bauckmeier + 13 €
+ Ben Frank Lodge + 12 €Have fun writing the new book. +I look forward to the results!
+ @partyfists + 15 $The book that taught me how Javascript can be beautiful and well written needs this update and I am proud to support it.
+ Anonymous + 15 $I read a large part of your book online. It was awesome, I intended to buy a hardcopy but never did. Hope this compensates you adequately and get you running on the second edition. Looking forward to it. +
+ Anonymous + 15 $
+ Anonymous + 15 $
+ Anonymous + 15 $Like the first book, so would use the second book. Good luck on the rewrite!
+ Anthony Yu + 15 $Thanks bro. You're teaching this baby bird how to take flight. I guess, "Thanks Daddy!" would be more appropriate!
+ Jason Laster + 15 $thanks
+ Jim Hart + 15 $
+ Karl J. Smith + 15 $
+ Miroki + 15 $
+ Patrick + 15 $
+ Ramkumar + 15 $
+ Shawn Searcy + 15 $
+ Sheldon + 15 $
+ Alexander Dobbert + 10 €
+ Amr Malik + 10 €
+ Andrew Ducker + 10 €
+ Andrew Price + 10 €
+ Andrew Whitehouse + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €For beginners chapter 6-8 are where you are losing them, when it comes to functions, recursion, method chaining, oop and so on within a few pages.
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €yay i am so excited
+ Anonymous + 10 €
+ Anonymous + 10 €
+ Anonymous + 10 €
+ bastian + 10 €
+ Bema + 10 €Thanks for doing this excellent work, I read the first edition online and leaned a lot. +I would like to help translating it to Portuguese, I am Brazilian, so if by any chance you are contacted by other Brazilians willing to do the same job please put us in contact so we can build a working group for this task. + +Best wishes, +Bema + +
+ blake johnson + 10 €this is one of the best programming books ever written regardless of language, thanks!
+ Bundyo + 10 €
+ Colm Heaney + 10 €I love this book! Still working my way through the first edition but it has been good enough to convince me to contribute to the 2nd. Great job man!
+ Daniel Beck + 10 €Eloquent Javascript is a really good book. Thanks for the hard work.
+ Dinis Correia + 10 €
+ Fabian Straubinger + 10 €
+ Ferdinand Salis-Samaden + 10 €
+ Garren Smith + 10 €Keep up the great work. I loved the first book.
+ gasper + 10 €yay for giving money to people to do cool stuff!
+ Greg McCarvell + 10 €
+ GZiolo + 10 €Good luck! Waiting for updated version.
+ Jag + 10 €Great work!
+ Jan Tiedemann + 10 €
+ Jiří Prokop + 10 €I hope this book will help to change world! :-)
+ jjjmmmhhh + 10 €
+ Kevin Dangoor + 10 €
+ Lech Rzedzicki + 10 €Hi. + +Thank you for all the work so far, looking forward to 2nd edition.
+ Loucas Papantoniou + 10 €
+ Lucas + 10 €Thank you!
+ Manuel Kiessling + 10 €Eloquent JavaScript is one of the very few books I did not sell before me and my family moved into another city last year. I really love having it.
+ Mark Robson + 10 €
+ Matthew Lancey + 10 €
+ Maurizio Mangione + 10 €
+ Paddy O'Hanlon + 10 €I learned so much from the first book. It broke down many JavaScript walls for me. Really looking forward to the new edition!
+ Patrick Te Tau + 10 €I'd prefer as much of a functional programming lean as possible in a rewrite. But, you know, it's your book ;)
+ Pedro Teixeira + 10 €Eloquent JavaScript has long been my go-to recommendation for people that want to have a good coverage of modern JavaScript programming. I'm really happy that the author is planning a second revision.
+ Piotr Migdał + 10 €A great starting point to learn JavaScript!
+ pixelkritzel + 10 €
+ PurplePilot + 10 €
+ qgi + 10 €
+ Richard + 10 €Great book! I'm a beginner but it helped me understand so much about Javascript and the fundamentals of the language.
+ Rob Campbell + 10 €It's been 6 whole years. JavaScript and the DOM have changed quite a bit since then. This new version should address that.
+ Roland Tanglao + 10 €
+ Rémi Gérard-Marchant + 10 €
+ Stefan Lodders + 10 €
+ Stuart Cuthbertson + 10 €
+ Thomas L. + 10 €Loved the first one. Keep up the good work!
+ Volodymyr Prokopyuk + 10 €
+ whostolemyhat + 10 €Great book, incredibly useful!
+ Juan Carlos Lopez B + 12 $I used your guide to learn JavaScript, and it was great! Thanks alot!
+ Anonymous + 11 $
+ Adam Khorshid + 10 $
+ Al Billings + 10 $
+ Alejandro Garcia + 10 $
+ Andriy G + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $You have written an excellent book on my favourite language... You havema made a difference to my life... I can afford only 10 $.. good luck
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $I used the original Eloquent Javascript to learn JS and I am grateful to it. It's a classic that was made available to everyone. I can't wait to see the next version and am happy to support its continued existence as a goto for learning JS especially as JS becomes more and more ubiquitous.
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $still going through the (very well-done) first version, excited to see the updates!
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $Thank you so much for your work. I've been working with javascript daily for about a year now, and your tutelage, especially in the way of data structures and programming paradigms, has been a huge boon to my abilities! Keep up the terrific work.
+ Anonymous + 10 $Thanks a lot for writing this.
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $Eloquent Javascript served as my intro to JS and it's been with me the whole way. Thanks a bunch.
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Anonymous + 10 $
+ Audrey + 10 $Great work with 1st edition. Please keep up with the good work.
+ Brian FitzGerald + 10 $I think the current intro is beautiful, don't change it too much :) Thanks for your efforts!
+ C J Silverio + 10 $
+ Chris Bobek + 10 $
+ Collin Garvey + 10 $
+ Cory Gagliardi + 10 $Eloquent JavaScript is my favorite JS book. I'm looking forward to seeing an update.
+ Cris Noble + 10 $
+ CyberAP + 10 $First edition helped me a lot. Looking forward for a new one.
+ Dan Lourenco + 10 $
+ davewhat + 10 $
+ Don Burks + 10 $The first edition is how I get people excited about JS.
+ Eduardo Nieto + 10 $Your book has been invaluable to me. I recommend it anytime I can, especially to people learning how to program. Thanks a lot!
+ Eric Schiller + 10 $This book taught me Javascript.
+ erutan + 10 $
+ Ivan Shornikov + 10 $Great idea!
+ James + 10 $
+ James Herdman + 10 $
+ Jan + 10 $
+ Jason + 10 $
+ Jeb Schiefer + 10 $
+ John Caruso + 10 $
+ Leandro D'Onofrio + 10 $JavaScript FTW!
+ max borghino + 10 $
+ Mayuresh Kathe + 10 $
+ Megan Taylor + 10 $Eloquent JavaScript got me over a lot of my early hurdles, and is still a resource I return to. I've also recommended it to friends who were looking to learn programming.
+ Michael Allan + 10 $Heck, I'd have paid just to have one of the bugs speak my bubble! *So* much cooler than any Kickstarter bonus. Also, can't wait to see an updated version of this classic :-)
+ Michael Paulukonis + 10 $I loved the original web-version, and have recommended it to colleagues learning JS. + +For the record, I was always impressed with the in-page editor/executor environment. Being able to test ideas in the same location I was reading about kept me from jarring context-shifts. Making the sample code vanilla-js is a good idea, though.
+ mike maxwell + 10 $
+ Mohammed Ameen + 10 $
+ Nick Ketter + 10 $Have been wanting to learn Javascript for a while and something about this seems like a better use of any $ I'd spend on a finished book.
+ Rick Yentzer + 10 $
+ Rob Bartholme + 10 $
+ Robert Brimhall + 10 $
+ Rolf + 10 $I bought your first book and it was great :-)
+ Sachin Palewar + 10 $I took Derek Sivers's advice and started learning Javascript recently with your online book. I am onto 3rd chapter now and already feel that your book is useful not only for novice programmers but for experienced ones as well. + +I am mighty impressed with current version and can only imagine what can you do with a re-written version. All the best. Also your background animation rocks.
+ Sandy + 10 $I love the first edition. Your writing made me rediscover the joy of learning to code.
+ Scott + 10 $
+ Shaun Santa Cruz + 10 $
+ Steve Kinney + 10 $The first edition of the book really helped me wrap my head around JavaScript. I'm looking forward to the second version.
+ Tessa Thornton + 10 $
+ Tony Ching + 10 $Thank you for teaching programming via JavaScript. I look forward to buying the book again.
+ Varun Raj + 10 $Thank Tou Marijn... I would like to express my gratitude for the work you are doing. Great Job.
+ Zachary Freeman + 10 $Can't wait for the new version!
+ 谢彪 + 10 $Love your book and open source works <3 :-)
+ Anonymous + 8 $
+ Anonymous + 8 $
+ Anonymous + 8 $Congratulations for the initiative and the book. I home you get all the money you need for write this second version and make an ePub version too :)
+ Farid Neshat + 8 $
+ Patrick Stapleton + 8 $@gdi2290
+ vobi + 6 €
+ Robbie Edwards + 8 $
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Anonymous + 5 €
+ Chris Mear + 5 €
+ Dima Samodurov + 5 €
+ Emilian Losneanu + 5 €
+ Francisco Fernández Castaño + 5 €
+ Harry Moreno + 5 €Loved the original. Consider including jquery, it's a pretty standard requirement these days for a JS programmer.
+ Ian Rose + 5 €Thanks and looking forward to the release!
+ Jacob + 5 €
+ Jan Aagaard + 5 €
+ Lasse + 5 €
+ Mario Estrada + 5 €
+ Peter Janotta + 5 €
+ Ralf Puchert + 5 €
+ Roberto Ferro + 5 €
+ Sergi + 5 €Awesome stuff, make it awesomer!
+ Slavo Ingilizov + 5 €Rock on dude. You're doing an awesome thing.
+ Staale Nataas + 5 €
+ Thomas Herzog + 5 €
+ Wojtek + 5 €
+ Wolfgang + 5 €
+ Anonymous + 4 €
+ Anonymous + 5 $
+ Ali Ukani + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Anonymous + 5 $
+ Ben Boarder + 5 $The first edition of 'Eloquent JavaScript' helped me at the beginning of my development career so much, that I hope for the next generation of JavaScripters to gain the same solid skills.
+ Christopher Lamm + 5 $
+ Colin Gourlay + 5 $The original is a classic. Excited to share the new edition with new JS devs!
+ curtis gagliardi + 5 $
+ CYRIL DAVID + 5 $
+ Dhruv Chandna + 5 $
+ Eugene Bulkin + 5 $
+ Garth Johnson + 5 $
+ Jack Crish + 5 $Thank you for the first book. Glad to see you're working on an update. Good luck with your progress.
+ Loren Saele + 5 $Enjoyed the first edition and looking forward to an updated second edition.
+ Lucas D + 5 $Amazing contribution to the javascript community.
+ Luiz Americo Pereira Camara + 5 $
+ Paul Jaworski + 5 $Probably the best introduction to Javascript I have encountered!
+ Peter M + 5 $
+ Ron Hamenahem + 5 $
+ Stanislav + 5 $EloquentJS rocks !
+ Ted Young + 5 $
+ Thomas + 5 $Can't wait!
+ tucaz + 5 $
+ wannianchuan + 5 $Like your book, look forward to the second edition.
+ Wyatt + 5 $Yours is the best JS resource available. Keep up the good work.
+ Yorgos Kopanias + 5 $I am only half-way the 1st edition and I think you have done a great job! Thank you!
+ Anonymous + 4 $
+ Anonymous + 3 €
+ Guido Corradi + 3 €Good job! : )
+ Radek P + 3 €
+ Anthony Mastrean + 3 $
+ Anonymous + 2 €
+ Anonymous + 2 €
+ Anonymous + 2 $
+ Toby + 2 $
+ Tori Hamblin + 2 $
+ Anonymous + 1 €
+ Anonymous + 1 €
+ bzlm + 1 €hi guys
+ Krzysztof B. Wicher + 1 €
+ robalarcon + 1 €I have never read the first edition, but I have used a lot of your software and I'll looking forward for this edition
+ Anonymous + 1 $
+ Farid Neshat + 1 $
+ s5s5 + 1 $good job
+ Tapan Shah + 1 $Good luck & Thank you
+
+ diff --git a/docs/backers3.html b/docs/backers3.html new file mode 100644 index 000000000..2fd7a2a52 --- /dev/null +++ b/docs/backers3.html @@ -0,0 +1,802 @@ + + + + + Eloquent JavaScript :: 3rd Edition Backers + + + + + +
+

List of Backers
Eloquent JavaScript, 3rd Edition

+ +

These are the wonderful people and organizations who have +contributed towards making the third edition +of Eloquent JavaScript happen.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1000 €
1000 €
Belma Gaukrodger200 €
Christopher Bejnar150 €
Dennis van Leeuwen150 €
Tim Caswell111 €
Anonymous100 €
John W. Bruce100 €
Liam Newman100 €
Morgan Roderick100 €
Peter-Paul van Gemerden100 €
Christopher Astfalk75 €
Lord Omlette75 €
Edward Cheng66 €
t.m.k.65 €
Charles McMackin60 €
Alexander Bartolo50 €
Allie Jones50 €
Anonymous50 €×5
Anthony Lupinetti50 €
Ash Kumar50 €
Blaine Cook50 €
Brandon Yanofsky50 €
Brian Jones50 €
Chandler50 €
Chris Wright50 €
Chrissy Gonzalez50 €
Connie Kephart50 €
Daisuke Horie50 €
David Sommers50 €
Dwayne Gardner50 €
Eleanor Weigert50 €
Florencia Herra Vega50 €
G C Glaser50 €
Geoffrey Kidd50 €
jake.ro50 €
JS Cheerleader50 €
Jaime Silvela50 €
Jarren Bird50 €
Jeremy Whitbred50 €
Jiaxing Wang50 €
Joe Paris50 €
Johan Gunawan50 €
José de Leon50 €
Juri Leino50 €
Kei Ito50 €
Lewis Liu50 €
Louis Cheung50 €
Mathieu Jouhet50 €
Max Zhuravsky50 €
Mirco Strässle50 €
Nikita Skrebets50 €
Peter Persson50 €
Phil Aylesworth50 €
Pitch Arunsuwannakorn50 €
Raphael LANG50 €
Richard Curzon50 €
Scott Sauyet50 €
Stephen Band50 €
Techit U.50 €
Tim Scheer50 €
Tom MacWright50 €
Tommy Williams50 €
Vincent Sisk50 €
aravind mohan50 €
jsm50 €
Martin Bohgard45 €
Anonymous40 €
Don McCurdy40 €
Steve Albers33 €
Anonymous30 €
Charlie Orford30 €
Paul Jacobson30 €
Steven Mitts26 €
Aaron Williams25 €
Adi Dahiya25 €
Alex Melville25 €
Alex Moldovan25 €
Alexis La Porte25 €
Alfonso Higuera Gamboa25 €
Ali Smith25 €
Alper25 €
Amberley Romo25 €
Anonymous25 €×23
August G Dombrow25 €
Bas Hintemann25 €
Bastian Albers25 €
Biniam Bekele25 €
Bradley Ayers25 €
Bálint Kléri25 €
C.H.A.D.25 €
Camilo25 €
Charles Stanhope25 €
Chris Aves25 €
Christian Simonsen25 €
Christophe Pouliot25 €
Colby25 €
Constantinescu Nicolaie25 €
Coy Sanders25 €
Craig Maloney25 €
Cristina Suarez Corzo25 €
Cyril Pierron25 €
D C Jackson25 €
Daniel Kocoj25 €
Darren Torpey25 €
Dave King25 €
David Bremner25 €
David Lukeš25 €
David Owens25 €
David Shirey25 €
Dennis Martinez25 €
Donald Craig25 €
Dor Tzur25 €
Drew Bollinger25 €
Dumoulin Pierre25 €
ECAD Labs Inc.25 €
Eamonn Bell25 €
Elena Santana25 €
Elijah Dorman25 €
Emil Redzic25 €
Eric Haseltine25 €
Ernest D Weems25 €
Ethan Sherbondy25 €
Ferdinand Salis-Samaden25 €
Flaki25 €
Fran Iglesias25 €
Francesco Agosti25 €
Frank Siebenlist25 €
Frederik Eichler25 €
Henrik Saksela25 €
Ignacy25 €
James Taylor25 €
Jeff Whitfield25 €
Jerry Mao25 €
Jesse Printz25 €
Johan25 €
John Meredith25 €
Jonathan So25 €
Kashyap25 €
Kenji Rikitake25 €
Kenyasoweta Bowman25 €
Kovács László25 €
LIONEL E RAMOS25 €
Lev Izraelit25 €
Lev Izraelit25 €
Luciano Mammino25 €
Maarten25 €
Marc Farra25 €
Marcus Weiner25 €
Marko Bilal25 €
Marshall25 €
Mattie Kenny25 €
Michael Saxton25 €
Michael Terry25 €
Mikko Tolmunen25 €
Muhammad Abdusamad25 €
Niels Gregersen25 €
Niles Turner25 €
Nikolaus Klumpp25 €
Octavian Dobrescu25 €
Olivier Forget25 €
Omari Rose25 €
Paul Haddad25 €
Pedro Tavares25 €
Pelle Lundgren25 €
Peter Weber25 €
Phillip Bruk25 €
Pietro Menna25 €
Rocio Chongtay25 €
Ron Male25 €
Rosario Fusca25 €
Roy Grubb25 €
Ryan Paul25 €
SHI Wenhao25 €
Safa Yasin Yıldırım25 €
Samuel Durkin25 €
Sathya Gunasekaran25 €
Stephan Seidt25 €
Steve Ingram25 €
Steven Landman25 €
Stuart Kennedy25 €
SunnyByte25 €
Tim Richards25 €
Tyler Cipriani25 €
Will Schmidt25 €
William Harris25 €
YOU,ZONGYAN25 €
Yuya Saito25 €
Zhivko Siderov25 €
jenn schiffer25 €
theo bousquet25 €
www.actioncy.co.uk25 €
Anonymous< 25×107
+
+ diff --git a/docs/code/animatevillage.js b/docs/code/animatevillage.js new file mode 100644 index 000000000..0f8efc334 --- /dev/null +++ b/docs/code/animatevillage.js @@ -0,0 +1,128 @@ +// test: no + +(function() { + "use strict" + + let active = null + + const places = { + "Alice's House": {x: 279, y: 100}, + "Bob's House": {x: 295, y: 203}, + "Cabin": {x: 372, y: 67}, + "Daria's House": {x: 183, y: 285}, + "Ernie's House": {x: 50, y: 283}, + "Farm": {x: 36, y: 118}, + "Grete's House": {x: 35, y: 187}, + "Marketplace": {x: 162, y: 110}, + "Post Office": {x: 205, y: 57}, + "Shop": {x: 137, y: 212}, + "Town Hall": {x: 202, y: 213} + } + const placeKeys = Object.keys(places) + + const speed = 2 + + class Animation { + constructor(worldState, robot, robotState) { + this.worldState = worldState + this.robot = robot + this.robotState = robotState + this.turn = 0 + + let outer = (window.__sandbox ? window.__sandbox.output.div : document.body), doc = outer.ownerDocument + this.node = outer.appendChild(doc.createElement("div")) + this.node.style.cssText = "position: relative; line-height: 0.1; margin-left: 10px" + this.map = this.node.appendChild(doc.createElement("img")) + this.map.src = "img/village2x.png" + this.map.style.cssText = "vertical-align: -8px" + this.robotElt = this.node.appendChild(doc.createElement("div")) + this.robotElt.style.cssText = `position: absolute; transition: left ${0.8 / speed}s, top ${0.8 / speed}s;` + let robotPic = this.robotElt.appendChild(doc.createElement("img")) + robotPic.src = "img/robot_moving2x.gif" + this.parcels = [] + + this.text = this.node.appendChild(doc.createElement("span")) + this.button = this.node.appendChild(doc.createElement("button")) + this.button.style.cssText = "color: white; background: #28b; border: none; border-radius: 2px; padding: 2px 5px; line-height: 1.1; font-family: sans-serif; font-size: 80%" + this.button.textContent = "Stop" + + this.button.addEventListener("click", () => this.clicked()) + this.schedule() + + this.updateView() + this.updateParcels() + + this.robotElt.addEventListener("transitionend", () => this.updateParcels()) + } + + + updateView() { + let pos = places[this.worldState.place] + this.robotElt.style.top = (pos.y - 38) + "px" + this.robotElt.style.left = (pos.x - 16) + "px" + + this.text.textContent = ` Turn ${this.turn} ` + } + + updateParcels() { + while (this.parcels.length) this.parcels.pop().remove() + let heights = {} + for (let {place, address} of this.worldState.parcels) { + let height = heights[place] || (heights[place] = 0) + heights[place] += 14 + let node = document.createElement("div") + let offset = placeKeys.indexOf(address) * 16 + node.style.cssText = "position: absolute; height: 16px; width: 16px; background-image: url(img/parcel2x.png); background-position: 0 -" + offset + "px"; + if (place == this.worldState.place) { + node.style.left = "25px" + node.style.bottom = (20 + height) + "px" + this.robotElt.appendChild(node) + } else { + let pos = places[place] + node.style.left = (pos.x - 5) + "px" + node.style.top = (pos.y - 10 - height) + "px" + this.node.appendChild(node) + } + this.parcels.push(node) + } + } + + tick() { + let {direction, memory} = this.robot(this.worldState, this.robotState) + this.worldState = this.worldState.move(direction) + this.robotState = memory + this.turn++ + this.updateView() + if (this.worldState.parcels.length == 0) { + this.button.remove() + this.text.textContent = ` Finished after ${this.turn} turns` + this.robotElt.firstChild.src = "img/robot_idle2x.png" + } else { + this.schedule() + } + } + + schedule() { + this.timeout = setTimeout(() => this.tick(), 1000 / speed) + } + + clicked() { + if (this.timeout == null) { + this.schedule() + this.button.textContent = "Stop" + this.robotElt.firstChild.src = "img/robot_moving2x.gif" + } else { + clearTimeout(this.timeout) + this.timeout = null + this.button.textContent = "Start" + this.robotElt.firstChild.src = "img/robot_idle2x.png" + } + } + } + + window.runRobotAnimation = function(worldState, robot, robotState) { + if (active && active.timeout != null) + clearTimeout(active.timeout) + active = new Animation(worldState, robot, robotState) + } +})() diff --git a/docs/code/chapter/.keep b/docs/code/chapter/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/code/chapter/04_data.js b/docs/code/chapter/04_data.js new file mode 100644 index 000000000..f6af2a94b --- /dev/null +++ b/docs/code/chapter/04_data.js @@ -0,0 +1,55 @@ +var journal = []; + +function addEntry(events, squirrel) { + journal.push({events, squirrel}); +} + +function phi(table) { + return (table[3] * table[0] - table[2] * table[1]) / + Math.sqrt((table[2] + table[3]) * + (table[0] + table[1]) * + (table[1] + table[3]) * + (table[0] + table[2])); +} + +function tableFor(event, journal) { + let table = [0, 0, 0, 0]; + for (let i = 0; i < journal.length; i++) { + let entry = journal[i], index = 0; + if (entry.events.includes(event)) index += 1; + if (entry.squirrel) index += 2; + table[index] += 1; + } + return table; +} + +function journalEvents(journal) { + let events = []; + for (let entry of journal) { + for (let event of entry.events) { + if (!events.includes(event)) { + events.push(event); + } + } + } + return events; +} + +function max(...numbers) { + let result = -Infinity; + for (let number of numbers) { + if (number > result) result = number; + } + return result; +} + +var list = { + value: 1, + rest: { + value: 2, + rest: { + value: 3, + rest: null + } + } +}; diff --git a/docs/code/chapter/05_higher_order.js b/docs/code/chapter/05_higher_order.js new file mode 100644 index 000000000..d63c84f07 --- /dev/null +++ b/docs/code/chapter/05_higher_order.js @@ -0,0 +1,44 @@ +function repeat(n, action) { + for (let i = 0; i < n; i++) { + action(i); + } +} + +function characterScript(code) { + for (let script of SCRIPTS) { + if (script.ranges.some(([from, to]) => { + return code >= from && code < to; + })) { + return script; + } + } + return null; +} + +function countBy(items, groupName) { + let counts = []; + for (let item of items) { + let name = groupName(item); + let known = counts.findIndex(c => c.name == name); + if (known == -1) { + counts.push({name, count: 1}); + } else { + counts[known].count++; + } + } + return counts; +} + +function textScripts(text) { + let scripts = countBy(text, char => { + let script = characterScript(char.codePointAt(0)); + return script ? script.name : "none"; + }).filter(({name}) => name != "none"); + + let total = scripts.reduce((n, {count}) => n + count, 0); + if (total == 0) return "No scripts found"; + + return scripts.map(({name, count}) => { + return `${Math.round(count * 100 / total)}% ${name}`; + }).join(", "); +} diff --git a/docs/code/chapter/06_object.js b/docs/code/chapter/06_object.js new file mode 100644 index 000000000..7a5d5fb6f --- /dev/null +++ b/docs/code/chapter/06_object.js @@ -0,0 +1,89 @@ +function speak(line) { + console.log(`The ${this.type} rabbit says '${line}'`); +} +var whiteRabbit = {type: "white", speak}; +var hungryRabbit = {type: "hungry", speak}; + + +var Rabbit = class Rabbit { + constructor(type) { + this.type = type; + } + speak(line) { + console.log(`The ${this.type} rabbit says '${line}'`); + } +} + +var killerRabbit = new Rabbit("killer"); +var blackRabbit = new Rabbit("black"); + +Rabbit.prototype.toString = function() { + return `a ${this.type} rabbit`; +}; + +var toStringSymbol = Symbol("toString"); + +var Matrix = class Matrix { + constructor(width, height, element = (x, y) => undefined) { + this.width = width; + this.height = height; + this.content = []; + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + this.content[y * width + x] = element(x, y); + } + } + } + + get(x, y) { + return this.content[y * this.width + x]; + } + set(x, y, value) { + this.content[y * this.width + x] = value; + } +} + +var MatrixIterator = class MatrixIterator { + constructor(matrix) { + this.x = 0; + this.y = 0; + this.matrix = matrix; + } + + next() { + if (this.y == this.matrix.height) return {done: true}; + + let value = {x: this.x, + y: this.y, + value: this.matrix.get(this.x, this.y)}; + this.x++; + if (this.x == this.matrix.width) { + this.x = 0; + this.y++; + } + return {value, done: false}; + } +} + +Matrix.prototype[Symbol.iterator] = function() { + return new MatrixIterator(this); +}; + +var SymmetricMatrix = class SymmetricMatrix extends Matrix { + constructor(size, element = (x, y) => undefined) { + super(size, size, (x, y) => { + if (x < y) return element(y, x); + else return element(x, y); + }); + } + + set(x, y, value) { + super.set(x, y, value); + if (x != y) { + super.set(y, x, value); + } + } +} + +var matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`); diff --git a/docs/code/chapter/07_robot.js b/docs/code/chapter/07_robot.js new file mode 100644 index 000000000..37a66e8b7 --- /dev/null +++ b/docs/code/chapter/07_robot.js @@ -0,0 +1,120 @@ +var roads = [ + "Alice's House-Bob's House", "Alice's House-Cabin", + "Alice's House-Post Office", "Bob's House-Town Hall", + "Daria's House-Ernie's House", "Daria's House-Town Hall", + "Ernie's House-Grete's House", "Grete's House-Farm", + "Grete's House-Shop", "Marketplace-Farm", + "Marketplace-Post Office", "Marketplace-Shop", + "Marketplace-Town Hall", "Shop-Town Hall" +]; + +function buildGraph(edges) { + let graph = Object.create(null); + function addEdge(from, to) { + if (graph[from] == null) { + graph[from] = [to]; + } else { + graph[from].push(to); + } + } + for (let [from, to] of edges.map(r => r.split("-"))) { + addEdge(from, to); + addEdge(to, from); + } + return graph; +} + +var roadGraph = buildGraph(roads); + +var VillageState = class VillageState { + constructor(place, parcels) { + this.place = place; + this.parcels = parcels; + } + + move(destination) { + if (!roadGraph[this.place].includes(destination)) { + return this; + } else { + let parcels = this.parcels.map(p => { + if (p.place != this.place) return p; + return {place: destination, address: p.address}; + }).filter(p => p.place != p.address); + return new VillageState(destination, parcels); + } + } +} + +function runRobot(state, robot, memory) { + for (let turn = 0;; turn++) { + if (state.parcels.length == 0) { + console.log(`Done in ${turn} turns`); + break; + } + let action = robot(state, memory); + state = state.move(action.direction); + memory = action.memory; + console.log(`Moved to ${action.direction}`); + } +} + +function randomPick(array) { + let choice = Math.floor(Math.random() * array.length); + return array[choice]; +} + +function randomRobot(state) { + return {direction: randomPick(roadGraph[state.place])}; +} + +VillageState.random = function(parcelCount = 5) { + let parcels = []; + for (let i = 0; i < parcelCount; i++) { + let address = randomPick(Object.keys(roadGraph)); + let place; + do { + place = randomPick(Object.keys(roadGraph)); + } while (place == address); + parcels.push({place, address}); + } + return new VillageState("Post Office", parcels); +}; + +var mailRoute = [ + "Alice's House", "Cabin", "Alice's House", "Bob's House", + "Town Hall", "Daria's House", "Ernie's House", + "Grete's House", "Shop", "Grete's House", "Farm", + "Marketplace", "Post Office" +]; + +function routeRobot(state, memory) { + if (memory.length == 0) { + memory = mailRoute; + } + return {direction: memory[0], memory: memory.slice(1)}; +} + +function findRoute(graph, from, to) { + let work = [{at: from, route: []}]; + for (let i = 0; i < work.length; i++) { + let {at, route} = work[i]; + for (let place of graph[at]) { + if (place == to) return route.concat(place); + if (!work.some(w => w.at == place)) { + work.push({at: place, route: route.concat(place)}); + } + } + } +} + +function goalOrientedRobot({place, parcels}, route) { + if (route.length == 0) { + let parcel = parcels[0]; + if (parcel.place != place) { + route = findRoute(roadGraph, place, parcel.place); + } else { + route = findRoute(roadGraph, place, parcel.address); + } + } + return {direction: route[0], memory: route.slice(1)}; +} diff --git a/docs/code/chapter/08_error.js b/docs/code/chapter/08_error.js new file mode 100644 index 000000000..1b7de0ace --- /dev/null +++ b/docs/code/chapter/08_error.js @@ -0,0 +1,43 @@ +var accounts = { + a: 100, + b: 0, + c: 20 +}; + +function getAccount() { + let accountName = prompt("Enter an account name"); + if (!accounts.hasOwnProperty(accountName)) { + throw new Error(`No such account: ${accountName}`); + } + return accountName; +} + +function transfer(from, amount) { + if (accounts[from] < amount) return; + accounts[from] -= amount; + accounts[getAccount()] += amount; +} + +function transfer(from, amount) { + if (accounts[from] < amount) return; + let progress = 0; + try { + accounts[from] -= amount; + progress = 1; + accounts[getAccount()] += amount; + progress = 2; + } finally { + if (progress == 1) { + accounts[from] += amount; + } + } +} + +var InputError = class InputError extends Error {} + +function promptDirection(question) { + let result = prompt(question); + if (result.toLowerCase() == "left") return "L"; + if (result.toLowerCase() == "right") return "R"; + throw new InputError("Invalid direction: " + result); +} diff --git a/docs/code/chapter/11_async.js b/docs/code/chapter/11_async.js new file mode 100644 index 000000000..13c251748 --- /dev/null +++ b/docs/code/chapter/11_async.js @@ -0,0 +1,186 @@ +var bigOak = require("./crow-tech").bigOak; + +var defineRequestType = require("./crow-tech").defineRequestType; + +defineRequestType("note", (nest, content, source, done) => { + console.log(`${nest.name} received note: ${content}`); + done(); +}); + +function storage(nest, name) { + return new Promise(resolve => { + nest.readStorage(name, result => resolve(result)); + }); +} + +var Timeout = class Timeout extends Error {} + +function request(nest, target, type, content) { + return new Promise((resolve, reject) => { + let done = false; + function attempt(n) { + nest.send(target, type, content, (failed, value) => { + done = true; + if (failed) reject(failed); + else resolve(value); + }); + setTimeout(() => { + if (done) return; + else if (n < 3) attempt(n + 1); + else reject(new Timeout("Timed out")); + }, 250); + } + attempt(1); + }); +} + +function requestType(name, handler) { + defineRequestType(name, (nest, content, source, + callback) => { + try { + Promise.resolve(handler(nest, content, source)) + .then(response => callback(null, response), + failure => callback(failure)); + } catch (exception) { + callback(exception); + } + }); +} + +requestType("ping", () => "pong"); + +function availableNeighbors(nest) { + let requests = nest.neighbors.map(neighbor => { + return request(nest, neighbor, "ping") + .then(() => true, () => false); + }); + return Promise.all(requests).then(result => { + return nest.neighbors.filter((_, i) => result[i]); + }); +} + +var everywhere = require("./crow-tech").everywhere; + +everywhere(nest => { + nest.state.gossip = []; +}); + +function sendGossip(nest, message, exceptFor = null) { + nest.state.gossip.push(message); + for (let neighbor of nest.neighbors) { + if (neighbor == exceptFor) continue; + request(nest, neighbor, "gossip", message); + } +} + +requestType("gossip", (nest, message, source) => { + if (nest.state.gossip.includes(message)) return; + console.log(`${nest.name} received gossip '${ + message}' from ${source}`); + sendGossip(nest, message, source); +}); + +requestType("connections", (nest, {name, neighbors}, + source) => { + let connections = nest.state.connections; + if (JSON.stringify(connections.get(name)) == + JSON.stringify(neighbors)) return; + connections.set(name, neighbors); + broadcastConnections(nest, name, source); +}); + +function broadcastConnections(nest, name, exceptFor = null) { + for (let neighbor of nest.neighbors) { + if (neighbor == exceptFor) continue; + request(nest, neighbor, "connections", { + name, + neighbors: nest.state.connections.get(name) + }); + } +} + +everywhere(nest => { + nest.state.connections = new Map; + nest.state.connections.set(nest.name, nest.neighbors); + broadcastConnections(nest, nest.name); +}); + +function findRoute(from, to, connections) { + let work = [{at: from, via: null}]; + for (let i = 0; i < work.length; i++) { + let {at, via} = work[i]; + for (let next of connections.get(at) || []) { + if (next == to) return via; + if (!work.some(w => w.at == next)) { + work.push({at: next, via: via || next}); + } + } + } + return null; +} + +function routeRequest(nest, target, type, content) { + if (nest.neighbors.includes(target)) { + return request(nest, target, type, content); + } else { + let via = findRoute(nest.name, target, + nest.state.connections); + if (!via) throw new Error(`No route to ${target}`); + return request(nest, via, "route", + {target, type, content}); + } +} + +requestType("route", (nest, {target, type, content}) => { + return routeRequest(nest, target, type, content); +}); + +requestType("storage", (nest, name) => storage(nest, name)); + +function findInStorage(nest, name) { + return storage(nest, name).then(found => { + if (found != null) return found; + else return findInRemoteStorage(nest, name); + }); +} + +function network(nest) { + return Array.from(nest.state.connections.keys()); +} + +function findInRemoteStorage(nest, name) { + let sources = network(nest).filter(n => n != nest.name); + function next() { + if (sources.length == 0) { + return Promise.reject(new Error("Not found")); + } else { + let source = sources[Math.floor(Math.random() * + sources.length)]; + sources = sources.filter(n => n != source); + return routeRequest(nest, source, "storage", name) + .then(value => value != null ? value : next(), + next); + } + } + return next(); +} + +var Group = class Group { + constructor() { this.members = []; } + add(m) { this.members.add(m); } +} + +function anyStorage(nest, source, name) { + if (source == nest.name) return storage(nest, name); + else return routeRequest(nest, source, "storage", name); +} + +async function chicks(nest, year) { + let list = ""; + await Promise.all(network(nest).map(async name => { + list += `${name}: ${ + await anyStorage(nest, name, `chicks in ${year}`) + }\n`; + })); + return list; +} diff --git a/docs/code/chapter/12_language.js b/docs/code/chapter/12_language.js new file mode 100644 index 000000000..a8d6695ce --- /dev/null +++ b/docs/code/chapter/12_language.js @@ -0,0 +1,163 @@ +function parseExpression(program) { + program = skipSpace(program); + let match, expr; + if (match = /^"([^"]*)"/.exec(program)) { + expr = {type: "value", value: match[1]}; + } else if (match = /^\d+\b/.exec(program)) { + expr = {type: "value", value: Number(match[0])}; + } else if (match = /^[^\s(),#"]+/.exec(program)) { + expr = {type: "word", name: match[0]}; + } else { + throw new SyntaxError("Unexpected syntax: " + program); + } + + return parseApply(expr, program.slice(match[0].length)); +} + +function skipSpace(string) { + let first = string.search(/\S/); + if (first == -1) return ""; + return string.slice(first); +} + +function parseApply(expr, program) { + program = skipSpace(program); + if (program[0] != "(") { + return {expr: expr, rest: program}; + } + + program = skipSpace(program.slice(1)); + expr = {type: "apply", operator: expr, args: []}; + while (program[0] != ")") { + let arg = parseExpression(program); + expr.args.push(arg.expr); + program = skipSpace(arg.rest); + if (program[0] == ",") { + program = skipSpace(program.slice(1)); + } else if (program[0] != ")") { + throw new SyntaxError("Expected ',' or ')'"); + } + } + return parseApply(expr, program.slice(1)); +} + +function parse(program) { + let {expr, rest} = parseExpression(program); + if (skipSpace(rest).length > 0) { + throw new SyntaxError("Unexpected text after program"); + } + return expr; +} +// operator: {type: "word", name: "+"}, +// args: [{type: "word", name: "a"}, +// {type: "value", value: 10}]} + +var specialForms = Object.create(null); + +function evaluate(expr, scope) { + if (expr.type == "value") { + return expr.value; + } else if (expr.type == "word") { + if (expr.name in scope) { + return scope[expr.name]; + } else { + throw new ReferenceError( + `Undefined binding: ${expr.name}`); + } + } else if (expr.type == "apply") { + let {operator, args} = expr; + if (operator.type == "word" && + operator.name in specialForms) { + return specialForms[operator.name](expr.args, scope); + } else { + let op = evaluate(operator, scope); + if (typeof op == "function") { + return op(...args.map(arg => evaluate(arg, scope))); + } else { + throw new TypeError("Applying a non-function."); + } + } + } +} + +specialForms.if = (args, scope) => { + if (args.length != 3) { + throw new SyntaxError("Wrong number of args to if"); + } else if (evaluate(args[0], scope) !== false) { + return evaluate(args[1], scope); + } else { + return evaluate(args[2], scope); + } +}; + +specialForms.while = (args, scope) => { + if (args.length != 2) { + throw new SyntaxError("Wrong number of args to while"); + } + while (evaluate(args[0], scope) !== false) { + evaluate(args[1], scope); + } + + // Since undefined does not exist in Egg, we return false, + // for lack of a meaningful result. + return false; +}; + +specialForms.do = (args, scope) => { + let value = false; + for (let arg of args) { + value = evaluate(arg, scope); + } + return value; +}; + +specialForms.define = (args, scope) => { + if (args.length != 2 || args[0].type != "word") { + throw new SyntaxError("Incorrect use of define"); + } + let value = evaluate(args[1], scope); + scope[args[0].name] = value; + return value; +}; + +var topScope = Object.create(null); + +topScope.true = true; +topScope.false = false; + +for (let op of ["+", "-", "*", "/", "==", "<", ">"]) { + topScope[op] = Function("a, b", `return a ${op} b;`); +} + +topScope.print = value => { + console.log(value); + return value; +}; + +function run(program) { + return evaluate(parse(program), Object.create(topScope)); +} + +specialForms.fun = (args, scope) => { + if (!args.length) { + throw new SyntaxError("Functions need a body"); + } + let body = args[args.length - 1]; + let params = args.slice(0, args.length - 1).map(expr => { + if (expr.type != "word") { + throw new SyntaxError("Parameter names must be words"); + } + return expr.name; + }); + + return function() { + if (arguments.length != params.length) { + throw new TypeError("Wrong number of arguments"); + } + let localScope = Object.create(scope); + for (let i = 0; i < arguments.length; i++) { + localScope[params[i]] = arguments[i]; + } + return evaluate(body, localScope); + }; +}; diff --git a/docs/code/chapter/16_game.js b/docs/code/chapter/16_game.js new file mode 100644 index 000000000..8b0a2d741 --- /dev/null +++ b/docs/code/chapter/16_game.js @@ -0,0 +1,359 @@ +var simpleLevelPlan = ` +...................... +..#................#.. +..#..............=.#.. +..#.........o.o....#.. +..#.@......#####...#.. +..#####............#.. +......#++++++++++++#.. +......##############.. +......................`; + +var Level = class Level { + constructor(plan) { + let rows = plan.trim().split("\n").map(l => [...l]); + this.height = rows.length; + this.width = rows[0].length; + this.startActors = []; + + this.rows = rows.map((row, y) => { + return row.map((ch, x) => { + let type = levelChars[ch]; + if (typeof type == "string") return type; + this.startActors.push( + type.create(new Vec(x, y), ch)); + return "empty"; + }); + }); + } +} + +var State = class State { + constructor(level, actors, status) { + this.level = level; + this.actors = actors; + this.status = status; + } + + static start(level) { + return new State(level, level.startActors, "playing"); + } + + get player() { + return this.actors.find(a => a.type == "player"); + } +} + +var Vec = class Vec { + constructor(x, y) { + this.x = x; this.y = y; + } + plus(other) { + return new Vec(this.x + other.x, this.y + other.y); + } + times(factor) { + return new Vec(this.x * factor, this.y * factor); + } +} + +var Player = class Player { + constructor(pos, speed) { + this.pos = pos; + this.speed = speed; + } + + get type() { return "player"; } + + static create(pos) { + return new Player(pos.plus(new Vec(0, -0.5)), + new Vec(0, 0)); + } +} + +Player.prototype.size = new Vec(0.8, 1.5); + +var Lava = class Lava { + constructor(pos, speed, reset) { + this.pos = pos; + this.speed = speed; + this.reset = reset; + } + + get type() { return "lava"; } + + static create(pos, ch) { + if (ch == "=") { + return new Lava(pos, new Vec(2, 0)); + } else if (ch == "|") { + return new Lava(pos, new Vec(0, 2)); + } else if (ch == "v") { + return new Lava(pos, new Vec(0, 3), pos); + } + } +} + +Lava.prototype.size = new Vec(1, 1); + +var Coin = class Coin { + constructor(pos, basePos, wobble) { + this.pos = pos; + this.basePos = basePos; + this.wobble = wobble; + } + + get type() { return "coin"; } + + static create(pos) { + let basePos = pos.plus(new Vec(0.2, 0.1)); + return new Coin(basePos, basePos, + Math.random() * Math.PI * 2); + } +} + +Coin.prototype.size = new Vec(0.6, 0.6); + +var levelChars = { + ".": "empty", "#": "wall", "+": "lava", + "@": Player, "o": Coin, + "=": Lava, "|": Lava, "v": Lava +}; + +var simpleLevel = new Level(simpleLevelPlan); + +function elt(name, attrs, ...children) { + let dom = document.createElement(name); + for (let attr of Object.keys(attrs)) { + dom.setAttribute(attr, attrs[attr]); + } + for (let child of children) { + dom.appendChild(child); + } + return dom; +} + +var DOMDisplay = class DOMDisplay { + constructor(parent, level) { + this.dom = elt("div", {class: "game"}, drawGrid(level)); + this.actorLayer = null; + parent.appendChild(this.dom); + } + + clear() { this.dom.remove(); } +} + +var scale = 20; + +function drawGrid(level) { + return elt("table", { + class: "background", + style: `width: ${level.width * scale}px` + }, ...level.rows.map(row => + elt("tr", {style: `height: ${scale}px`}, + ...row.map(type => elt("td", {class: type}))) + )); +} + +function drawActors(actors) { + return elt("div", {}, ...actors.map(actor => { + let rect = elt("div", {class: `actor ${actor.type}`}); + rect.style.width = `${actor.size.x * scale}px`; + rect.style.height = `${actor.size.y * scale}px`; + rect.style.left = `${actor.pos.x * scale}px`; + rect.style.top = `${actor.pos.y * scale}px`; + return rect; + })); +} + +DOMDisplay.prototype.syncState = function(state) { + if (this.actorLayer) this.actorLayer.remove(); + this.actorLayer = drawActors(state.actors); + this.dom.appendChild(this.actorLayer); + this.dom.className = `game ${state.status}`; + this.scrollPlayerIntoView(state); +}; + +DOMDisplay.prototype.scrollPlayerIntoView = function(state) { + let width = this.dom.clientWidth; + let height = this.dom.clientHeight; + let margin = width / 3; + + // The viewport + let left = this.dom.scrollLeft, right = left + width; + let top = this.dom.scrollTop, bottom = top + height; + + let player = state.player; + let center = player.pos.plus(player.size.times(0.5)) + .times(scale); + + if (center.x < left + margin) { + this.dom.scrollLeft = center.x - margin; + } else if (center.x > right - margin) { + this.dom.scrollLeft = center.x + margin - width; + } + if (center.y < top + margin) { + this.dom.scrollTop = center.y - margin; + } else if (center.y > bottom - margin) { + this.dom.scrollTop = center.y + margin - height; + } +}; + +Level.prototype.touches = function(pos, size, type) { + var xStart = Math.floor(pos.x); + var xEnd = Math.ceil(pos.x + size.x); + var yStart = Math.floor(pos.y); + var yEnd = Math.ceil(pos.y + size.y); + + for (var y = yStart; y < yEnd; y++) { + for (var x = xStart; x < xEnd; x++) { + let isOutside = x < 0 || x >= this.width || + y < 0 || y >= this.height; + let here = isOutside ? "wall" : this.rows[y][x]; + if (here == type) return true; + } + } + return false; +}; + +State.prototype.update = function(time, keys) { + let actors = this.actors + .map(actor => actor.update(time, this, keys)); + let newState = new State(this.level, actors, this.status); + + if (newState.status != "playing") return newState; + + let player = newState.player; + if (this.level.touches(player.pos, player.size, "lava")) { + return new State(this.level, actors, "lost"); + } + + for (let actor of actors) { + if (actor != player && overlap(actor, player)) { + newState = actor.collide(newState); + } + } + return newState; +}; + +function overlap(actor1, actor2) { + return actor1.pos.x + actor1.size.x > actor2.pos.x && + actor1.pos.x < actor2.pos.x + actor2.size.x && + actor1.pos.y + actor1.size.y > actor2.pos.y && + actor1.pos.y < actor2.pos.y + actor2.size.y; +} + +Lava.prototype.collide = function(state) { + return new State(state.level, state.actors, "lost"); +}; + +Coin.prototype.collide = function(state) { + let filtered = state.actors.filter(a => a != this); + let status = state.status; + if (!filtered.some(a => a.type == "coin")) status = "won"; + return new State(state.level, filtered, status); +}; + +Lava.prototype.update = function(time, state) { + let newPos = this.pos.plus(this.speed.times(time)); + if (!state.level.touches(newPos, this.size, "wall")) { + return new Lava(newPos, this.speed, this.reset); + } else if (this.reset) { + return new Lava(this.reset, this.speed, this.reset); + } else { + return new Lava(this.pos, this.speed.times(-1)); + } +}; + +var wobbleSpeed = 8, wobbleDist = 0.07; + +Coin.prototype.update = function(time) { + let wobble = this.wobble + time * wobbleSpeed; + let wobblePos = Math.sin(wobble) * wobbleDist; + return new Coin(this.basePos.plus(new Vec(0, wobblePos)), + this.basePos, wobble); +}; + +var playerXSpeed = 7; +var gravity = 30; +var jumpSpeed = 17; + +Player.prototype.update = function(time, state, keys) { + let xSpeed = 0; + if (keys.ArrowLeft) xSpeed -= playerXSpeed; + if (keys.ArrowRight) xSpeed += playerXSpeed; + let pos = this.pos; + let movedX = pos.plus(new Vec(xSpeed * time, 0)); + if (!state.level.touches(movedX, this.size, "wall")) { + pos = movedX; + } + + let ySpeed = this.speed.y + time * gravity; + let movedY = pos.plus(new Vec(0, ySpeed * time)); + if (!state.level.touches(movedY, this.size, "wall")) { + pos = movedY; + } else if (keys.ArrowUp && ySpeed > 0) { + ySpeed = -jumpSpeed; + } else { + ySpeed = 0; + } + return new Player(pos, new Vec(xSpeed, ySpeed)); +}; + +function trackKeys(keys) { + let down = Object.create(null); + function track(event) { + if (keys.includes(event.key)) { + down[event.key] = event.type == "keydown"; + event.preventDefault(); + } + } + window.addEventListener("keydown", track); + window.addEventListener("keyup", track); + return down; +} + +var arrowKeys = + trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]); + +function runAnimation(frameFunc) { + let lastTime = null; + function frame(time) { + if (lastTime != null) { + let timeStep = Math.min(time - lastTime, 100) / 1000; + if (frameFunc(timeStep) === false) return; + } + lastTime = time; + requestAnimationFrame(frame); + } + requestAnimationFrame(frame); +} + +function runLevel(level, Display) { + let display = new Display(document.body, level); + let state = State.start(level); + let ending = 1; + return new Promise(resolve => { + runAnimation(time => { + state = state.update(time, arrowKeys); + display.syncState(state); + if (state.status == "playing") { + return true; + } else if (ending > 0) { + ending -= time; + return true; + } else { + display.clear(); + resolve(state.status); + return false; + } + }); + }); +} + +async function runGame(plans, Display) { + for (let level = 0; level < plans.length;) { + let status = await runLevel(new Level(plans[level]), + Display); + if (status == "won") level++; + } + console.log("You've won!"); +} diff --git a/docs/code/chapter/17_canvas.js b/docs/code/chapter/17_canvas.js new file mode 100644 index 000000000..dd6ee70f4 --- /dev/null +++ b/docs/code/chapter/17_canvas.js @@ -0,0 +1,143 @@ +var results = [ + {name: "Satisfied", count: 1043, color: "lightblue"}, + {name: "Neutral", count: 563, color: "lightgreen"}, + {name: "Unsatisfied", count: 510, color: "pink"}, + {name: "No comment", count: 175, color: "silver"} +]; + +function flipHorizontally(context, around) { + context.translate(around, 0); + context.scale(-1, 1); + context.translate(-around, 0); +} + +var CanvasDisplay = class CanvasDisplay { + constructor(parent, level) { + this.canvas = document.createElement("canvas"); + this.canvas.width = Math.min(600, level.width * scale); + this.canvas.height = Math.min(450, level.height * scale); + parent.appendChild(this.canvas); + this.cx = this.canvas.getContext("2d"); + + this.flipPlayer = false; + + this.viewport = { + left: 0, + top: 0, + width: this.canvas.width / scale, + height: this.canvas.height / scale + }; + } + + clear() { + this.canvas.remove(); + } +} + +CanvasDisplay.prototype.syncState = function(state) { + this.updateViewport(state); + this.clearDisplay(state.status); + this.drawBackground(state.level); + this.drawActors(state.actors); +}; + +CanvasDisplay.prototype.updateViewport = function(state) { + let view = this.viewport, margin = view.width / 3; + let player = state.player; + let center = player.pos.plus(player.size.times(0.5)); + + if (center.x < view.left + margin) { + view.left = Math.max(center.x - margin, 0); + } else if (center.x > view.left + view.width - margin) { + view.left = Math.min(center.x + margin - view.width, + state.level.width - view.width); + } + if (center.y < view.top + margin) { + view.top = Math.max(center.y - margin, 0); + } else if (center.y > view.top + view.height - margin) { + view.top = Math.min(center.y + margin - view.height, + state.level.height - view.height); + } +}; + +CanvasDisplay.prototype.clearDisplay = function(status) { + if (status == "won") { + this.cx.fillStyle = "rgb(68, 191, 255)"; + } else if (status == "lost") { + this.cx.fillStyle = "rgb(44, 136, 214)"; + } else { + this.cx.fillStyle = "rgb(52, 166, 251)"; + } + this.cx.fillRect(0, 0, + this.canvas.width, this.canvas.height); +}; + +var otherSprites = document.createElement("img"); +otherSprites.src = "img/sprites.png"; + +CanvasDisplay.prototype.drawBackground = function(level) { + let {left, top, width, height} = this.viewport; + let xStart = Math.floor(left); + let xEnd = Math.ceil(left + width); + let yStart = Math.floor(top); + let yEnd = Math.ceil(top + height); + + for (let y = yStart; y < yEnd; y++) { + for (let x = xStart; x < xEnd; x++) { + let tile = level.rows[y][x]; + if (tile == "empty") continue; + let screenX = (x - left) * scale; + let screenY = (y - top) * scale; + let tileX = tile == "lava" ? scale : 0; + this.cx.drawImage(otherSprites, + tileX, 0, scale, scale, + screenX, screenY, scale, scale); + } + } +}; + +var playerSprites = document.createElement("img"); +playerSprites.src = "img/player.png"; +var playerXOverlap = 4; + +CanvasDisplay.prototype.drawPlayer = function(player, x, y, + width, height){ + width += playerXOverlap * 2; + x -= playerXOverlap; + if (player.speed.x != 0) { + this.flipPlayer = player.speed.x < 0; + } + + let tile = 8; + if (player.speed.y != 0) { + tile = 9; + } else if (player.speed.x != 0) { + tile = Math.floor(Date.now() / 60) % 8; + } + + this.cx.save(); + if (this.flipPlayer) { + flipHorizontally(this.cx, x + width / 2); + } + let tileX = tile * width; + this.cx.drawImage(playerSprites, tileX, 0, width, height, + x, y, width, height); + this.cx.restore(); +}; + +CanvasDisplay.prototype.drawActors = function(actors) { + for (let actor of actors) { + let width = actor.size.x * scale; + let height = actor.size.y * scale; + let x = (actor.pos.x - this.viewport.left) * scale; + let y = (actor.pos.y - this.viewport.top) * scale; + if (actor.type == "player") { + this.drawPlayer(actor, x, y, width, height); + } else { + let tileX = (actor.type == "coin" ? 2 : 1) * scale; + this.cx.drawImage(otherSprites, + tileX, 0, width, height, + x, y, width, height); + } + } +}; diff --git a/docs/code/chapter/19_paint.js b/docs/code/chapter/19_paint.js new file mode 100644 index 000000000..c13f21132 --- /dev/null +++ b/docs/code/chapter/19_paint.js @@ -0,0 +1,340 @@ +var Picture = class Picture { + constructor(width, height, pixels) { + this.width = width; + this.height = height; + this.pixels = pixels; + } + static empty(width, height, color) { + let pixels = new Array(width * height).fill(color); + return new Picture(width, height, pixels); + } + pixel(x, y) { + return this.pixels[x + y * this.width]; + } + draw(pixels) { + let copy = this.pixels.slice(); + for (let {x, y, color} of pixels) { + copy[x + y * this.width] = color; + } + return new Picture(this.width, this.height, copy); + } +} + +function updateState(state, action) { + return Object.assign({}, state, action); +} + +function elt(type, props, ...children) { + let dom = document.createElement(type); + if (props) Object.assign(dom, props); + for (let child of children) { + if (typeof child != "string") dom.appendChild(child); + else dom.appendChild(document.createTextNode(child)); + } + return dom; +} + +var scale = 10; + +var PictureCanvas = class PictureCanvas { + constructor(picture, pointerDown) { + this.dom = elt("canvas", { + onmousedown: event => this.mouse(event, pointerDown), + ontouchstart: event => this.touch(event, pointerDown) + }); + this.syncState(picture); + } + syncState(picture) { + if (this.picture == picture) return; + this.picture = picture; + drawPicture(this.picture, this.dom, scale); + } +} + +function drawPicture(picture, canvas, scale) { + canvas.width = picture.width * scale; + canvas.height = picture.height * scale; + let cx = canvas.getContext("2d"); + + for (let y = 0; y < picture.height; y++) { + for (let x = 0; x < picture.width; x++) { + cx.fillStyle = picture.pixel(x, y); + cx.fillRect(x * scale, y * scale, scale, scale); + } + } +} + +PictureCanvas.prototype.mouse = function(downEvent, onDown) { + if (downEvent.button != 0) return; + let pos = pointerPosition(downEvent, this.dom); + let onMove = onDown(pos); + if (!onMove) return; + let move = moveEvent => { + if (moveEvent.buttons == 0) { + this.dom.removeEventListener("mousemove", move); + } else { + let newPos = pointerPosition(moveEvent, this.dom); + if (newPos.x == pos.x && newPos.y == pos.y) return; + pos = newPos; + onMove(newPos); + } + }; + this.dom.addEventListener("mousemove", move); +}; + +function pointerPosition(pos, domNode) { + let rect = domNode.getBoundingClientRect(); + return {x: Math.floor((pos.clientX - rect.left) / scale), + y: Math.floor((pos.clientY - rect.top) / scale)}; +} + +PictureCanvas.prototype.touch = function(startEvent, + onDown) { + let pos = pointerPosition(startEvent.touches[0], this.dom); + let onMove = onDown(pos); + startEvent.preventDefault(); + if (!onMove) return; + let move = moveEvent => { + let newPos = pointerPosition(moveEvent.touches[0], + this.dom); + if (newPos.x == pos.x && newPos.y == pos.y) return; + pos = newPos; + onMove(newPos); + }; + let end = () => { + this.dom.removeEventListener("touchmove", move); + this.dom.removeEventListener("touchend", end); + }; + this.dom.addEventListener("touchmove", move); + this.dom.addEventListener("touchend", end); +}; + +var PixelEditor = class PixelEditor { + constructor(state, config) { + let {tools, controls, dispatch} = config; + this.state = state; + + this.canvas = new PictureCanvas(state.picture, pos => { + let tool = tools[this.state.tool]; + let onMove = tool(pos, this.state, dispatch); + if (onMove) return pos => onMove(pos, this.state); + }); + this.controls = controls.map( + Control => new Control(state, config)); + this.dom = elt("div", {}, this.canvas.dom, elt("br"), + ...this.controls.reduce( + (a, c) => a.concat(" ", c.dom), [])); + } + syncState(state) { + this.state = state; + this.canvas.syncState(state.picture); + for (let ctrl of this.controls) ctrl.syncState(state); + } +} + +var ToolSelect = class ToolSelect { + constructor(state, {tools, dispatch}) { + this.select = elt("select", { + onchange: () => dispatch({tool: this.select.value}) + }, ...Object.keys(tools).map(name => elt("option", { + selected: name == state.tool + }, name))); + this.dom = elt("label", null, "🖌 Tool: ", this.select); + } + syncState(state) { this.select.value = state.tool; } +} + +var ColorSelect = class ColorSelect { + constructor(state, {dispatch}) { + this.input = elt("input", { + type: "color", + value: state.color, + onchange: () => dispatch({color: this.input.value}) + }); + this.dom = elt("label", null, "🎨 Color: ", this.input); + } + syncState(state) { this.input.value = state.color; } +} + +function draw(pos, state, dispatch) { + function drawPixel({x, y}, state) { + let drawn = {x, y, color: state.color}; + dispatch({picture: state.picture.draw([drawn])}); + } + drawPixel(pos, state); + return drawPixel; +} + +function rectangle(start, state, dispatch) { + function drawRectangle(pos) { + let xStart = Math.min(start.x, pos.x); + let yStart = Math.min(start.y, pos.y); + let xEnd = Math.max(start.x, pos.x); + let yEnd = Math.max(start.y, pos.y); + let drawn = []; + for (let y = yStart; y <= yEnd; y++) { + for (let x = xStart; x <= xEnd; x++) { + drawn.push({x, y, color: state.color}); + } + } + dispatch({picture: state.picture.draw(drawn)}); + } + drawRectangle(start); + return drawRectangle; +} + +var around = [{dx: -1, dy: 0}, {dx: 1, dy: 0}, + {dx: 0, dy: -1}, {dx: 0, dy: 1}]; + +function fill({x, y}, state, dispatch) { + let targetColor = state.picture.pixel(x, y); + let drawn = [{x, y, color: state.color}]; + for (let done = 0; done < drawn.length; done++) { + for (let {dx, dy} of around) { + let x = drawn[done].x + dx, y = drawn[done].y + dy; + if (x >= 0 && x < state.picture.width && + y >= 0 && y < state.picture.height && + state.picture.pixel(x, y) == targetColor && + !drawn.some(p => p.x == x && p.y == y)) { + drawn.push({x, y, color: state.color}); + } + } + } + dispatch({picture: state.picture.draw(drawn)}); +} + +function pick(pos, state, dispatch) { + dispatch({color: state.picture.pixel(pos.x, pos.y)}); +} + +var SaveButton = class SaveButton { + constructor(state) { + this.picture = state.picture; + this.dom = elt("button", { + onclick: () => this.save() + }, "💾 Save"); + } + save() { + let canvas = elt("canvas"); + drawPicture(this.picture, canvas, 1); + let link = elt("a", { + href: canvas.toDataURL(), + download: "pixelart.png" + }); + document.body.appendChild(link); + link.click(); + link.remove(); + } + syncState(state) { this.picture = state.picture; } +} + +var LoadButton = class LoadButton { + constructor(_, {dispatch}) { + this.dom = elt("button", { + onclick: () => startLoad(dispatch) + }, "📁 Load"); + } + syncState() {} +} + +function startLoad(dispatch) { + let input = elt("input", { + type: "file", + onchange: () => finishLoad(input.files[0], dispatch) + }); + document.body.appendChild(input); + input.click(); + input.remove(); +} + +function finishLoad(file, dispatch) { + if (file == null) return; + let reader = new FileReader(); + reader.addEventListener("load", () => { + let image = elt("img", { + onload: () => dispatch({ + picture: pictureFromImage(image) + }), + src: reader.result + }); + }); + reader.readAsDataURL(file); +} + +function pictureFromImage(image) { + let width = Math.min(100, image.width); + let height = Math.min(100, image.height); + let canvas = elt("canvas", {width, height}); + let cx = canvas.getContext("2d"); + cx.drawImage(image, 0, 0); + let pixels = []; + let {data} = cx.getImageData(0, 0, width, height); + + function hex(n) { + return n.toString(16).padStart(2, "0"); + } + for (let i = 0; i < data.length; i += 4) { + let [r, g, b] = data.slice(i, i + 3); + pixels.push("#" + hex(r) + hex(g) + hex(b)); + } + return new Picture(width, height, pixels); +} + +function historyUpdateState(state, action) { + if (action.undo == true) { + if (state.done.length == 0) return state; + return Object.assign({}, state, { + picture: state.done[0], + done: state.done.slice(1), + doneAt: 0 + }); + } else if (action.picture && + state.doneAt < Date.now() - 1000) { + return Object.assign({}, state, action, { + done: [state.picture, ...state.done], + doneAt: Date.now() + }); + } else { + return Object.assign({}, state, action); + } +} + +var UndoButton = class UndoButton { + constructor(state, {dispatch}) { + this.dom = elt("button", { + onclick: () => dispatch({undo: true}), + disabled: state.done.length == 0 + }, "⮪ Undo"); + } + syncState(state) { + this.dom.disabled = state.done.length == 0; + } +} + +var startState = { + tool: "draw", + color: "#000000", + picture: Picture.empty(60, 30, "#f0f0f0"), + done: [], + doneAt: 0 +}; + +var baseTools = {draw, fill, rectangle, pick}; + +var baseControls = [ + ToolSelect, ColorSelect, SaveButton, LoadButton, UndoButton +]; + +function startPixelEditor({state = startState, + tools = baseTools, + controls = baseControls}) { + let app = new PixelEditor(state, { + tools, + controls, + dispatch(action) { + state = historyUpdateState(state, action); + app.syncState(state); + } + }); + return app.dom; +} diff --git a/docs/code/chapter/22_fast.js b/docs/code/chapter/22_fast.js new file mode 100644 index 000000000..75161751d --- /dev/null +++ b/docs/code/chapter/22_fast.js @@ -0,0 +1,156 @@ +var GraphNode = class GraphNode { + constructor() { + this.pos = new Vec(Math.random() * 1000, + Math.random() * 1000); + this.edges = []; + } + connect(other) { + this.edges.push(other); + other.edges.push(this); + } + hasEdge(other) { + return this.edges.includes(other); + } +} + +function treeGraph(depth, branches) { + let graph = [new GraphNode()]; + if (depth > 1) { + for (let i = 0; i < branches; i++) { + let subGraph = treeGraph(depth - 1, branches); + graph[0].connect(subGraph[0]); + graph = graph.concat(subGraph); + } + } + return graph; +} + +var springLength = 40; +var springStrength = 0.1; + +var repulsionStrength = 1500; + +function forceDirected_simple(graph) { + for (let node of graph) { + for (let other of graph) { + if (other == node) continue; + let apart = other.pos.minus(node.pos); + let distance = Math.max(1, apart.length); + let forceSize = -repulsionStrength / (distance * distance); + if (node.hasEdge(other)) { + forceSize += (distance - springLength) * springStrength; + } + let normalized = apart.times(1 / distance); + node.pos = node.pos.plus(normalized.times(forceSize)); + } + } +} + +function runLayout(implementation, graph) { + function run(steps, time) { + let startTime = Date.now(); + for (let i = 0; i < 100; i++) { + implementation(graph); + } + time += Date.now() - startTime; + drawGraph(graph); + + if (steps == 0) console.log(time); + else requestAnimationFrame(() => run(steps - 100, time)); + } + run(4000, 0); +} + +function forceDirected_noRepeat(graph) { + for (let i = 0; i < graph.length; i++) { + let node = graph[i]; + for (let j = i + 1; j < graph.length; j++) { + let other = graph[j]; + let apart = other.pos.minus(node.pos); + let distance = Math.max(1, apart.length); + let forceSize = -repulsionStrength / (distance * distance); + if (node.hasEdge(other)) { + forceSize += (distance - springLength) * springStrength; + } + let applied = apart.times(forceSize / distance); + node.pos = node.pos.plus(applied); + other.pos = other.pos.minus(applied); + } + } +} + +var skipDistance = 175; + +function forceDirected_skip(graph) { + for (let i = 0; i < graph.length; i++) { + let node = graph[i]; + for (let j = i + 1; j < graph.length; j++) { + let other = graph[j]; + let apart = other.pos.minus(node.pos); + let distance = Math.max(1, apart.length); + let hasEdge = node.hasEdge(other); + if (!hasEdge && distance > skipDistance) continue; + let forceSize = -repulsionStrength / (distance * distance); + if (hasEdge) { + forceSize += (distance - springLength) * springStrength; + } + let applied = apart.times(forceSize / distance); + node.pos = node.pos.plus(applied); + other.pos = other.pos.minus(applied); + } + } +} + +GraphNode.prototype.hasEdgeFast = function(other) { + for (let i = 0; i < this.edges.length; i++) { + if (this.edges[i] === other) return true; + } + return false; +}; + +function forceDirected_hasEdgeFast(graph) { + for (let i = 0; i < graph.length; i++) { + let node = graph[i]; + for (let j = i + 1; j < graph.length; j++) { + let other = graph[j]; + let apart = other.pos.minus(node.pos); + let distance = Math.max(1, apart.length); + let hasEdge = node.hasEdgeFast(other); + if (!hasEdge && distance > skipDistance) continue; + let forceSize = -repulsionStrength / (distance * distance); + if (hasEdge) { + forceSize += (distance - springLength) * springStrength; + } + let applied = apart.times(forceSize / distance); + node.pos = node.pos.plus(applied); + other.pos = other.pos.minus(applied); + } + } +} + +function forceDirected_noVector(graph) { + for (let i = 0; i < graph.length; i++) { + let node = graph[i]; + for (let j = i + 1; j < graph.length; j++) { + let other = graph[j]; + let apartX = other.pos.x - node.pos.x; + let apartY = other.pos.y - node.pos.y; + let distance = Math.max(1, Math.sqrt(apartX * apartX + apartY * apartY)); + let hasEdge = node.hasEdgeFast(other); + if (!hasEdge && distance > skipDistance) continue; + let forceSize = -repulsionStrength / (distance * distance); + if (hasEdge) { + forceSize += (distance - springLength) * springStrength; + } + let forceX = apartX * forceSize / distance; + let forceY = apartY * forceSize / distance; + node.pos.x += forceX; node.pos.y += forceY; + other.pos.x -= forceX; other.pos.y -= forceY; + } + } +} + +var mangledGraph = treeGraph(4, 4); +for (let node of mangledGraph) { + node[`p${Math.floor(Math.random() * 999)}`] = true; +} diff --git a/docs/code/crow-tech.js b/docs/code/crow-tech.js new file mode 100644 index 000000000..b61560aa0 --- /dev/null +++ b/docs/code/crow-tech.js @@ -0,0 +1,136 @@ +(function() { + const connections = [ + "Church Tower-Sportsgrounds", "Church Tower-Big Maple", "Big Maple-Sportsgrounds", + "Big Maple-Woods", "Big Maple-Fabienne's Garden", "Fabienne's Garden-Woods", + "Fabienne's Garden-Cow Pasture", "Cow Pasture-Big Oak", "Big Oak-Butcher Shop", + "Butcher Shop-Tall Poplar", "Tall Poplar-Sportsgrounds", "Tall Poplar-Chateau", + "Chateau-Great Pine", "Great Pine-Jacques' Farm", "Jacques' Farm-Hawthorn", + "Great Pine-Hawthorn", "Hawthorn-Gilles' Garden", "Great Pine-Gilles' Garden", + "Gilles' Garden-Big Oak", "Gilles' Garden-Butcher Shop", "Chateau-Butcher Shop" + ] + + function storageFor(name) { + let storage = Object.create(null) + storage["food caches"] = ["cache in the oak", "cache in the meadow", "cache under the hedge"] + storage["cache in the oak"] = "A hollow above the third big branch from the bottom. Several pieces of bread and a pile of acorns." + storage["cache in the meadow"] = "Buried below the patch of nettles (south side). A dead snake." + storage["cache under the hedge"] = "Middle of the hedge at Gilles' garden. Marked with a forked twig. Two bottles of beer." + storage["enemies"] = ["Farmer Jacques' dog", "The butcher", "That one-legged jackdaw", "The boy with the airgun"] + if (name == "Church Tower" || name == "Hawthorn" || name == "Chateau") + storage["events on 2017-12-21"] = "Deep snow. Butcher's garbage can fell over. We chased off the ravens from Saint-Vulbas." + let hash = 0 + for (let i = 0; i < name.length; i++) hash += name.charCodeAt(i) + for (let y = 1985; y <= 2018; y++) { + storage[`chicks in ${y}`] = hash % 6 + hash = Math.abs((hash << 2) ^ (hash + y)) + } + if (name == "Big Oak") storage.scalpel = "Gilles' Garden" + else if (name == "Gilles' Garden") storage.scalpel = "Woods" + else if (name == "Woods") storage.scalpel = "Chateau" + else if (name == "Chateau" || name == "Butcher Shop") storage.scalpel = "Butcher Shop" + else storage.scalpel = "Big Oak" + for (let prop of Object.keys(storage)) storage[prop] = JSON.stringify(storage[prop]) + return storage + } + + class Network { + constructor(connections, storageFor) { + let reachable = Object.create(null) + for (let [from, to] of connections.map(conn => conn.split("-"))) { + ;(reachable[from] || (reachable[from] = [])).push(to) + ;(reachable[to] || (reachable[to] = [])).push(from) + } + this.nodes = Object.create(null) + for (let name of Object.keys(reachable)) + this.nodes[name] = new Node(name, reachable[name], this, storageFor(name)) + this.types = Object.create(null) + } + + defineRequestType(name, handler) { + this.types[name] = handler + } + + everywhere(f) { + for (let node of Object.values(this.nodes)) f(node) + } + } + + const $storage = Symbol("storage"), $network = Symbol("network") + + function ser(value) { + return value == null ? null : JSON.parse(JSON.stringify(value)) + } + + class Node { + constructor(name, neighbors, network, storage) { + this.name = name + this.neighbors = neighbors + this[$network] = network + this.state = Object.create(null) + this[$storage] = storage + } + + send(to, type, message, callback) { + let toNode = this[$network].nodes[to] + if (!toNode || !this.neighbors.includes(to)) + return callback(new Error(`${to} is not reachable from ${this.name}`)) + let handler = this[$network].types[type] + if (!handler) + return callback(new Error("Unknown request type " + type)) + if (Math.random() > 0.03) setTimeout(() => { + try { + handler(toNode, ser(message), this.name, (error, response) => { + setTimeout(() => callback(error, ser(response)), 10) + }) + } catch(e) { + callback(e) + } + }, 10 + Math.floor(Math.random() * 10)) + } + + readStorage(name, callback) { + let value = this[$storage][name] + setTimeout(() => callback(value && JSON.parse(value)), 20) + } + + writeStorage(name, value, callback) { + setTimeout(() => { + this[$storage][name] = JSON.stringify(value) + callback() + }, 20) + } + } + + let network = new Network(connections, storageFor) + exports.bigOak = network.nodes["Big Oak"] + exports.everywhere = network.everywhere.bind(network) + exports.defineRequestType = network.defineRequestType.bind(network) + + if (typeof __sandbox != "undefined") { + __sandbox.handleDeps = false + __sandbox.notify.onLoad = () => { + // Kludge to make sure some functions are delayed until the + // nodes have been running for 500ms, to give them a chance to + // propagate network information. + let waitFor = Date.now() + 500 + function wrapWaiting(f) { + return function(...args) { + let wait = waitFor - Date.now() + if (wait <= 0) return f(...args) + return new Promise(ok => setTimeout(ok, wait)).then(() => f(...args)) + } + } + for (let n of ["routeRequest", "findInStorage", "chicks"]) + window[n] = wrapWaiting(window[n]) + } + } + + if (typeof window != "undefined") { + window.require = name => { + if (name != "./crow-tech") throw new Error("Crow nests can only require \"./crow-tech\"") + return exports + } + } else if (typeof module != "undefined" && module.exports) { + module.exports = exports + } +})() diff --git a/docs/code/draw_layout.js b/docs/code/draw_layout.js new file mode 100644 index 000000000..0a2541c7e --- /dev/null +++ b/docs/code/draw_layout.js @@ -0,0 +1,103 @@ +// The familiar Vec type. + +class Vec { + constructor(x, y) { + this.x = x; this.y = y; + } + plus(other) { + return new Vec(this.x + other.x, this.y + other.y); + } + minus(other) { + return new Vec(this.x - other.x, this.y - other.y); + } + times(factor) { + return new Vec(this.x * factor, this.y * factor); + } + get length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } +} + +// Since we will want to inspect the layouts our code produces, let's +// first write code to draw a graph onto a canvas. Since we don't know +// in advance how big the graph is, the `Scale` object computes a +// scale and offset so that all nodes fit onto the given canvas. + +const nodeSize = 8; + +function drawGraph(graph) { + let canvas = document.querySelector("canvas"); + if (!canvas) { + canvas = document.body.appendChild(document.createElement("canvas")); + canvas.width = canvas.height = 400; + } + let cx = canvas.getContext("2d"); + + cx.clearRect(0, 0, canvas.width, canvas.height); + let scale = new Scale(graph, canvas.width, canvas.height); + + // Draw the edges. + cx.strokeStyle = "orange"; + cx.lineWidth = 3; + for (let i = 0; i < graph.length; i++) { + let origin = graph[i]; + for (let target of origin.edges) { + if (graph.indexOf(target) <= i) continue; + cx.beginPath(); + cx.moveTo(scale.x(origin.pos.x), scale.y(origin.pos.y)); + cx.lineTo(scale.x(target.pos.x), scale.y(target.pos.y)); + cx.stroke(); + } + } + + // Draw the nodes. + cx.fillStyle = "purple"; + for (let node of graph) { + cx.beginPath(); + cx.arc(scale.x(node.pos.x), scale.y(node.pos.y), nodeSize, 0, 7); + cx.fill(); + } +} + +// The function starts by drawing the edges, so that they appear +// behind the nodes. Since the nodes on _both_ side of an edge refer +// to each other, and we don't want to draw every edge twice, edges +// are only drawn then the target comes _after_ the current node in +// the `graph` array. + +// When the edges have been drawn, the nodes are drawn on top of them +// as purple discs. Remember that the last argument to `arc` gives the +// rotation, and we have to pass something bigger than 2π to get a +// full circle. + +// Finding a scale at which to draw the graph is done by finding the +// top left and bottom right corners of the area taken up by the +// nodes. The offset at which nodes are drawn is based on the top left +// corner, and the scale is based on the size of the canvas divided by +// the distance between those corners. The function reserves space +// along the sides of the canvas based on the `nodeSize` variable, so +// that the circles drawn around nodes’ center points don't get cut off. + +class Scale { + constructor(graph, width, height) { + let xs = graph.map(node => node.pos.x); + let ys = graph.map(node => node.pos.y); + let minX = Math.min(...xs); + let minY = Math.min(...ys); + let maxX = Math.max(...xs); + let maxY = Math.max(...ys); + + this.offsetX = minX; this.offsetY = minY; + this.scaleX = (width - 2 * nodeSize) / (maxX - minX); + this.scaleY = (height - 2 * nodeSize) / (maxY - minY); + } + + // The `x` and `y` methods convert from graph coordinates into + // canvas coordinates. + x(x) { + return this.scaleX * (x - this.offsetX) + nodeSize; + } + y(y) { + return this.scaleY * (y - this.offsetY) + nodeSize; + } +} diff --git a/docs/code/file_server.js b/docs/code/file_server.js new file mode 100644 index 000000000..58091984e --- /dev/null +++ b/docs/code/file_server.js @@ -0,0 +1,109 @@ +const {createServer} = require("http"); + +const methods = Object.create(null); + +createServer((request, response) => { + let handler = methods[request.method] || notAllowed; + handler(request) + .catch(error => { + if (error.status != null) return error; + return {body: String(error), status: 500}; + }) + .then(({body, status = 200, type = "text/plain"}) => { + response.writeHead(status, {"Content-Type": type}); + if (body && body.pipe) body.pipe(response); + else response.end(body); + }); +}).listen(8000); + +async function notAllowed(request) { + return { + status: 405, + body: `Method ${request.method} not allowed.` + }; +} + +var {parse} = require("url"); +var {resolve, sep} = require("path"); + +var baseDirectory = process.cwd(); + +function urlPath(url) { + let {pathname} = parse(url); + let path = resolve(decodeURIComponent(pathname).slice(1)); + if (path != baseDirectory && + !path.startsWith(baseDirectory + sep)) { + throw {status: 403, body: "Forbidden"}; + } + return path; +} + +const {createReadStream} = require("fs"); +const {stat, readdir} = require("fs").promises; +const mime = require("mime"); + +methods.GET = async function(request) { + let path = urlPath(request.url); + let stats; + try { + stats = await stat(path); + } catch (error) { + if (error.code != "ENOENT") throw error; + else return {status: 404, body: "File not found"}; + } + if (stats.isDirectory()) { + return {body: (await readdir(path)).join("\n")}; + } else { + return {body: createReadStream(path), + type: mime.getType(path)}; + } +}; + +const {rmdir, unlink} = require("fs").promises; + +methods.DELETE = async function(request) { + let path = urlPath(request.url); + let stats; + try { + stats = await stat(path); + } catch (error) { + if (error.code != "ENOENT") throw error; + else return {status: 204}; + } + if (stats.isDirectory()) await rmdir(path); + else await unlink(path); + return {status: 204}; +}; + +const {createWriteStream} = require("fs"); + +function pipeStream(from, to) { + return new Promise((resolve, reject) => { + from.on("error", reject); + to.on("error", reject); + to.on("finish", resolve); + from.pipe(to); + }); +} + +methods.PUT = async function(request) { + let path = urlPath(request.url); + await pipeStream(request, createWriteStream(path)); + return {status: 204}; +}; + +const {mkdir} = require("fs").promises; + +methods.MKCOL = async function(request) { + let path = urlPath(request.url); + let stats; + try { + stats = await stat(path); + } catch (error) { + if (error.code != "ENOENT") throw error; + await mkdir(path); + return {status: 204}; + } + if (stats.isDirectory()) return {status: 204}; + else return {status: 400, body: "Not a directory"}; +}; diff --git a/docs/code/hello.js b/docs/code/hello.js new file mode 100644 index 000000000..619a8c516 --- /dev/null +++ b/docs/code/hello.js @@ -0,0 +1 @@ +alert("hello!"); diff --git a/docs/code/index.html b/docs/code/index.html new file mode 100644 index 000000000..b470928e3 --- /dev/null +++ b/docs/code/index.html @@ -0,0 +1,66 @@ + + + + + + Eloquent JavaScript :: Code Sandbox + + + + + + + + + + + +
+ Note: If you are reading the second edition of the book, + you'll want to go + to that + edition's sandbox instead! +
+ +
+

Code Sandbox

+ +

You can use this page to download source code and solutions to + exercises for the book Eloquent JavaScript, and to directly run code + in the context of chapters from that book, either to solve exercises + to simply play around.

+ +

+ Chapter: + + +

+ +
+ +
+
+ +
+ To run this chapter's code locally, use these files: +
    +
    + +
    + These files contain this chapter’s project code: +
      +
      + +

      If you've solved the exercise and want to compare your code with + mine, or you really tried, but can't get your code to work, + you can (or download it).

      + +

      + The base environment for this chapter (if any) is available in the + sandbox above, allowing you to run the chapter's examples by + simply pasting them into the editor. +

      +
      diff --git a/docs/code/intro.js b/docs/code/intro.js new file mode 100644 index 000000000..5b20af43a --- /dev/null +++ b/docs/code/intro.js @@ -0,0 +1,28 @@ +function range(start, end, step) { + if (step == null) step = 1; + var array = []; + + if (step > 0) { + for (var i = start; i <= end; i += step) + array.push(i); + } else { + for (var i = start; i >= end; i += step) + array.push(i); + } + return array; +} + +function sum(array) { + var total = 0; + for (var i = 0; i < array.length; i++) + total += array[i]; + return total; +} + +function factorial(n) { + if (n == 0) { + return 1; + } else { + return factorial(n - 1) * n; + } +} diff --git a/docs/code/journal.js b/docs/code/journal.js new file mode 100644 index 000000000..ee98d227f --- /dev/null +++ b/docs/code/journal.js @@ -0,0 +1,99 @@ +var JOURNAL = [ + {"events":["carrot","exercise","weekend"],"squirrel":false}, + {"events":["bread","pudding","brushed teeth","weekend","touched tree"],"squirrel":false}, + {"events":["carrot","nachos","brushed teeth","cycling","weekend"],"squirrel":false}, + {"events":["brussel sprouts","ice cream","brushed teeth","computer","weekend"],"squirrel":false}, + {"events":["potatoes","candy","brushed teeth","exercise","weekend","dentist"],"squirrel":false}, + {"events":["brussel sprouts","pudding","brushed teeth","running","weekend"],"squirrel":false}, + {"events":["pizza","brushed teeth","computer","work","touched tree"],"squirrel":false}, + {"events":["bread","beer","brushed teeth","cycling","work"],"squirrel":false}, + {"events":["cauliflower","brushed teeth","work"],"squirrel":false}, + {"events":["pizza","brushed teeth","cycling","work"],"squirrel":false}, + {"events":["lasagna","nachos","brushed teeth","work"],"squirrel":false}, + {"events":["brushed teeth","weekend","touched tree"],"squirrel":false}, + {"events":["lettuce","brushed teeth","television","weekend"],"squirrel":false}, + {"events":["spaghetti","brushed teeth","work"],"squirrel":false}, + {"events":["brushed teeth","computer","work"],"squirrel":false}, + {"events":["lettuce","nachos","brushed teeth","work"],"squirrel":false}, + {"events":["carrot","brushed teeth","running","work"],"squirrel":false}, + {"events":["brushed teeth","work"],"squirrel":false}, + {"events":["cauliflower","reading","weekend"],"squirrel":false}, + {"events":["bread","brushed teeth","weekend"],"squirrel":false}, + {"events":["lasagna","brushed teeth","exercise","work"],"squirrel":false}, + {"events":["spaghetti","brushed teeth","reading","work"],"squirrel":false}, + {"events":["carrot","ice cream","brushed teeth","television","work"],"squirrel":false}, + {"events":["spaghetti","nachos","work"],"squirrel":false}, + {"events":["cauliflower","ice cream","brushed teeth","cycling","work"],"squirrel":false}, + {"events":["spaghetti","peanuts","computer","weekend"],"squirrel":true}, + {"events":["potatoes","ice cream","brushed teeth","computer","weekend"],"squirrel":false}, + {"events":["potatoes","ice cream","brushed teeth","work"],"squirrel":false}, + {"events":["peanuts","brushed teeth","running","work"],"squirrel":false}, + {"events":["potatoes","exercise","work"],"squirrel":false}, + {"events":["pizza","ice cream","computer","work"],"squirrel":false}, + {"events":["lasagna","ice cream","work"],"squirrel":false}, + {"events":["cauliflower","candy","reading","weekend"],"squirrel":false}, + {"events":["lasagna","nachos","brushed teeth","running","weekend"],"squirrel":false}, + {"events":["potatoes","brushed teeth","work"],"squirrel":false}, + {"events":["carrot","work"],"squirrel":false}, + {"events":["pizza","beer","work","dentist"],"squirrel":false}, + {"events":["lasagna","pudding","cycling","work"],"squirrel":false}, + {"events":["spaghetti","brushed teeth","reading","work"],"squirrel":false}, + {"events":["spaghetti","pudding","television","weekend"],"squirrel":false}, + {"events":["bread","brushed teeth","exercise","weekend"],"squirrel":false}, + {"events":["lasagna","peanuts","work"],"squirrel":true}, + {"events":["pizza","work"],"squirrel":false}, + {"events":["potatoes","exercise","work"],"squirrel":false}, + {"events":["brushed teeth","exercise","work"],"squirrel":false}, + {"events":["spaghetti","brushed teeth","television","work"],"squirrel":false}, + {"events":["pizza","cycling","weekend"],"squirrel":false}, + {"events":["carrot","brushed teeth","weekend"],"squirrel":false}, + {"events":["carrot","beer","brushed teeth","work"],"squirrel":false}, + {"events":["pizza","peanuts","candy","work"],"squirrel":true}, + {"events":["carrot","peanuts","brushed teeth","reading","work"],"squirrel":false}, + {"events":["potatoes","peanuts","brushed teeth","work"],"squirrel":false}, + {"events":["carrot","nachos","brushed teeth","exercise","work"],"squirrel":false}, + {"events":["pizza","peanuts","brushed teeth","television","weekend"],"squirrel":false}, + {"events":["lasagna","brushed teeth","cycling","weekend"],"squirrel":false}, + {"events":["cauliflower","peanuts","brushed teeth","computer","work","touched tree"],"squirrel":false}, + {"events":["lettuce","brushed teeth","television","work"],"squirrel":false}, + {"events":["potatoes","brushed teeth","computer","work"],"squirrel":false}, + {"events":["bread","candy","work"],"squirrel":false}, + {"events":["potatoes","nachos","work"],"squirrel":false}, + {"events":["carrot","pudding","brushed teeth","weekend"],"squirrel":false}, + {"events":["carrot","brushed teeth","exercise","weekend","touched tree"],"squirrel":false}, + {"events":["brussel sprouts","running","work"],"squirrel":false}, + {"events":["brushed teeth","work"],"squirrel":false}, + {"events":["lettuce","brushed teeth","running","work"],"squirrel":false}, + {"events":["candy","brushed teeth","work"],"squirrel":false}, + {"events":["brussel sprouts","brushed teeth","computer","work"],"squirrel":false}, + {"events":["bread","brushed teeth","weekend"],"squirrel":false}, + {"events":["cauliflower","brushed teeth","weekend"],"squirrel":false}, + {"events":["spaghetti","candy","television","work","touched tree"],"squirrel":false}, + {"events":["carrot","pudding","brushed teeth","work"],"squirrel":false}, + {"events":["lettuce","brushed teeth","work"],"squirrel":false}, + {"events":["carrot","ice cream","brushed teeth","cycling","work"],"squirrel":false}, + {"events":["pizza","brushed teeth","work"],"squirrel":false}, + {"events":["spaghetti","peanuts","exercise","weekend"],"squirrel":true}, + {"events":["bread","beer","computer","weekend","touched tree"],"squirrel":false}, + {"events":["brushed teeth","running","work"],"squirrel":false}, + {"events":["lettuce","peanuts","brushed teeth","work","touched tree"],"squirrel":false}, + {"events":["lasagna","brushed teeth","television","work"],"squirrel":false}, + {"events":["cauliflower","brushed teeth","running","work"],"squirrel":false}, + {"events":["carrot","brushed teeth","running","work"],"squirrel":false}, + {"events":["carrot","reading","weekend"],"squirrel":false}, + {"events":["carrot","peanuts","reading","weekend"],"squirrel":true}, + {"events":["potatoes","brushed teeth","running","work"],"squirrel":false}, + {"events":["lasagna","ice cream","work","touched tree"],"squirrel":false}, + {"events":["cauliflower","peanuts","brushed teeth","cycling","work"],"squirrel":false}, + {"events":["pizza","brushed teeth","running","work"],"squirrel":false}, + {"events":["lettuce","brushed teeth","work"],"squirrel":false}, + {"events":["bread","brushed teeth","television","weekend"],"squirrel":false}, + {"events":["cauliflower","peanuts","brushed teeth","weekend"],"squirrel":false} +]; + +// This makes sure the data is exported in node.js — +// `require('./path/to/journal.js')` will get you the array. +if (typeof module != "undefined" && module.exports && (typeof window == "undefined" || window.exports != exports)) + module.exports = JOURNAL; +if (typeof global != "undefined" && !global.JOURNAL) + global.JOURNAL = JOURNAL; diff --git a/docs/code/levels.js b/docs/code/levels.js new file mode 100644 index 000000000..9ff491fab --- /dev/null +++ b/docs/code/levels.js @@ -0,0 +1,178 @@ +var GAME_LEVELS = [` +................................................................................ +................................................................................ +................................................................................ +................................................................................ +................................................................................ +................................................................................ +..................................................................###........... +...................................................##......##....##+##.......... +....................................o.o......##..................#+++#.......... +.................................................................##+##.......... +...................................#####..........................#v#........... +............................................................................##.. +..##......................................o.o................................#.. +..#.....................o....................................................#.. +..#......................................#####.............................o.#.. +..#..........####.......o....................................................#.. +..#..@.......#..#................................................#####.......#.. +..############..###############...####################.....#######...#########.. +..............................#...#..................#.....#.................... +..............................#+++#..................#+++++#.................... +..............................#+++#..................#+++++#.................... +..............................#####..................#######.................... +................................................................................ +................................................................................ +`,` +................................................................................ +................................................................................ +....###############################............................................. +...##.............................##########################################.... +...#.......................................................................##... +...#....o...................................................................#... +...#................................................=.......................#... +...#.o........################...................o..o...........|........o..#... +...#.........................#..............................................#... +...#....o....................##########.....###################....##########... +...#..................................#+++++#.................#....#............ +...###############....oo......=o.o.o..#######.###############.#....#............ +.....#...............o..o.............#.......#......#........#....#............ +.....#....................#############..######.####.#.########....########..... +.....#.............########..............#...........#.#..................#..... +.....#..........####......####...#####################.#..................#..... +.....#........###............###.......................########....########..... +.....#.......##................#########################......#....#............ +.....#.......#................................................#....#............ +.....###......................................................#....#............ +.......#...............o...........................................#............ +.......#...............................................o...........#............ +.......#########......###.....############.........................##........... +.............#..................#........#####....#######.o.........########.... +.............#++++++++++++++++++#............#....#.....#..................#.... +.............#++++++++++++++++++#..........###....###...####.o.............#.... +.............####################..........#........#......#.....|.........#.... +...........................................#++++++++#......####............#.... +...........................................#++++++++#.........#........@...#.... +...........................................#++++++++#.........##############.... +...........................................##########........................... +................................................................................ +`,` +......................................#++#........................#######....................................#+#.. +......................................#++#.....................####.....####.................................#+#.. +......................................#++##########...........##...........##................................#+#.. +......................................##++++++++++##.........##.............##...............................#+#.. +.......................................##########++#.........#....................................o...o...o..#+#.. +................................................##+#.........#.....o...o....................................##+#.. +.................................................#+#.........#................................###############++#.. +.................................................#v#.........#.....#...#........................++++++++++++++##.. +.............................................................##..|...|...|..##............#####################... +..............................................................##+++++++++++##............v........................ +...............................................................####+++++####...................................... +...............................................#.....#............#######........###.........###.................. +...............................................#.....#...........................#.#.........#.#.................. +...............................................#.....#.............................#.........#.................... +...............................................#.....#.............................##........#.................... +...............................................##....#.............................#.........#.................... +...............................................#.....#......o..o.....#...#.........#.........#.................... +...............#######........###...###........#.....#...............#...#.........#.........#.................... +..............##.....##.........#...#..........#.....#.....######....#...#...#########.......#.................... +.............##.......##........#.o.#..........#....##...............#...#...#...............#.................... +.....@.......#.........#........#...#..........#.....#...............#...#...#...............#.................... +....###......#.........#........#...#..........#.....#...............#...#####...######......#.................... +....#.#......#.........#.......##.o.##.........#.....#...............#.....o.....#.#.........#.................... +++++#.#++++++#.........#++++++##.....##++++++++##....#++++++++++.....#.....=.....#.#.........#.................... +++++#.#++++++#.........#+++++##.......##########.....#+++++++##+.....#############.##..o.o..##.................... +++++#.#++++++#.........#+++++#....o.................##++++++##.+....................##.....##..................... +++++#.#++++++#.........#+++++#.....................##++++++##..+.....................#######...................... +++++#.#++++++#.........#+++++##.......##############++++++##...+.................................................. +++++#.#++++++#.........#++++++#########++++++++++++++++++##....+.................................................. +++++#.#++++++#.........#++++++++++++++++++++++++++++++++##.....+.................................................. +`,` +.............................................................................................................. +.............................................................................................................. +.............................................................................................................. +.............................................................................................................. +.............................................................................................................. +........................................o..................................................................... +.............................................................................................................. +........................................#..................................................................... +........................................#..................................................................... +........................................#..................................................................... +........................................#..................................................................... +.......................................###.................................................................... +.......................................#.#.................+++........+++..###................................ +.......................................#.#.................+#+........+#+..................................... +.....................................###.###................#..........#...................................... +......................................#...#.................#...oooo...#.......###............................ +......................................#...#.................#..........#......#+++#........................... +......................................#...#.................############.......###............................ +.....................................##...##......#...#......#................................................ +......................................#...#########...########..............#.#............................... +......................................#...#...........#....................#+++#.............................. +......................................#...#...........#.....................###............................... +.....................................##...##..........#....................................................... +......................................#...#=.=.=.=....#............###........................................ +......................................#...#...........#...........#+++#....................................... +......................................#...#....=.=.=.=#.....o......###.......###.............................. +.....................................##...##..........#.....................#+++#............................. +..............................o...o...#...#...........#.....#................##v........###................... +......................................#...#...........#..............#.................#+++#.................. +.............................###.###.###.###.....o.o..#++++++++++++++#...................v#................... +.............................#.###.#.#.###.#..........#++++++++++++++#........................................ +.............................#.............#...#######################........................................ +.............................##...........##.........................................###...................... +..###.........................#.....#.....#.........................................#+++#................###.. +..#.#.........................#....###....#..........................................###.................#.#.. +..#...........................#....###....#######........................#####.............................#.. +..#...........................#...........#..............................#...#.............................#.. +..#...........................##..........#..............................#.#.#.............................#.. +..#.......................................#.......|####|....|####|.....###.###.............................#.. +..#................###.............o.o....#..............................#.........###.....................#.. +..#...............#####.......##..........#.............................###.......#+++#..........#.........#.. +..#...............o###o.......#....###....#.............................#.#........###..........###........#.. +..#................###........#############..#.oo.#....#.oo.#....#.oo..##.##....................###........#.. +..#......@..........#.........#...........#++#....#++++#....#++++#....##...##....................#.........#.. +..#############################...........#############################.....################################.. +.............................................................................................................. +.............................................................................................................. +`,` +..................................................................................................###.#....... +......................................................................................................#....... +..................................................................................................#####....... +..................................................................................................#........... +..................................................................................................#.###....... +..........................o.......................................................................#.#.#....... +.............................................................................................o.o.o###.#....... +...................###................................................................................#....... +.......+..o..+................................................#####.#####.#####.#####.#####.#####.#####....... +.......#.....#................................................#...#.#...#.#...#.#...#.#...#.#...#.#........... +.......#=.o..#............#...................................###.#.###.#.###.#.###.#.###.#.###.#.#####....... +.......#.....#..................................................#.#...#.#...#.#...#.#...#.#...#.#.....#....... +.......+..o..+............o..................................####.#####.#####.#####.#####.#####.#######....... +.............................................................................................................. +..........o..............###..............................##.................................................. +.............................................................................................................. +.............................................................................................................. +......................................................##...................................................... +...................###.........###............................................................................ +.............................................................................................................. +..........................o.....................................................#......#...................... +..........................................................##.....##........................................... +.............###.........###.........###.................................#..................#................. +.............................................................................................................. +.................................................................||........................................... +..###########................................................................................................. +..#.........#.o.#########.o.#########.o.##................................................#................... +..#.........#...#.......#...#.......#...#.................||..................#.....#......................... +..#..@......#####...o...#####...o...#####..................................................................... +..#######.....................................#####.......##.....##.....###................................... +........#=..................=................=#...#.....................###................................... +........#######################################...#+++++++++++++++++++++###+++++++++++++++++++++++++++++++++++ +..................................................############################################################ +.............................................................................................................. +`]; + +if (typeof module != "undefined" && module.exports && (typeof window == "undefined" || window.exports != exports)) + module.exports = GAME_LEVELS; +if (typeof global != "undefined" && !global.GAME_LEVELS) + global.GAME_LEVELS = GAME_LEVELS; diff --git a/docs/code/load.js b/docs/code/load.js new file mode 100644 index 000000000..bf44c1080 --- /dev/null +++ b/docs/code/load.js @@ -0,0 +1,9 @@ +// Since the code for most chapter in Eloquent JavaScript isn't +// written with node's module system in mind, this kludge is used to +// load dependency files into the global namespace, so that the +// examples can run on node. + +module.exports = function(...args) { + for (let arg of args) + (1,eval)(require("fs").readFileSync(__dirname + "/../" + arg, "utf8")) +} diff --git a/docs/code/packages_chapter_10.js b/docs/code/packages_chapter_10.js new file mode 100644 index 000000000..5d11cd7b0 --- /dev/null +++ b/docs/code/packages_chapter_10.js @@ -0,0 +1,507 @@ +/* ordinal 1.0.2: https://github.com/dcousens/ordinal/ + +Copyright (c) 2016, Daniel Cousens + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +require.preload("ordinal", String.raw` +function indicator (i) { + var cent = i % 100 + if (cent >= 10 && cent <= 20) return 'th' + var dec = i % 10 + if (dec === 1) return 'st' + if (dec === 2) return 'nd' + if (dec === 3) return 'rd' + return 'th' +} + +function ordinal (i) { + if (typeof i !== 'number') throw new TypeError('Expected Number, got ' + (typeof i) + ' ' + i) + return i + indicator(i) +} + +ordinal.indicator = indicator +module.exports = ordinal`) + +/* date-names 0.1.11: https://github.com/martinandert/date-names + +The MIT License (MIT) + +Copyright (c) 2014 Martin Andert + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +require.preload("date-names", String.raw` +module.exports = { + __locale: "en", + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + abbreviated_days: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + abbreviated_months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + am: 'AM', + pm: 'PM' +};`) + +/* ini 1.3.5: https://github.com/npm/ini + +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +require.preload("ini", String.raw`exports.parse = exports.decode = decode + +exports.stringify = exports.encode = encode + +exports.safe = safe +exports.unsafe = unsafe + +var eol = typeof process !== 'undefined' && + process.platform === 'win32' ? '\r\n' : '\n' + +function encode (obj, opt) { + var children = [] + var out = '' + + if (typeof opt === 'string') { + opt = { + section: opt, + whitespace: false + } + } else { + opt = opt || {} + opt.whitespace = opt.whitespace === true + } + + var separator = opt.whitespace ? ' = ' : '=' + + Object.keys(obj).forEach(function (k, _, __) { + var val = obj[k] + if (val && Array.isArray(val)) { + val.forEach(function (item) { + out += safe(k + '[]') + separator + safe(item) + '\n' + }) + } else if (val && typeof val === 'object') { + children.push(k) + } else { + out += safe(k) + separator + safe(val) + eol + } + }) + + if (opt.section && out.length) { + out = '[' + safe(opt.section) + ']' + eol + out + } + + children.forEach(function (k, _, __) { + var nk = dotSplit(k).join('\\.') + var section = (opt.section ? opt.section + '.' : '') + nk + var child = encode(obj[k], { + section: section, + whitespace: opt.whitespace + }) + if (out.length && child.length) { + out += eol + } + out += child + }) + + return out +} + +function dotSplit (str) { + return str.replace(/\1/g, '\u0002LITERAL\\1LITERAL\u0002') + .replace(/\\\./g, '\u0001') + .split(/\./).map(function (part) { + return part.replace(/\1/g, '\\.') + .replace(/\2LITERAL\\1LITERAL\2/g, '\u0001') + }) +} + +function decode (str) { + var out = {} + var p = out + var section = null + // section |key = value + var re = /^\[([^\]]*)\]$|^([^=]+)(=(.*))?$/i + var lines = str.split(/[\r\n]+/g) + + lines.forEach(function (line, _, __) { + if (!line || line.match(/^\s*[;#]/)) return + var match = line.match(re) + if (!match) return + if (match[1] !== undefined) { + section = unsafe(match[1]) + p = out[section] = out[section] || {} + return + } + var key = unsafe(match[2]) + var value = match[3] ? unsafe(match[4]) : true + switch (value) { + case 'true': + case 'false': + case 'null': value = JSON.parse(value) + } + + // Convert keys with '[]' suffix to an array + if (key.length > 2 && key.slice(-2) === '[]') { + key = key.substring(0, key.length - 2) + if (!p[key]) { + p[key] = [] + } else if (!Array.isArray(p[key])) { + p[key] = [p[key]] + } + } + + // safeguard against resetting a previously defined + // array by accidentally forgetting the brackets + if (Array.isArray(p[key])) { + p[key].push(value) + } else { + p[key] = value + } + }) + + // {a:{y:1},"a.b":{x:2}} --> {a:{y:1,b:{x:2}}} + // use a filter to return the keys that have to be deleted. + Object.keys(out).filter(function (k, _, __) { + if (!out[k] || + typeof out[k] !== 'object' || + Array.isArray(out[k])) { + return false + } + // see if the parent section is also an object. + // if so, add it to that, and mark this one for deletion + var parts = dotSplit(k) + var p = out + var l = parts.pop() + var nl = l.replace(/\\\./g, '.') + parts.forEach(function (part, _, __) { + if (!p[part] || typeof p[part] !== 'object') p[part] = {} + p = p[part] + }) + if (p === out && nl === l) { + return false + } + p[nl] = out[k] + return true + }).forEach(function (del, _, __) { + delete out[del] + }) + + return out +} + +function isQuoted (val) { + return (val.charAt(0) === '"' && val.slice(-1) === '"') || + (val.charAt(0) === "'" && val.slice(-1) === "'") +} + +function safe (val) { + return (typeof val !== 'string' || + val.match(/[=\r\n]/) || + val.match(/^\[/) || + (val.length > 1 && + isQuoted(val)) || + val !== val.trim()) + ? JSON.stringify(val) + : val.replace(/;/g, '\\;').replace(/#/g, '\\#') +} + +function unsafe (val, doUnesc) { + val = (val || '').trim() + if (isQuoted(val)) { + // remove the single quotes before calling JSON.parse + if (val.charAt(0) === "'") { + val = val.substr(1, val.length - 2) + } + try { val = JSON.parse(val) } catch (_) {} + } else { + // walk the val to find the first not-escaped ; character + var esc = false + var unesc = '' + for (var i = 0, l = val.length; i < l; i++) { + var c = val.charAt(i) + if (esc) { + if ('\\;#'.indexOf(c) !== -1) { + unesc += c + } else { + unesc += '\\' + c + } + esc = false + } else if (';#'.indexOf(c) !== -1) { + break + } else if (c === '\\') { + esc = true + } else { + unesc += c + } + } + if (esc) { + unesc += '\\' + } + return unesc.trim() + } + return val +}`) + +/* dijkstrajs 1.0.1: https://github.com/tcort/dijkstrajs/ + +Dijkstra path-finding functions. Adapted from the Dijkstar Python project. + +Copyright (C) 2008 + Wyatt Baldwin + All rights reserved + +Licensed under the MIT license. + + http://www.opensource.org/licenses/mit-license.php + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +require.preload("dijkstrajs", String.raw`'use strict'; + +var dijkstra = { + single_source_shortest_paths: function(graph, s, d) { + // Predecessor map for each node that has been encountered. + // node ID => predecessor node ID + var predecessors = {}; + + // Costs of shortest paths from s to all nodes encountered. + // node ID => cost + var costs = {}; + costs[s] = 0; + + // Costs of shortest paths from s to all nodes encountered; differs from + // costs in that it provides easy access to the node that currently has + // the known shortest path from s. + // XXX: Do we actually need both costs and open? + var open = dijkstra.PriorityQueue.make(); + open.push(s, 0); + + var closest, + u, v, + cost_of_s_to_u, + adjacent_nodes, + cost_of_e, + cost_of_s_to_u_plus_cost_of_e, + cost_of_s_to_v, + first_visit; + while (!open.empty()) { + // In the nodes remaining in graph that have a known cost from s, + // find the node, u, that currently has the shortest path from s. + closest = open.pop(); + u = closest.value; + cost_of_s_to_u = closest.cost; + + // Get nodes adjacent to u... + adjacent_nodes = graph[u] || {}; + + // ...and explore the edges that connect u to those nodes, updating + // the cost of the shortest paths to any or all of those nodes as + // necessary. v is the node across the current edge from u. + for (v in adjacent_nodes) { + if (adjacent_nodes.hasOwnProperty(v)) { + // Get the cost of the edge running from u to v. + cost_of_e = adjacent_nodes[v]; + + // Cost of s to u plus the cost of u to v across e--this is *a* + // cost from s to v that may or may not be less than the current + // known cost to v. + cost_of_s_to_u_plus_cost_of_e = cost_of_s_to_u + cost_of_e; + + // If we haven't visited v yet OR if the current known cost from s to + // v is greater than the new cost we just found (cost of s to u plus + // cost of u to v across e), update v's cost in the cost list and + // update v's predecessor in the predecessor list (it's now u). + cost_of_s_to_v = costs[v]; + first_visit = (typeof costs[v] === 'undefined'); + if (first_visit || cost_of_s_to_v > cost_of_s_to_u_plus_cost_of_e) { + costs[v] = cost_of_s_to_u_plus_cost_of_e; + open.push(v, cost_of_s_to_u_plus_cost_of_e); + predecessors[v] = u; + } + } + } + } + + if (typeof d !== 'undefined' && typeof costs[d] === 'undefined') { + var msg = ['Could not find a path from ', s, ' to ', d, '.'].join(''); + throw new Error(msg); + } + + return predecessors; + }, + + extract_shortest_path_from_predecessor_list: function(predecessors, d) { + var nodes = []; + var u = d; + var predecessor; + while (u) { + nodes.push(u); + predecessor = predecessors[u]; + u = predecessors[u]; + } + nodes.reverse(); + return nodes; + }, + + find_path: function(graph, s, d) { + var predecessors = dijkstra.single_source_shortest_paths(graph, s, d); + return dijkstra.extract_shortest_path_from_predecessor_list( + predecessors, d); + }, + + /** + * A very naive priority queue implementation. + */ + PriorityQueue: { + make: function (opts) { + var T = dijkstra.PriorityQueue, + t = {}, + key; + opts = opts || {}; + for (key in T) { + if (T.hasOwnProperty(key)) { + t[key] = T[key]; + } + } + t.queue = []; + t.sorter = opts.sorter || T.default_sorter; + return t; + }, + + default_sorter: function (a, b) { + return a.cost - b.cost; + }, + + /** + * Add a new item to the queue and ensure the highest priority element + * is at the front of the queue. + */ + push: function (value, cost) { + var item = {value: value, cost: cost}; + this.queue.push(item); + this.queue.sort(this.sorter); + }, + + /** + * Return the highest priority element in the queue. + */ + pop: function () { + return this.queue.shift(); + }, + + empty: function () { + return this.queue.length === 0; + } + } +}; + + +// node.js module exports +if (typeof module !== 'undefined') { + module.exports = dijkstra; +}`) + +/* random-item 1.0.0: https://github.com/sindresorhus/random-item + +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +require.preload("random-item", String.raw`'use strict'; +module.exports = function (arr) { + if (!Array.isArray(arr)) { + throw new TypeError('Expected an array'); + } + + return arr[Math.floor(Math.random() * arr.length)]; +};`) + +require.preload("./format-date", String.raw`const ordinal = require("ordinal"); +const {days, months} = require("date-names"); + +exports.formatDate = function(date, format) { + return format.replace(/YYYY|M(MMM)?|Do?|dddd/g, tag => { + if (tag == "YYYY") return date.getFullYear(); + if (tag == "M") return date.getMonth(); + if (tag == "MMMM") return months[date.getMonth()]; + if (tag == "D") return date.getDate(); + if (tag == "Do") return ordinal(date.getDate()); + if (tag == "dddd") return days[date.getDay()]; + }); +};`) + +require.preload("./graph", String.raw`exports.buildGraph = function(edges) { + let graph = Object.create(null); + function addEdge(from, to) { + if (!(from in graph)) graph[from] = Object.create(null); + graph[from][to] = 1; + } + for (let [from, to] of edges) { + addEdge(from, to); + addEdge(to, from); + } + return graph; +};`) + diff --git a/docs/code/scripts.js b/docs/code/scripts.js new file mode 100644 index 000000000..a58105b27 --- /dev/null +++ b/docs/code/scripts.js @@ -0,0 +1,1123 @@ +// Generated from the Unicode 10 database and https://en.wikipedia.org/wiki/Script_(Unicode) + +var SCRIPTS = [ + { + name: "Adlam", + ranges: [[125184, 125259], [125264, 125274], [125278, 125280]], + direction: "rtl", + year: 1987, + living: true, + link: "https://en.wikipedia.org/wiki/Fula_alphabets#Adlam_alphabet" + }, + { + name: "Caucasian Albanian", + ranges: [[66864, 66916], [66927, 66928]], + direction: "ltr", + year: 420, + living: false, + link: "https://en.wikipedia.org/wiki/Caucasian_Albanian_alphabet" + }, + { + name: "Ahom", + ranges: [[71424, 71450], [71453, 71468], [71472, 71488]], + direction: "ltr", + year: 1250, + living: false, + link: "https://en.wikipedia.org/wiki/Ahom_alphabet" + }, + { + name: "Arabic", + ranges: [[1536, 1541], [1542, 1548], [1549, 1563], [1564, 1565], [1566, 1567], [1568, 1600], [1601, 1611], [1622, 1648], [1649, 1757], [1758, 1792], [1872, 1920], [2208, 2229], [2230, 2238], [2260, 2274], [2275, 2304], [64336, 64450], [64467, 64830], [64848, 64912], [64914, 64968], [65008, 65022], [65136, 65141], [65142, 65277], [69216, 69247], [126464, 126468], [126469, 126496], [126497, 126499], [126500, 126501], [126503, 126504], [126505, 126515], [126516, 126520], [126521, 126522], [126523, 126524], [126530, 126531], [126535, 126536], [126537, 126538], [126539, 126540], [126541, 126544], [126545, 126547], [126548, 126549], [126551, 126552], [126553, 126554], [126555, 126556], [126557, 126558], [126559, 126560], [126561, 126563], [126564, 126565], [126567, 126571], [126572, 126579], [126580, 126584], [126585, 126589], [126590, 126591], [126592, 126602], [126603, 126620], [126625, 126628], [126629, 126634], [126635, 126652], [126704, 126706]], + direction: "rtl", + year: 400, + living: true, + link: "https://en.wikipedia.org/wiki/Arabic_script" + }, + { + name: "Imperial Aramaic", + ranges: [[67648, 67670], [67671, 67680]], + direction: "rtl", + year: 800, + living: false, + link: "https://en.wikipedia.org/wiki/Aramaic_alphabet" + }, + { + name: "Armenian", + ranges: [[1329, 1367], [1369, 1376], [1377, 1416], [1418, 1419], [1421, 1424], [64275, 64280]], + direction: "ltr", + year: 405, + living: true, + link: "https://en.wikipedia.org/wiki/Armenian_alphabet" + }, + { + name: "Avestan", + ranges: [[68352, 68406], [68409, 68416]], + direction: "rtl", + year: 400, + living: false, + link: "https://en.wikipedia.org/wiki/Avestan_alphabet" + }, + { + name: "Balinese", + ranges: [[6912, 6988], [6992, 7037]], + direction: "ltr", + year: 1000, + living: true, + link: "https://en.wikipedia.org/wiki/Balinese_script" + }, + { + name: "Bamum", + ranges: [[42656, 42744], [92160, 92729]], + direction: "ltr", + year: 1896, + living: true, + link: "https://en.wikipedia.org/wiki/Bamum_script" + }, + { + name: "Bassa Vah", + ranges: [[92880, 92910], [92912, 92918]], + direction: "ltr", + year: 1950, + living: false, + link: "https://en.wikipedia.org/wiki/Bassa_alphabet" + }, + { + name: "Batak", + ranges: [[7104, 7156], [7164, 7168]], + direction: "ltr", + year: 1300, + living: true, + link: "https://en.wikipedia.org/wiki/Batak_alphabet" + }, + { + name: "Bengali", + ranges: [[2432, 2436], [2437, 2445], [2447, 2449], [2451, 2473], [2474, 2481], [2482, 2483], [2486, 2490], [2492, 2501], [2503, 2505], [2507, 2511], [2519, 2520], [2524, 2526], [2527, 2532], [2534, 2558]], + direction: "ltr", + year: 1050, + living: true, + link: "https://en.wikipedia.org/wiki/Bengali_alphabet" + }, + { + name: "Bhaiksuki", + ranges: [[72704, 72713], [72714, 72759], [72760, 72774], [72784, 72813]], + direction: "ltr", + year: 1050, + living: false, + link: "https://en.wikipedia.org/wiki/Bhaiksuki_alphabet" + }, + { + name: "Bopomofo", + ranges: [[746, 748], [12549, 12591], [12704, 12731]], + direction: "ltr", + year: 1918, + living: true, + link: "https://en.wikipedia.org/wiki/Bopomofo" + }, + { + name: "Brahmi", + ranges: [[69632, 69710], [69714, 69744], [69759, 69760]], + direction: "ltr", + year: -250, + living: false, + link: "https://en.wikipedia.org/wiki/Brahmi_script" + }, + { + name: "Braille", + ranges: [[10240, 10496]], + direction: "ltr", + year: 1824, + living: true, + link: "https://en.wikipedia.org/wiki/Braille" + }, + { + name: "Buginese", + ranges: [[6656, 6684], [6686, 6688]], + direction: "ltr", + year: 1650, + living: true, + link: "https://en.wikipedia.org/wiki/Lontara_script" + }, + { + name: "Buhid", + ranges: [[5952, 5972]], + direction: "ltr", + year: 1300, + living: true, + link: "https://en.wikipedia.org/wiki/Buhid_alphabet" + }, + { + name: "Chakma", + ranges: [[69888, 69941], [69942, 69956]], + direction: "ltr", + year: 1050, + living: true, + link: "https://en.wikipedia.org/wiki/Chakma_alphabet" + }, + { + name: "Canadian Aboriginal", + ranges: [[5120, 5760], [6320, 6390]], + direction: "ltr", + year: 1840, + living: true, + link: "https://en.wikipedia.org/wiki/Canadian_Aboriginal_syllabics" + }, + { + name: "Carian", + ranges: [[66208, 66257]], + direction: "ltr", + year: -650, + living: false, + link: "https://en.wikipedia.org/wiki/Carian_alphabets" + }, + { + name: "Cham", + ranges: [[43520, 43575], [43584, 43598], [43600, 43610], [43612, 43616]], + direction: "ltr", + year: 750, + living: true, + link: "https://en.wikipedia.org/wiki/Cham_alphabet" + }, + { + name: "Cherokee", + ranges: [[5024, 5110], [5112, 5118], [43888, 43968]], + direction: "ltr", + year: 1820, + living: true, + link: "https://en.wikipedia.org/wiki/Cherokee_syllabary" + }, + { + name: "Coptic", + ranges: [[994, 1008], [11392, 11508], [11513, 11520]], + direction: "ltr", + year: -200, + living: false, + link: "https://en.wikipedia.org/wiki/Coptic_alphabet" + }, + { + name: "Cypriot", + ranges: [[67584, 67590], [67592, 67593], [67594, 67638], [67639, 67641], [67644, 67645], [67647, 67648]], + direction: "rtl", + year: -1100, + living: false, + link: "https://en.wikipedia.org/wiki/Cypriot_syllabary" + }, + { + name: "Cyrillic", + ranges: [[1024, 1157], [1159, 1328], [7296, 7305], [7467, 7468], [7544, 7545], [11744, 11776], [42560, 42656], [65070, 65072]], + direction: "ltr", + year: 950, + living: true, + link: "https://en.wikipedia.org/wiki/Cyrillic_script" + }, + { + name: "Devanagari", + ranges: [[2304, 2385], [2387, 2404], [2406, 2432], [43232, 43262]], + direction: "ltr", + year: 100, + living: true, + link: "https://en.wikipedia.org/wiki/Devanagari" + }, + { + name: "Deseret", + ranges: [[66560, 66640]], + direction: "ltr", + year: 1854, + living: true, + link: "https://en.wikipedia.org/wiki/Deseret_alphabet" + }, + { + name: "Duployan", + ranges: [[113664, 113771], [113776, 113789], [113792, 113801], [113808, 113818], [113820, 113824]], + direction: "ltr", + year: 1860, + living: true, + link: "https://en.wikipedia.org/wiki/Duployan_shorthand" + }, + { + name: "Egyptian Hieroglyphs", + ranges: [[77824, 78895]], + direction: "ltr", + year: -3200, + living: false, + link: "https://en.wikipedia.org/wiki/Egyptian_hieroglyphs" + }, + { + name: "Elbasan", + ranges: [[66816, 66856]], + direction: "ltr", + year: 1750, + living: false, + link: "https://en.wikipedia.org/wiki/Elbasan_alphabet" + }, + { + name: "Ethiopic", + ranges: [[4608, 4681], [4682, 4686], [4688, 4695], [4696, 4697], [4698, 4702], [4704, 4745], [4746, 4750], [4752, 4785], [4786, 4790], [4792, 4799], [4800, 4801], [4802, 4806], [4808, 4823], [4824, 4881], [4882, 4886], [4888, 4955], [4957, 4989], [4992, 5018], [11648, 11671], [11680, 11687], [11688, 11695], [11696, 11703], [11704, 11711], [11712, 11719], [11720, 11727], [11728, 11735], [11736, 11743], [43777, 43783], [43785, 43791], [43793, 43799], [43808, 43815], [43816, 43823]], + direction: "ltr", + year: -900, + living: true, + link: "https://en.wikipedia.org/wiki/Ge%27ez_script" + }, + { + name: "Georgian", + ranges: [[4256, 4294], [4295, 4296], [4301, 4302], [4304, 4347], [4348, 4352], [11520, 11558], [11559, 11560], [11565, 11566]], + direction: "ltr", + year: 430, + living: true, + link: "https://en.wikipedia.org/wiki/Georgian_scripts" + }, + { + name: "Glagolitic", + ranges: [[11264, 11311], [11312, 11359], [122880, 122887], [122888, 122905], [122907, 122914], [122915, 122917], [122918, 122923]], + direction: "ltr", + year: 862, + living: false, + link: "https://en.wikipedia.org/wiki/Glagolitic_script" + }, + { + name: "Masaram Gondi", + ranges: [[72960, 72967], [72968, 72970], [72971, 73015], [73018, 73019], [73020, 73022], [73023, 73032], [73040, 73050]], + direction: "ltr", + year: 1918, + living: true, + link: "https://en.wikipedia.org/wiki/Gondi_writing#Masaram" + }, + { + name: "Gothic", + ranges: [[66352, 66379]], + direction: "ltr", + year: 350, + living: false, + link: "https://en.wikipedia.org/wiki/Gothic_alphabet" + }, + { + name: "Grantha", + ranges: [[70400, 70404], [70405, 70413], [70415, 70417], [70419, 70441], [70442, 70449], [70450, 70452], [70453, 70458], [70460, 70469], [70471, 70473], [70475, 70478], [70480, 70481], [70487, 70488], [70493, 70500], [70502, 70509], [70512, 70517]], + direction: "ltr", + year: 550, + living: false, + link: "https://en.wikipedia.org/wiki/Grantha_alphabet" + }, + { + name: "Greek", + ranges: [[880, 884], [885, 888], [890, 894], [895, 896], [900, 901], [902, 903], [904, 907], [908, 909], [910, 930], [931, 994], [1008, 1024], [7462, 7467], [7517, 7522], [7526, 7531], [7615, 7616], [7936, 7958], [7960, 7966], [7968, 8006], [8008, 8014], [8016, 8024], [8025, 8026], [8027, 8028], [8029, 8030], [8031, 8062], [8064, 8117], [8118, 8133], [8134, 8148], [8150, 8156], [8157, 8176], [8178, 8181], [8182, 8191], [8486, 8487], [43877, 43878], [65856, 65935], [65952, 65953], [119296, 119366]], + direction: "ltr", + year: -800, + living: true, + link: "https://en.wikipedia.org/wiki/Greek_alphabet" + }, + { + name: "Gujarati", + ranges: [[2689, 2692], [2693, 2702], [2703, 2706], [2707, 2729], [2730, 2737], [2738, 2740], [2741, 2746], [2748, 2758], [2759, 2762], [2763, 2766], [2768, 2769], [2784, 2788], [2790, 2802], [2809, 2816]], + direction: "ltr", + year: 1592, + living: true, + link: "https://en.wikipedia.org/wiki/Gujarati_alphabet" + }, + { + name: "Gurmukhi", + ranges: [[2561, 2564], [2565, 2571], [2575, 2577], [2579, 2601], [2602, 2609], [2610, 2612], [2613, 2615], [2616, 2618], [2620, 2621], [2622, 2627], [2631, 2633], [2635, 2638], [2641, 2642], [2649, 2653], [2654, 2655], [2662, 2678]], + direction: "ltr", + year: 1550, + living: true, + link: "https://en.wikipedia.org/wiki/Gurmukh%C4%AB_alphabet" + }, + { + name: "Hangul", + ranges: [[4352, 4608], [12334, 12336], [12593, 12687], [12800, 12831], [12896, 12927], [43360, 43389], [44032, 55204], [55216, 55239], [55243, 55292], [65440, 65471], [65474, 65480], [65482, 65488], [65490, 65496], [65498, 65501]], + direction: "ltr", + year: 1443, + living: true, + link: "https://en.wikipedia.org/wiki/Hangul" + }, + { + name: "Han", + ranges: [[11904, 11930], [11931, 12020], [12032, 12246], [12293, 12294], [12295, 12296], [12321, 12330], [12344, 12348], [13312, 19894], [19968, 40939], [63744, 64110], [64112, 64218], [131072, 173783], [173824, 177973], [177984, 178206], [178208, 183970], [183984, 191457], [194560, 195102]], + direction: "ltr", + year: -1100, + living: true, + link: "https://en.wikipedia.org/wiki/Chinese_characters" + }, + { + name: "Hanunoo", + ranges: [[5920, 5941]], + direction: "ltr", + year: 1300, + living: true, + link: "https://en.wikipedia.org/wiki/Hanun%C3%B3%27o_alphabet" + }, + { + name: "Hatran", + ranges: [[67808, 67827], [67828, 67830], [67835, 67840]], + direction: "rtl", + year: -40, + living: false, + link: "https://en.wikipedia.org/wiki/Hatran_alphabet" + }, + { + name: "Hebrew", + ranges: [[1425, 1480], [1488, 1515], [1520, 1525], [64285, 64311], [64312, 64317], [64318, 64319], [64320, 64322], [64323, 64325], [64326, 64336]], + direction: "rtl", + year: -100, + living: true, + link: "https://en.wikipedia.org/wiki/Hebrew_alphabet" + }, + { + name: "Hiragana", + ranges: [[12353, 12439], [12445, 12448], [110593, 110879], [127488, 127489]], + direction: "ltr", + year: 800, + living: true, + link: "https://en.wikipedia.org/wiki/Hiragana" + }, + { + name: "Anatolian Hieroglyphs", + ranges: [[82944, 83527]], + direction: "ltr", + year: -1400, + living: false, + link: "https://en.wikipedia.org/wiki/Anatolian_hieroglyphs" + }, + { + name: "Pahawh Hmong", + ranges: [[92928, 92998], [93008, 93018], [93019, 93026], [93027, 93048], [93053, 93072]], + direction: "ltr", + year: 1959, + living: true, + link: "https://en.wikipedia.org/wiki/Pahawh_Hmong" + }, + { + name: "Old Hungarian", + ranges: [[68736, 68787], [68800, 68851], [68858, 68864]], + direction: "rtl", + year: 1150, + living: false, + link: "https://en.wikipedia.org/wiki/Old_Hungarian_alphabet" + }, + { + name: "Old Italic", + ranges: [[66304, 66340], [66349, 66352]], + direction: "ltr", + year: -750, + living: false, + link: "https://en.wikipedia.org/wiki/Old_Italic_script" + }, + { + name: "Javanese", + ranges: [[43392, 43470], [43472, 43482], [43486, 43488]], + direction: "ltr", + year: 1250, + living: true, + link: "https://en.wikipedia.org/wiki/Javanese_script" + }, + { + name: "Kayah Li", + ranges: [[43264, 43310], [43311, 43312]], + direction: "ltr", + year: 1962, + living: true, + link: "https://en.wikipedia.org/wiki/Kayah_Li_alphabet" + }, + { + name: "Katakana", + ranges: [[12449, 12539], [12541, 12544], [12784, 12800], [13008, 13055], [13056, 13144], [65382, 65392], [65393, 65438], [110592, 110593]], + direction: "ltr", + year: 800, + living: true, + link: "https://en.wikipedia.org/wiki/Katakana" + }, + { + name: "Kharoshthi", + ranges: [[68096, 68100], [68101, 68103], [68108, 68116], [68117, 68120], [68121, 68148], [68152, 68155], [68159, 68168], [68176, 68185]], + direction: "rtl", + year: -400, + living: false, + link: "https://en.wikipedia.org/wiki/Kharosthi" + }, + { + name: "Khmer", + ranges: [[6016, 6110], [6112, 6122], [6128, 6138], [6624, 6656]], + direction: "ltr", + year: 611, + living: true, + link: "https://en.wikipedia.org/wiki/Khmer_alphabet" + }, + { + name: "Khojki", + ranges: [[70144, 70162], [70163, 70207]], + direction: "ltr", + year: 1520, + living: false, + link: "https://en.wikipedia.org/wiki/Khojki_script" + }, + { + name: "Kannada", + ranges: [[3200, 3204], [3205, 3213], [3214, 3217], [3218, 3241], [3242, 3252], [3253, 3258], [3260, 3269], [3270, 3273], [3274, 3278], [3285, 3287], [3294, 3295], [3296, 3300], [3302, 3312], [3313, 3315]], + direction: "ltr", + year: 450, + living: true, + link: "https://en.wikipedia.org/wiki/Kannada_alphabet" + }, + { + name: "Kaithi", + ranges: [[69760, 69826]], + direction: "ltr", + year: 1550, + living: false, + link: "https://en.wikipedia.org/wiki/Kaithi" + }, + { + name: "Tai Tham", + ranges: [[6688, 6751], [6752, 6781], [6783, 6794], [6800, 6810], [6816, 6830]], + direction: "ltr", + year: 1300, + living: true, + link: "https://en.wikipedia.org/wiki/Tai_Tham_alphabet" + }, + { + name: "Lao", + ranges: [[3713, 3715], [3716, 3717], [3719, 3721], [3722, 3723], [3725, 3726], [3732, 3736], [3737, 3744], [3745, 3748], [3749, 3750], [3751, 3752], [3754, 3756], [3757, 3770], [3771, 3774], [3776, 3781], [3782, 3783], [3784, 3790], [3792, 3802], [3804, 3808]], + direction: "ltr", + year: 1350, + living: true, + link: "https://en.wikipedia.org/wiki/Lao_alphabet" + }, + { + name: "Latin", + ranges: [[65, 91], [97, 123], [170, 171], [186, 187], [192, 215], [216, 247], [248, 697], [736, 741], [7424, 7462], [7468, 7517], [7522, 7526], [7531, 7544], [7545, 7615], [7680, 7936], [8305, 8306], [8319, 8320], [8336, 8349], [8490, 8492], [8498, 8499], [8526, 8527], [8544, 8585], [11360, 11392], [42786, 42888], [42891, 42927], [42928, 42936], [42999, 43008], [43824, 43867], [43868, 43877], [64256, 64263], [65313, 65339], [65345, 65371]], + direction: "ltr", + year: -700, + living: true, + link: "https://en.wikipedia.org/wiki/Latin_script" + }, + { + name: "Lepcha", + ranges: [[7168, 7224], [7227, 7242], [7245, 7248]], + direction: "ltr", + year: 1700, + living: true, + link: "https://en.wikipedia.org/wiki/Lepcha_alphabet" + }, + { + name: "Limbu", + ranges: [[6400, 6431], [6432, 6444], [6448, 6460], [6464, 6465], [6468, 6480]], + direction: "ltr", + year: 1740, + living: true, + link: "https://en.wikipedia.org/wiki/Limbu_alphabet" + }, + { + name: "Linear A", + ranges: [[67072, 67383], [67392, 67414], [67424, 67432]], + direction: "ltr", + year: -2500, + living: false, + link: "https://en.wikipedia.org/wiki/Linear_A" + }, + { + name: "Linear B", + ranges: [[65536, 65548], [65549, 65575], [65576, 65595], [65596, 65598], [65599, 65614], [65616, 65630], [65664, 65787]], + direction: "ltr", + year: -1450, + living: false, + link: "https://en.wikipedia.org/wiki/Linear_B" + }, + { + name: "Lisu", + ranges: [[42192, 42240]], + direction: "ltr", + year: 1915, + living: true, + link: "https://en.wikipedia.org/wiki/Fraser_alphabet" + }, + { + name: "Lycian", + ranges: [[66176, 66205]], + direction: "ltr", + year: -500, + living: false, + link: "https://en.wikipedia.org/wiki/Lycian_alphabet" + }, + { + name: "Lydian", + ranges: [[67872, 67898], [67903, 67904]], + direction: "rtl", + year: -700, + living: false, + link: "https://en.wikipedia.org/wiki/Lydian_alphabet" + }, + { + name: "Mahajani", + ranges: [[69968, 70007]], + direction: "ltr", + year: 1150, + living: false, + link: "https://en.wikipedia.org/wiki/Mahajani" + }, + { + name: "Mandaic", + ranges: [[2112, 2140], [2142, 2143]], + direction: "rtl", + year: 200, + living: true, + link: "https://en.wikipedia.org/wiki/Mandaic_alphabet" + }, + { + name: "Manichaean", + ranges: [[68288, 68327], [68331, 68343]], + direction: "rtl", + year: 250, + living: false, + link: "https://en.wikipedia.org/wiki/Manichaean_alphabet" + }, + { + name: "Marchen", + ranges: [[72816, 72848], [72850, 72872], [72873, 72887]], + direction: "ltr", + year: 650, + living: false, + link: "https://en.wikipedia.org/wiki/Zhang-Zhung_language#Scripts" + }, + { + name: "Mende Kikakui", + ranges: [[124928, 125125], [125127, 125143]], + direction: "rtl", + year: 1880, + living: true, + link: "https://en.wikipedia.org/wiki/Mende_Kikakui_script" + }, + { + name: "Meroitic Cursive", + ranges: [[68000, 68024], [68028, 68048], [68050, 68096]], + direction: "rtl", + year: -300, + living: false, + link: "https://en.wikipedia.org/wiki/Meroitic_alphabet" + }, + { + name: "Meroitic Hieroglyphs", + ranges: [[67968, 68000]], + direction: "rtl", + year: -300, + living: false, + link: "https://en.wikipedia.org/wiki/Meroitic_alphabet" + }, + { + name: "Malayalam", + ranges: [[3328, 3332], [3333, 3341], [3342, 3345], [3346, 3397], [3398, 3401], [3402, 3408], [3412, 3428], [3430, 3456]], + direction: "ltr", + year: 830, + living: true, + link: "https://en.wikipedia.org/wiki/Malayalam_script" + }, + { + name: "Modi", + ranges: [[71168, 71237], [71248, 71258]], + direction: "ltr", + year: 1200, + living: false, + link: "https://en.wikipedia.org/wiki/Modi_alphabet" + }, + { + name: "Mongolian", + ranges: [[6144, 6146], [6148, 6149], [6150, 6159], [6160, 6170], [6176, 6264], [6272, 6315], [71264, 71277]], + direction: "ttb", + year: 1204, + living: false, + link: "https://en.wikipedia.org/wiki/Mongolian_script" + }, + { + name: "Mro", + ranges: [[92736, 92767], [92768, 92778], [92782, 92784]], + direction: "ltr", + year: 1985, + living: true, + link: "https://en.wikipedia.org/wiki/Mru_language#Alphabet" + }, + { + name: "Meetei Mayek", + ranges: [[43744, 43767], [43968, 44014], [44016, 44026]], + direction: "ltr", + year: 200, + living: true, + link: "https://en.wikipedia.org/wiki/Meitei_script" + }, + { + name: "Multani", + ranges: [[70272, 70279], [70280, 70281], [70282, 70286], [70287, 70302], [70303, 70314]], + direction: "ltr", + year: 1750, + living: false, + link: "https://en.wikipedia.org/wiki/Multani_alphabet" + }, + { + name: "Myanmar", + ranges: [[4096, 4256], [43488, 43519], [43616, 43648]], + direction: "ltr", + year: 984, + living: true, + link: "https://en.wikipedia.org/wiki/Burmese_alphabet" + }, + { + name: "Old North Arabian", + ranges: [[68224, 68256]], + direction: "rtl", + year: 750, + living: false, + link: "https://en.wikipedia.org/wiki/Ancient_North_Arabian" + }, + { + name: "Nabataean", + ranges: [[67712, 67743], [67751, 67760]], + direction: "rtl", + year: 150, + living: false, + link: "https://en.wikipedia.org/wiki/Nabataean_alphabet" + }, + { + name: "Newa", + ranges: [[70656, 70746], [70747, 70748], [70749, 70750]], + direction: "ltr", + year: 1000, + living: true, + link: "https://en.wikipedia.org/wiki/Prachalit_Nepal_alphabet" + }, + { + name: "Nko", + ranges: [[1984, 2043]], + direction: "rtl", + year: 1949, + living: false, + link: "https://en.wikipedia.org/wiki/N%27Ko_alphabet" + }, + { + name: "Nushu", + ranges: [[94177, 94178], [110960, 111356]], + direction: "ltr", + year: 1500, + living: true, + link: "https://en.wikipedia.org/wiki/N%C3%BCshu_script" + }, + { + name: "Ogham", + ranges: [[5760, 5789]], + direction: "ltr", + year: 350, + living: false, + link: "https://en.wikipedia.org/wiki/Ogham" + }, + { + name: "Ol Chiki", + ranges: [[7248, 7296]], + direction: "ltr", + year: 1925, + living: true, + link: "https://en.wikipedia.org/wiki/Ol_Chiki_script" + }, + { + name: "Old Turkic", + ranges: [[68608, 68681]], + direction: "rtl", + year: 750, + living: false, + link: "https://en.wikipedia.org/wiki/Old_Turkic_alphabet" + }, + { + name: "Oriya", + ranges: [[2817, 2820], [2821, 2829], [2831, 2833], [2835, 2857], [2858, 2865], [2866, 2868], [2869, 2874], [2876, 2885], [2887, 2889], [2891, 2894], [2902, 2904], [2908, 2910], [2911, 2916], [2918, 2936]], + direction: "ltr", + year: 1060, + living: true, + link: "https://en.wikipedia.org/wiki/Odia_alphabet" + }, + { + name: "Osage", + ranges: [[66736, 66772], [66776, 66812]], + direction: "ltr", + year: 2006, + living: true, + link: "https://en.wikipedia.org/wiki/Osage_alphabet" + }, + { + name: "Osmanya", + ranges: [[66688, 66718], [66720, 66730]], + direction: "ltr", + year: 1920, + living: true, + link: "https://en.wikipedia.org/wiki/Osmanya_alphabet" + }, + { + name: "Palmyrene", + ranges: [[67680, 67712]], + direction: "rtl", + year: -100, + living: false, + link: "https://en.wikipedia.org/wiki/Palmyrene_alphabet" + }, + { + name: "Pau Cin Hau", + ranges: [[72384, 72441]], + direction: "ltr", + year: 1900, + living: true, + link: "https://en.wikipedia.org/wiki/Pau_Cin_Hau" + }, + { + name: "Old Permic", + ranges: [[66384, 66427]], + direction: "ltr", + year: 1372, + living: false, + link: "https://en.wikipedia.org/wiki/Old_Permic_alphabet" + }, + { + name: "Phags-pa", + ranges: [[43072, 43123], [43124, 43127]], + direction: "ttb", + year: 1269, + living: false, + link: "https://en.wikipedia.org/wiki/%27Phags-pa_script" + }, + { + name: "Inscriptional Pahlavi", + ranges: [[68448, 68467], [68472, 68480]], + direction: "rtl", + year: -171, + living: false, + link: "https://en.wikipedia.org/wiki/Inscriptional_Pahlavi" + }, + { + name: "Psalter Pahlavi", + ranges: [[68480, 68498], [68505, 68509], [68521, 68528]], + direction: "rtl", + year: 550, + living: false, + link: "https://en.wikipedia.org/wiki/Psalter_Pahlavi" + }, + { + name: "Phoenician", + ranges: [[67840, 67868], [67871, 67872]], + direction: "rtl", + year: -1200, + living: false, + link: "https://en.wikipedia.org/wiki/Phoenician_alphabet" + }, + { + name: "Miao", + ranges: [[93952, 94021], [94032, 94079], [94095, 94112]], + direction: "ltr", + year: 1936, + living: true, + link: "https://en.wikipedia.org/wiki/Pollard_script" + }, + { + name: "Inscriptional Parthian", + ranges: [[68416, 68438], [68440, 68448]], + direction: "rtl", + year: -250, + living: false, + link: "https://en.wikipedia.org/wiki/Inscriptional_Parthian" + }, + { + name: "Rejang", + ranges: [[43312, 43348], [43359, 43360]], + direction: "ltr", + year: 1750, + living: true, + link: "https://en.wikipedia.org/wiki/Rejang_script" + }, + { + name: "Runic", + ranges: [[5792, 5867], [5870, 5881]], + direction: "ltr", + year: 150, + living: false, + link: "https://en.wikipedia.org/wiki/Runes" + }, + { + name: "Samaritan", + ranges: [[2048, 2094], [2096, 2111]], + direction: "rtl", + year: -600, + living: true, + link: "https://en.wikipedia.org/wiki/Samaritan_alphabet" + }, + { + name: "Old South Arabian", + ranges: [[68192, 68224]], + direction: "rtl", + year: -850, + living: false, + link: "https://en.wikipedia.org/wiki/Ancient_South_Arabian_script" + }, + { + name: "Saurashtra", + ranges: [[43136, 43206], [43214, 43226]], + direction: "ltr", + year: 1920, + living: true, + link: "https://en.wikipedia.org/wiki/Saurashtra_alphabet" + }, + { + name: "SignWriting", + ranges: [[120832, 121484], [121499, 121504], [121505, 121520]], + direction: "ttb", + year: 1974, + living: true, + link: "https://en.wikipedia.org/wiki/SignWriting" + }, + { + name: "Shavian", + ranges: [[66640, 66688]], + direction: "ltr", + year: 1960, + living: true, + link: "https://en.wikipedia.org/wiki/Shavian_alphabet" + }, + { + name: "Sharada", + ranges: [[70016, 70094], [70096, 70112]], + direction: "ltr", + year: 800, + living: true, + link: "https://en.wikipedia.org/wiki/%C5%9A%C4%81rad%C4%81_script" + }, + { + name: "Siddham", + ranges: [[71040, 71094], [71096, 71134]], + direction: "ltr", + year: 550, + living: false, + link: "https://en.wikipedia.org/wiki/Siddha%E1%B9%83_script" + }, + { + name: "Khudawadi", + ranges: [[70320, 70379], [70384, 70394]], + direction: "ltr", + year: 1550, + living: true, + link: "https://en.wikipedia.org/wiki/Khudabadi_script" + }, + { + name: "Sinhala", + ranges: [[3458, 3460], [3461, 3479], [3482, 3506], [3507, 3516], [3517, 3518], [3520, 3527], [3530, 3531], [3535, 3541], [3542, 3543], [3544, 3552], [3558, 3568], [3570, 3573], [70113, 70133]], + direction: "ltr", + year: 700, + living: true, + link: "https://en.wikipedia.org/wiki/Sinhalese_alphabet" + }, + { + name: "Sora Sompeng", + ranges: [[69840, 69865], [69872, 69882]], + direction: "ltr", + year: 1936, + living: true, + link: "https://en.wikipedia.org/wiki/Sorang_Sompeng_alphabet" + }, + { + name: "Soyombo", + ranges: [[72272, 72324], [72326, 72349], [72350, 72355]], + direction: "ltr", + year: 1650, + living: false, + link: "https://en.wikipedia.org/wiki/Soyombo_alphabet" + }, + { + name: "Sundanese", + ranges: [[7040, 7104], [7360, 7368]], + direction: "ltr", + year: 1350, + living: true, + link: "https://en.wikipedia.org/wiki/Sundanese_script" + }, + { + name: "Syloti Nagri", + ranges: [[43008, 43052]], + direction: "ltr", + year: 1303, + living: true, + link: "https://en.wikipedia.org/wiki/Sylheti_Nagari" + }, + { + name: "Syriac", + ranges: [[1792, 1806], [1807, 1867], [1869, 1872], [2144, 2155]], + direction: "rtl", + year: -200, + living: true, + link: "https://en.wikipedia.org/wiki/Syriac_alphabet" + }, + { + name: "Tagbanwa", + ranges: [[5984, 5997], [5998, 6001], [6002, 6004]], + direction: "ltr", + year: 1300, + living: true, + link: "https://en.wikipedia.org/wiki/Tagbanwa_script" + }, + { + name: "Takri", + ranges: [[71296, 71352], [71360, 71370]], + direction: "ltr", + year: 1550, + living: true, + link: "https://en.wikipedia.org/wiki/Takri_alphabet" + }, + { + name: "Tai Le", + ranges: [[6480, 6510], [6512, 6517]], + direction: "ltr", + year: 1200, + living: true, + link: "https://en.wikipedia.org/wiki/Tai_Le_alphabet" + }, + { + name: "New Tai Lue", + ranges: [[6528, 6572], [6576, 6602], [6608, 6619], [6622, 6624]], + direction: "ltr", + year: 1950, + living: true, + link: "https://en.wikipedia.org/wiki/New_Tai_Lue_alphabet" + }, + { + name: "Tamil", + ranges: [[2946, 2948], [2949, 2955], [2958, 2961], [2962, 2966], [2969, 2971], [2972, 2973], [2974, 2976], [2979, 2981], [2984, 2987], [2990, 3002], [3006, 3011], [3014, 3017], [3018, 3022], [3024, 3025], [3031, 3032], [3046, 3067]], + direction: "ltr", + year: 700, + living: true, + link: "https://en.wikipedia.org/wiki/Tamil_script" + }, + { + name: "Tangut", + ranges: [[94176, 94177], [94208, 100333], [100352, 101107]], + direction: "ltr", + year: 1036, + living: false, + link: "https://en.wikipedia.org/wiki/Tangut_script" + }, + { + name: "Tai Viet", + ranges: [[43648, 43715], [43739, 43744]], + direction: "ltr", + year: 1200, + living: true, + link: "https://en.wikipedia.org/wiki/Tai_Dam_language#Writing_system" + }, + { + name: "Telugu", + ranges: [[3072, 3076], [3077, 3085], [3086, 3089], [3090, 3113], [3114, 3130], [3133, 3141], [3142, 3145], [3146, 3150], [3157, 3159], [3160, 3163], [3168, 3172], [3174, 3184], [3192, 3200]], + direction: "ltr", + year: -900, + living: true, + link: "https://en.wikipedia.org/wiki/Telugu_script" + }, + { + name: "Tifinagh", + ranges: [[11568, 11624], [11631, 11633], [11647, 11648]], + direction: "ltr", + year: -300, + living: true, + link: "https://en.wikipedia.org/wiki/Tifinagh" + }, + { + name: "Tagalog", + ranges: [[5888, 5901], [5902, 5909]], + direction: "ltr", + year: 1250, + living: true, + link: "https://en.wikipedia.org/wiki/Baybayin" + }, + { + name: "Thaana", + ranges: [[1920, 1970]], + direction: "rtl", + year: 1599, + living: true, + link: "https://en.wikipedia.org/wiki/Thaana" + }, + { + name: "Thai", + ranges: [[3585, 3643], [3648, 3676]], + direction: "ltr", + year: 1283, + living: true, + link: "https://en.wikipedia.org/wiki/Thai_alphabet" + }, + { + name: "Tibetan", + ranges: [[3840, 3912], [3913, 3949], [3953, 3992], [3993, 4029], [4030, 4045], [4046, 4053], [4057, 4059]], + direction: "ltr", + year: 650, + living: false, + link: "https://en.wikipedia.org/wiki/Tibetan_alphabet" + }, + { + name: "Tirhuta", + ranges: [[70784, 70856], [70864, 70874]], + direction: "ltr", + year: 1450, + living: true, + link: "https://en.wikipedia.org/wiki/Tirhuta" + }, + { + name: "Ugaritic", + ranges: [[66432, 66462], [66463, 66464]], + direction: "ltr", + year: -1400, + living: false, + link: "https://en.wikipedia.org/wiki/Ugaritic_alphabet" + }, + { + name: "Vai", + ranges: [[42240, 42540]], + direction: "ltr", + year: 1830, + living: true, + link: "https://en.wikipedia.org/wiki/Vai_syllabary" + }, + { + name: "Warang Citi", + ranges: [[71840, 71923], [71935, 71936]], + direction: "ltr", + year: 1946, + living: true, + link: "https://en.wikipedia.org/wiki/Warang_Citi" + }, + { + name: "Old Persian", + ranges: [[66464, 66500], [66504, 66518]], + direction: "ltr", + year: -525, + living: false, + link: "https://en.wikipedia.org/wiki/Old_Persian_cuneiform" + }, + { + name: "Cuneiform", + ranges: [[73728, 74650], [74752, 74863], [74864, 74869], [74880, 75076]], + direction: "ltr", + year: -3050, + living: false, + link: "https://en.wikipedia.org/wiki/Cuneiform_script" + }, + { + name: "Yi", + ranges: [[40960, 42125], [42128, 42183]], + direction: "ltr", + year: 1450, + living: true, + link: "https://en.wikipedia.org/wiki/Yi_script" + }, + { + name: "Zanabazar Square", + ranges: [[72192, 72264]], + direction: "ltr", + year: 1700, + living: false, + link: "https://en.wikipedia.org/wiki/Mongolian_writing_systems#Horizontal_square_script" + } +]; + +// This makes sure the data is exported in node.js — +// `require('./path/to/scripts.js')` will get you the array. +if (typeof module != "undefined" && module.exports && (typeof window == "undefined" || window.exports != exports)) + module.exports = SCRIPTS; +if (typeof global != "undefined" && !global.SCRIPTS) + global.SCRIPTS = SCRIPTS; diff --git a/docs/code/skillsharing.zip b/docs/code/skillsharing.zip new file mode 100644 index 0000000000000000000000000000000000000000..ba5b0652c8366a71e3b4a7ef3e0d8df50db0ee2b GIT binary patch literal 5013 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 + +Skill Sharing + + +

      Skill Sharing

      + + diff --git a/docs/code/skillsharing/public/skillsharing.css b/docs/code/skillsharing/public/skillsharing.css new file mode 100644 index 000000000..75da06bbc --- /dev/null +++ b/docs/code/skillsharing/public/skillsharing.css @@ -0,0 +1,11 @@ +.talk { margin: 40px 0; } + +.comment { font-style: italic; margin: 0; } +.comment strong { font-style: normal; } + +.talk h2 { font-size: 130%; margin-bottom: 0; } +.talk h2 button { vertical-align: bottom; } + +h1, h3 { margin-bottom: 0.33em; } + +label input { display: block; width: 30em; } diff --git a/docs/code/skillsharing/public/skillsharing_client.js b/docs/code/skillsharing/public/skillsharing_client.js new file mode 100644 index 000000000..9ae7b80c0 --- /dev/null +++ b/docs/code/skillsharing/public/skillsharing_client.js @@ -0,0 +1,178 @@ +function handleAction(state, action) { + if (action.type == "setUser") { + localStorage.setItem("userName", action.user); + return Object.assign({}, state, {user: action.user}); + } else if (action.type == "setTalks") { + return Object.assign({}, state, {talks: action.talks}); + } else if (action.type == "newTalk") { + fetchOK(talkURL(action.title), { + method: "PUT", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({ + presenter: state.user, + summary: action.summary + }) + }).catch(reportError); + } else if (action.type == "deleteTalk") { + fetchOK(talkURL(action.talk), {method: "DELETE"}) + .catch(reportError); + } else if (action.type == "newComment") { + fetchOK(talkURL(action.talk) + "/comments", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({ + author: state.user, + message: action.message + }) + }).catch(reportError); + } + return state; +} + +function fetchOK(url, options) { + return fetch(url, options).then(response => { + if (response.status < 400) return response; + else throw new Error(response.statusText); + }); +} + +function talkURL(title) { + return "talks/" + encodeURIComponent(title); +} + +function reportError(error) { + alert(String(error)); +} + +function renderUserField(name, dispatch) { + return elt("label", {}, "Your name: ", elt("input", { + type: "text", + value: name, + onchange(event) { + dispatch({type: "setUser", user: event.target.value}); + } + })); +} + +function elt(type, props, ...children) { + let dom = document.createElement(type); + if (props) Object.assign(dom, props); + for (let child of children) { + if (typeof child != "string") dom.appendChild(child); + else dom.appendChild(document.createTextNode(child)); + } + return dom; +} + +function renderTalk(talk, dispatch) { + return elt( + "section", {className: "talk"}, + elt("h2", null, talk.title, " ", elt("button", { + type: "button", + onclick() { + dispatch({type: "deleteTalk", talk: talk.title}); + } + }, "Delete")), + elt("div", null, "by ", + elt("strong", null, talk.presenter)), + elt("p", null, talk.summary), + ...talk.comments.map(renderComment), + elt("form", { + onsubmit(event) { + event.preventDefault(); + let form = event.target; + dispatch({type: "newComment", + talk: talk.title, + message: form.elements.comment.value}); + form.reset(); + } + }, elt("input", {type: "text", name: "comment"}), " ", + elt("button", {type: "submit"}, "Add comment"))); +} + +function renderComment(comment) { + return elt("p", {className: "comment"}, + elt("strong", null, comment.author), + ": ", comment.message); +} + +function renderTalkForm(dispatch) { + let title = elt("input", {type: "text"}); + let summary = elt("input", {type: "text"}); + return elt("form", { + onsubmit(event) { + event.preventDefault(); + dispatch({type: "newTalk", + title: title.value, + summary: summary.value}); + event.target.reset(); + } + }, elt("h3", null, "Submit a Talk"), + elt("label", null, "Title: ", title), + elt("label", null, "Summary: ", summary), + elt("button", {type: "submit"}, "Submit")); +} + +async function pollTalks(update) { + let tag = undefined; + for (;;) { + let response; + try { + response = await fetchOK("/talks", { + headers: tag && {"If-None-Match": tag, + "Prefer": "wait=90"} + }); + } catch (e) { + console.log("Request failed: " + e); + await new Promise(resolve => setTimeout(resolve, 500)); + continue; + } + if (response.status == 304) continue; + tag = response.headers.get("ETag"); + update(await response.json()); + } +} + +var SkillShareApp = class SkillShareApp { + constructor(state, dispatch) { + this.dispatch = dispatch; + this.talkDOM = elt("div", {className: "talks"}); + this.dom = elt("div", null, + renderUserField(state.user, dispatch), + this.talkDOM, + renderTalkForm(dispatch)); + this.syncState(state); + } + + syncState(state) { + if (state.talks != this.talks) { + this.talkDOM.textContent = ""; + for (let talk of state.talks) { + this.talkDOM.appendChild( + renderTalk(talk, this.dispatch)); + } + this.talks = state.talks; + } + } +} + +function runApp() { + let user = localStorage.getItem("userName") || "Anon"; + let state, app; + function dispatch(action) { + state = handleAction(state, action); + app.syncState(state); + } + + pollTalks(talks => { + if (!app) { + state = {user, talks}; + app = new SkillShareApp(state, dispatch); + document.body.appendChild(app.dom); + } else { + dispatch({type: "setTalks", talks}); + } + }).catch(reportError); +} + +runApp(); diff --git a/docs/code/skillsharing/router.js b/docs/code/skillsharing/router.js new file mode 100644 index 000000000..522a94da7 --- /dev/null +++ b/docs/code/skillsharing/router.js @@ -0,0 +1,21 @@ +var {parse} = require("url"); + +module.exports = class Router { + constructor() { + this.routes = []; + } + add(method, url, handler) { + this.routes.push({method, url, handler}); + } + resolve(context, request) { + let path = parse(request.url).pathname; + + for (let {method, url, handler} of this.routes) { + let match = url.exec(path); + if (!match || request.method != method) continue; + let urlParts = match.slice(1).map(decodeURIComponent); + return handler(context, ...urlParts, request); + } + return null; + } +}; diff --git a/docs/code/skillsharing/skillsharing_server.js b/docs/code/skillsharing/skillsharing_server.js new file mode 100644 index 000000000..8012a5f41 --- /dev/null +++ b/docs/code/skillsharing/skillsharing_server.js @@ -0,0 +1,150 @@ +var {createServer} = require("http"); +var Router = require("./router"); +var ecstatic = require("ecstatic"); + +var router = new Router(); +var defaultHeaders = {"Content-Type": "text/plain"}; + +var SkillShareServer = class SkillShareServer { + constructor(talks) { + this.talks = talks; + this.version = 0; + this.waiting = []; + + let fileServer = ecstatic({root: "./public"}); + this.server = createServer((request, response) => { + let resolved = router.resolve(this, request); + if (resolved) { + resolved.catch(error => { + if (error.status != null) return error; + return {body: String(error), status: 500}; + }).then(({body, + status = 200, + headers = defaultHeaders}) => { + response.writeHead(status, headers); + response.end(body); + }); + } else { + fileServer(request, response); + } + }); + } + start(port) { + this.server.listen(port); + } + stop() { + this.server.close(); + } +} + +const talkPath = /^\/talks\/([^\/]+)$/; + +router.add("GET", talkPath, async (server, title) => { + if (title in server.talks) { + return {body: JSON.stringify(server.talks[title]), + headers: {"Content-Type": "application/json"}}; + } else { + return {status: 404, body: `No talk '${title}' found`}; + } +}); + +router.add("DELETE", talkPath, async (server, title) => { + if (title in server.talks) { + delete server.talks[title]; + server.updated(); + } + return {status: 204}; +}); + +function readStream(stream) { + return new Promise((resolve, reject) => { + let data = ""; + stream.on("error", reject); + stream.on("data", chunk => data += chunk.toString()); + stream.on("end", () => resolve(data)); + }); +} + +router.add("PUT", talkPath, + async (server, title, request) => { + let requestBody = await readStream(request); + let talk; + try { talk = JSON.parse(requestBody); } + catch (_) { return {status: 400, body: "Invalid JSON"}; } + + if (!talk || + typeof talk.presenter != "string" || + typeof talk.summary != "string") { + return {status: 400, body: "Bad talk data"}; + } + server.talks[title] = {title, + presenter: talk.presenter, + summary: talk.summary, + comments: []}; + server.updated(); + return {status: 204}; +}); + +router.add("POST", /^\/talks\/([^\/]+)\/comments$/, + async (server, title, request) => { + let requestBody = await readStream(request); + let comment; + try { comment = JSON.parse(requestBody); } + catch (_) { return {status: 400, body: "Invalid JSON"}; } + + if (!comment || + typeof comment.author != "string" || + typeof comment.message != "string") { + return {status: 400, body: "Bad comment data"}; + } else if (title in server.talks) { + server.talks[title].comments.push(comment); + server.updated(); + return {status: 204}; + } else { + return {status: 404, body: `No talk '${title}' found`}; + } +}); + +SkillShareServer.prototype.talkResponse = function() { + let talks = []; + for (let title of Object.keys(this.talks)) { + talks.push(this.talks[title]); + } + return { + body: JSON.stringify(talks), + headers: {"Content-Type": "application/json", + "ETag": `"${this.version}"`} + }; +}; + +router.add("GET", /^\/talks$/, async (server, request) => { + let tag = /"(.*)"/.exec(request.headers["if-none-match"]); + let wait = /\bwait=(\d+)/.exec(request.headers["prefer"]); + if (!tag || tag[1] != server.version) { + return server.talkResponse(); + } else if (!wait) { + return {status: 304}; + } else { + return server.waitForChanges(Number(wait[1])); + } +}); + +SkillShareServer.prototype.waitForChanges = function(time) { + return new Promise(resolve => { + this.waiting.push(resolve); + setTimeout(() => { + if (!this.waiting.includes(resolve)) return; + this.waiting = this.waiting.filter(r => r != resolve); + resolve({status: 304}); + }, time * 1000); + }); +}; + +SkillShareServer.prototype.updated = function() { + this.version++; + let response = this.talkResponse(); + this.waiting.forEach(resolve => resolve(response)); + this.waiting = []; +}; + +new SkillShareServer(Object.create(null)).start(8000); diff --git a/docs/code/solutions/02_1_looping_a_triangle.js b/docs/code/solutions/02_1_looping_a_triangle.js new file mode 100644 index 000000000..2a7ba5f15 --- /dev/null +++ b/docs/code/solutions/02_1_looping_a_triangle.js @@ -0,0 +1,2 @@ +for (let line = "#"; line.length < 8; line += "#") + console.log(line); diff --git a/docs/code/solutions/02_2_fizzbuzz.js b/docs/code/solutions/02_2_fizzbuzz.js new file mode 100644 index 000000000..67138a2a3 --- /dev/null +++ b/docs/code/solutions/02_2_fizzbuzz.js @@ -0,0 +1,6 @@ +for (let n = 1; n <= 100; n++) { + let output = ""; + if (n % 3 == 0) output += "Fizz"; + if (n % 5 == 0) output += "Buzz"; + console.log(output || n); +} diff --git a/docs/code/solutions/02_3_chessboard.js b/docs/code/solutions/02_3_chessboard.js new file mode 100644 index 000000000..0d70bac9b --- /dev/null +++ b/docs/code/solutions/02_3_chessboard.js @@ -0,0 +1,16 @@ +let size = 8; + +let board = ""; + +for (let y = 0; y < size; y++) { + for (let x = 0; x < size; x++) { + if ((x + y) % 2 == 0) { + board += " "; + } else { + board += "#"; + } + } + board += "\n"; +} + +console.log(board); diff --git a/docs/code/solutions/03_1_minimum.js b/docs/code/solutions/03_1_minimum.js new file mode 100644 index 000000000..6954b43a9 --- /dev/null +++ b/docs/code/solutions/03_1_minimum.js @@ -0,0 +1,9 @@ +function min(a, b) { + if (a < b) return a; + else return b; +} + +console.log(min(0, 10)); +// → 0 +console.log(min(0, -10)); +// → -10 diff --git a/docs/code/solutions/03_2_recursion.js b/docs/code/solutions/03_2_recursion.js new file mode 100644 index 000000000..db94f41e4 --- /dev/null +++ b/docs/code/solutions/03_2_recursion.js @@ -0,0 +1,13 @@ +function isEven(n) { + if (n == 0) return true; + else if (n == 1) return false; + else if (n < 0) return isEven(-n); + else return isEven(n - 2); +} + +console.log(isEven(50)); +// → true +console.log(isEven(75)); +// → false +console.log(isEven(-1)); +// → false diff --git a/docs/code/solutions/03_3_bean_counting.js b/docs/code/solutions/03_3_bean_counting.js new file mode 100644 index 000000000..02b04cb86 --- /dev/null +++ b/docs/code/solutions/03_3_bean_counting.js @@ -0,0 +1,18 @@ +function countChar(string, ch) { + let counted = 0; + for (let i = 0; i < string.length; i++) { + if (string[i] == ch) { + counted += 1; + } + } + return counted; +} + +function countBs(string) { + return countChar(string, "B"); +} + +console.log(countBs("BBC")); +// → 2 +console.log(countChar("kakkerlak", "k")); +// → 4 diff --git a/docs/code/solutions/04_1_the_sum_of_a_range.js b/docs/code/solutions/04_1_the_sum_of_a_range.js new file mode 100644 index 000000000..d5b502990 --- /dev/null +++ b/docs/code/solutions/04_1_the_sum_of_a_range.js @@ -0,0 +1,25 @@ +function range(start, end, step = start < end ? 1 : -1) { + let array = []; + + if (step > 0) { + for (let i = start; i <= end; i += step) array.push(i); + } else { + for (let i = start; i >= end; i += step) array.push(i); + } + return array; +} + +function sum(array) { + let total = 0; + for (let value of array) { + total += value; + } + return total; +} + +console.log(range(1, 10)) +// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +console.log(range(5, 2, -1)); +// → [5, 4, 3, 2] +console.log(sum(range(1, 10))); +// → 55 diff --git a/docs/code/solutions/04_2_reversing_an_array.js b/docs/code/solutions/04_2_reversing_an_array.js new file mode 100644 index 000000000..442dde8af --- /dev/null +++ b/docs/code/solutions/04_2_reversing_an_array.js @@ -0,0 +1,23 @@ +function reverseArray(array) { + let output = []; + for (let i = array.length - 1; i >= 0; i--) { + output.push(array[i]); + } + return output; +} + +function reverseArrayInPlace(array) { + for (let i = 0; i < Math.floor(array.length / 2); i++) { + let old = array[i]; + array[i] = array[array.length - 1 - i]; + array[array.length - 1 - i] = old; + } + return array; +} + +console.log(reverseArray(["A", "B", "C"])); +// → ["C", "B", "A"]; +let arrayValue = [1, 2, 3, 4, 5]; +reverseArrayInPlace(arrayValue); +console.log(arrayValue); +// → [5, 4, 3, 2, 1] diff --git a/docs/code/solutions/04_3_a_list.js b/docs/code/solutions/04_3_a_list.js new file mode 100644 index 000000000..bf93ac075 --- /dev/null +++ b/docs/code/solutions/04_3_a_list.js @@ -0,0 +1,34 @@ +function arrayToList(array) { + let list = null; + for (let i = array.length - 1; i >= 0; i--) { + list = {value: array[i], rest: list}; + } + return list; +} + +function listToArray(list) { + let array = []; + for (let node = list; node; node = node.rest) { + array.push(node.value); + } + return array; +} + +function prepend(value, list) { + return {value, rest: list}; +} + +function nth(list, n) { + if (!list) return undefined; + else if (n == 0) return list.value; + else return nth(list.rest, n - 1); +} + +console.log(arrayToList([10, 20])); +// → {value: 10, rest: {value: 20, rest: null}} +console.log(listToArray(arrayToList([10, 20, 30]))); +// → [10, 20, 30] +console.log(prepend(10, prepend(20, null))); +// → {value: 10, rest: {value: 20, rest: null}} +console.log(nth(arrayToList([10, 20, 30]), 1)); +// → 20 diff --git a/docs/code/solutions/04_4_deep_comparison.js b/docs/code/solutions/04_4_deep_comparison.js new file mode 100644 index 000000000..bd3fde9b1 --- /dev/null +++ b/docs/code/solutions/04_4_deep_comparison.js @@ -0,0 +1,24 @@ +function deepEqual(a, b) { + if (a === b) return true; + + if (a == null || typeof a != "object" || + b == null || typeof b != "object") return false; + + let keysA = Object.keys(a), keysB = Object.keys(b); + + if (keysA.length != keysB.length) return false; + + for (let key of keysA) { + if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false; + } + + return true; +} + +let obj = {here: {is: "an"}, object: 2}; +console.log(deepEqual(obj, obj)); +// → true +console.log(deepEqual(obj, {here: 1, object: 2})); +// → false +console.log(deepEqual(obj, {here: {is: "an"}, object: 2})); +// → true diff --git a/docs/code/solutions/05_1_flattening.js b/docs/code/solutions/05_1_flattening.js new file mode 100644 index 000000000..fbd6b0fed --- /dev/null +++ b/docs/code/solutions/05_1_flattening.js @@ -0,0 +1,4 @@ +let arrays = [[1, 2, 3], [4, 5], [6]]; + +console.log(arrays.reduce((flat, current) => flat.concat(current), [])); +// → [1, 2, 3, 4, 5, 6] diff --git a/docs/code/solutions/05_2_your_own_loop.js b/docs/code/solutions/05_2_your_own_loop.js new file mode 100644 index 000000000..55a715c43 --- /dev/null +++ b/docs/code/solutions/05_2_your_own_loop.js @@ -0,0 +1,10 @@ +function loop(start, test, update, body) { + for (let value = start; test(value); value = update(value)) { + body(value); + } +} + +loop(3, n => n > 0, n => n - 1, console.log); +// → 3 +// → 2 +// → 1 diff --git a/docs/code/solutions/05_3_everything.js b/docs/code/solutions/05_3_everything.js new file mode 100644 index 000000000..08e0e533a --- /dev/null +++ b/docs/code/solutions/05_3_everything.js @@ -0,0 +1,17 @@ +function every(array, predicate) { + for (let element of array) { + if (!predicate(element)) return false; + } + return true; +} + +function every2(array, predicate) { + return !array.some(element => !predicate(element)); +} + +console.log(every([1, 3, 5], n => n < 10)); +// → true +console.log(every([2, 4, 16], n => n < 10)); +// → false +console.log(every([], n => n < 10)); +// → true diff --git a/docs/code/solutions/05_4_dominant_writing_direction.js b/docs/code/solutions/05_4_dominant_writing_direction.js new file mode 100644 index 000000000..1b5494981 --- /dev/null +++ b/docs/code/solutions/05_4_dominant_writing_direction.js @@ -0,0 +1,15 @@ +function dominantDirection(text) { + let counted = countBy(text, char => { + let script = characterScript(char.codePointAt(0)); + return script ? script.direction : "none"; + }).filter(({name}) => name != "none"); + + if (counted.length == 0) return "ltr"; + + return counted.reduce((a, b) => a.count > b.count ? a : b).name; +} + +console.log(dominantDirection("Hello!")); +// → ltr +console.log(dominantDirection("Hey, مساء الخير")); +// → rtl diff --git a/docs/code/solutions/06_1_a_vector_type.js b/docs/code/solutions/06_1_a_vector_type.js new file mode 100644 index 000000000..ee676c10e --- /dev/null +++ b/docs/code/solutions/06_1_a_vector_type.js @@ -0,0 +1,25 @@ +class Vec { + constructor(x, y) { + this.x = x; + this.y = y; + } + + plus(other) { + return new Vec(this.x + other.x, this.y + other.y); + } + + minus(other) { + return new Vec(this.x - other.x, this.y - other.y); + } + + get length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } +} + +console.log(new Vec(1, 2).plus(new Vec(2, 3))); +// → Vec{x: 3, y: 5} +console.log(new Vec(1, 2).minus(new Vec(2, 3))); +// → Vec{x: -1, y: -1} +console.log(new Vec(3, 4).length); +// → 5 diff --git a/docs/code/solutions/06_2_groups.js b/docs/code/solutions/06_2_groups.js new file mode 100644 index 000000000..e1c4cb3c9 --- /dev/null +++ b/docs/code/solutions/06_2_groups.js @@ -0,0 +1,36 @@ +class Group { + constructor() { + this.members = []; + } + + add(value) { + if (!this.has(value)) { + this.members.push(value); + } + } + + delete(value) { + this.members = this.members.filter(v => v !== value); + } + + has(value) { + return this.members.includes(value); + } + + static from(collection) { + let group = new Group; + for (let value of collection) { + group.add(value); + } + return group; + } +} + +let group = Group.from([10, 20]); +console.log(group.has(10)); +// → true +console.log(group.has(30)); +// → false +group.add(10); +group.delete(10); +console.log(group.has(10)); diff --git a/docs/code/solutions/06_3_iterable_groups.js b/docs/code/solutions/06_3_iterable_groups.js new file mode 100644 index 000000000..202d98ebe --- /dev/null +++ b/docs/code/solutions/06_3_iterable_groups.js @@ -0,0 +1,56 @@ +class Group { + constructor() { + this.members = []; + } + + add(value) { + if (!this.has(value)) { + this.members.push(value); + } + } + + delete(value) { + this.members = this.members.filter(v => v !== value); + } + + has(value) { + return this.members.includes(value); + } + + static from(collection) { + let group = new Group; + for (let value of collection) { + group.add(value); + } + return group; + } + + [Symbol.iterator]() { + return new GroupIterator(this); + } +} + +class GroupIterator { + constructor(group) { + this.group = group; + this.position = 0; + } + + next() { + if (this.position >= this.group.members.length) { + return {done: true}; + } else { + let result = {value: this.group.members[this.position], + done: false}; + this.position++; + return result; + } + } +} + +for (let value of Group.from(["a", "b", "c"])) { + console.log(value); +} +// → a +// → b +// → c diff --git a/docs/code/solutions/06_4_borrowing_a_method.js b/docs/code/solutions/06_4_borrowing_a_method.js new file mode 100644 index 000000000..29543894e --- /dev/null +++ b/docs/code/solutions/06_4_borrowing_a_method.js @@ -0,0 +1,4 @@ +let map = {one: true, two: true, hasOwnProperty: true}; + +console.log(Object.prototype.hasOwnProperty.call(map, "one")); +// → true diff --git a/docs/code/solutions/07_1_measuring_a_robot.js b/docs/code/solutions/07_1_measuring_a_robot.js new file mode 100644 index 000000000..dc95bbcee --- /dev/null +++ b/docs/code/solutions/07_1_measuring_a_robot.js @@ -0,0 +1,21 @@ +function countSteps(state, robot, memory) { + for (let steps = 0;; steps++) { + if (state.parcels.length == 0) return steps; + let action = robot(state, memory); + state = state.move(action.direction); + memory = action.memory; + } +} + +function compareRobots(robot1, memory1, robot2, memory2) { + let total1 = 0, total2 = 0; + for (let i = 0; i < 100; i++) { + let state = VillageState.random(); + total1 += countSteps(state, robot1, memory1); + total2 += countSteps(state, robot2, memory2); + } + console.log(`Robot 1 needed ${total1 / 100} steps per task`) + console.log(`Robot 2 needed ${total2 / 100}`) +} + +compareRobots(routeRobot, [], goalOrientedRobot, []); diff --git a/docs/code/solutions/07_2_robot_efficiency.js b/docs/code/solutions/07_2_robot_efficiency.js new file mode 100644 index 000000000..5d2184aa1 --- /dev/null +++ b/docs/code/solutions/07_2_robot_efficiency.js @@ -0,0 +1,26 @@ +function lazyRobot({place, parcels}, route) { + if (route.length == 0) { + // Describe a route for every parcel + let routes = parcels.map(parcel => { + if (parcel.place != place) { + return {route: findRoute(roadGraph, place, parcel.place), + pickUp: true}; + } else { + return {route: findRoute(roadGraph, place, parcel.address), + pickUp: false}; + } + }); + + // This determines the precedence a route gets when choosing. + // Route length counts negatively, routes that pick up a package + // get a small bonus. + function score({route, pickUp}) { + return (pickUp ? 0.5 : 0) - route.length; + } + route = routes.reduce((a, b) => score(a) > score(b) ? a : b).route; + } + + return {direction: route[0], memory: route.slice(1)}; +} + +runRobotAnimation(VillageState.random(), lazyRobot, []); diff --git a/docs/code/solutions/07_3_persistent_group.js b/docs/code/solutions/07_3_persistent_group.js new file mode 100644 index 000000000..33020584c --- /dev/null +++ b/docs/code/solutions/07_3_persistent_group.js @@ -0,0 +1,32 @@ +class PGroup { + constructor(members) { + this.members = members; + } + + add(value) { + if (this.has(value)) return this; + return new PGroup(this.members.concat([value])); + } + + delete(value) { + if (!this.has(value)) return this; + return new PGroup(this.members.filter(m => m !== value)); + } + + has(value) { + return this.members.includes(value); + } +} + +PGroup.empty = new PGroup([]); + +let a = PGroup.empty.add("a"); +let ab = a.add("b"); +let b = ab.delete("a"); + +console.log(b.has("b")); +// → true +console.log(a.has("b")); +// → false +console.log(b.has("a")); +// → false diff --git a/docs/code/solutions/08_1_retry.js b/docs/code/solutions/08_1_retry.js new file mode 100644 index 000000000..f2a7ae10c --- /dev/null +++ b/docs/code/solutions/08_1_retry.js @@ -0,0 +1,23 @@ +class MultiplicatorUnitFailure extends Error {} + +function primitiveMultiply(a, b) { + if (Math.random() < 0.2) { + return a * b; + } else { + throw new MultiplicatorUnitFailure("Klunk"); + } +} + +function reliableMultiply(a, b) { + for (;;) { + try { + return primitiveMultiply(a, b); + } catch (e) { + if (!(e instanceof MultiplicatorUnitFailure)) + throw e; + } + } +} + +console.log(reliableMultiply(8, 8)); +// → 64 diff --git a/docs/code/solutions/08_2_the_locked_box.js b/docs/code/solutions/08_2_the_locked_box.js new file mode 100644 index 000000000..6a36be3f4 --- /dev/null +++ b/docs/code/solutions/08_2_the_locked_box.js @@ -0,0 +1,39 @@ +const box = { + locked: true, + unlock() { this.locked = false; }, + lock() { this.locked = true; }, + _content: [], + get content() { + if (this.locked) throw new Error("Locked!"); + return this._content; + } +}; + +function withBoxUnlocked(body) { + let locked = box.locked; + if (!locked) { + return body(); + } + + box.unlock(); + try { + return body(); + } finally { + box.lock(); + } +} + +withBoxUnlocked(function() { + box.content.push("gold piece"); +}); + +try { + withBoxUnlocked(function() { + throw new Error("Pirates on the horizon! Abort!"); + }); +} catch (e) { + console.log("Error raised:", e); +} + +console.log(box.locked); +// → true diff --git a/docs/code/solutions/09_1_regexp_golf.js b/docs/code/solutions/09_1_regexp_golf.js new file mode 100644 index 000000000..72a6af16a --- /dev/null +++ b/docs/code/solutions/09_1_regexp_golf.js @@ -0,0 +1,41 @@ +// Fill in the regular expressions + +verify(/ca[rt]/, + ["my car", "bad cats"], + ["camper", "high art"]); + +verify(/pr?op/, + ["pop culture", "mad props"], + ["plop", "prrrop"]); + +verify(/ferr(et|y|ari)/, + ["ferret", "ferry", "ferrari"], + ["ferrum", "transfer A"]); + +verify(/ious\b/, + ["how delicious", "spacious room"], + ["ruinous", "consciousness"]); + +verify(/\s[.,:;]/, + ["bad punctuation ."], + ["escape the dot"]); + +verify(/\w{7}/, + ["hottentottententen"], + ["no", "hotten totten tenten"]); + +verify(/\b[^\We]+\b/i, + ["red platypus", "wobbling nest"], + ["earth bed", "learning ape", "BEET"]); + + +function verify(regexp, yes, no) { + // Ignore unfinished exercises + if (regexp.source == "...") return; + for (let str of yes) if (!regexp.test(str)) { + console.log(`Failure to match '${str}'`); + } + for (let str of no) if (regexp.test(str)) { + console.log(`Unexpected match for '${str}'`); + } +} diff --git a/docs/code/solutions/09_2_quoting_style.js b/docs/code/solutions/09_2_quoting_style.js new file mode 100644 index 000000000..f5b0f8934 --- /dev/null +++ b/docs/code/solutions/09_2_quoting_style.js @@ -0,0 +1,5 @@ +let text = "'I'm the cook,' he said, 'it's my job.'"; + +console.log(text.replace(/(^|\W)'|'(\W|$)/g, '$1"$2')); +// → "I'm the cook," he said, "it's my job." + diff --git a/docs/code/solutions/09_3_numbers_again.js b/docs/code/solutions/09_3_numbers_again.js new file mode 100644 index 000000000..34691f449 --- /dev/null +++ b/docs/code/solutions/09_3_numbers_again.js @@ -0,0 +1,16 @@ +// Fill in this regular expression. +let number = /^[+\-]?(\d+(\.\d*)?|\.\d+)([eE][+\-]?\d+)?$/; + +// Tests: +for (let str of ["1", "-1", "+15", "1.55", ".5", "5.", + "1.3e2", "1E-4", "1e+12"]) { + if (!number.test(str)) { + console.log(`Failed to match '${str}'`); + } +} +for (let str of ["1a", "+-1", "1.2.3", "1+1", "1e4.5", + ".5.", "1f5", "."]) { + if (number.test(str)) { + console.log(`Incorrectly accepted '${str}'`); + } +} diff --git a/docs/code/solutions/10_2_roads_module.js b/docs/code/solutions/10_2_roads_module.js new file mode 100644 index 000000000..ec83e579b --- /dev/null +++ b/docs/code/solutions/10_2_roads_module.js @@ -0,0 +1,13 @@ +const {buildGraph} = require("./graph"); + +const roads = [ + "Alice's House-Bob's House", "Alice's House-Cabin", + "Alice's House-Post Office", "Bob's House-Town Hall", + "Daria's House-Ernie's House", "Daria's House-Town Hall", + "Ernie's House-Grete's House", "Grete's House-Farm", + "Grete's House-Shop", "Marketplace-Farm", + "Marketplace-Post Office", "Marketplace-Shop", + "Marketplace-Town Hall", "Shop-Town Hall" +]; + +exports.roadGraph = buildGraph(roads.map(r => r.split("-"))); diff --git a/docs/code/solutions/11_1_tracking_the_scalpel.js b/docs/code/solutions/11_1_tracking_the_scalpel.js new file mode 100644 index 000000000..cd74b5119 --- /dev/null +++ b/docs/code/solutions/11_1_tracking_the_scalpel.js @@ -0,0 +1,23 @@ +async function locateScalpel(nest) { + let current = nest.name; + for (;;) { + let next = await anyStorage(nest, current, "scalpel"); + if (next == current) return current; + current = next; + } +} + +function locateScalpel2(nest) { + function loop(current) { + return anyStorage(nest, current, "scalpel").then(next => { + if (next == current) return current; + else return loop(next); + }); + } + return loop(nest.name); +} + +locateScalpel(bigOak).then(console.log); +// → Butcher's Shop +locateScalpel2(bigOak).then(console.log); +// → Butcher's Shop diff --git a/docs/code/solutions/11_2_building_promiseall.js b/docs/code/solutions/11_2_building_promiseall.js new file mode 100644 index 000000000..9e8e1465e --- /dev/null +++ b/docs/code/solutions/11_2_building_promiseall.js @@ -0,0 +1,34 @@ +function Promise_all(promises) { + return new Promise((resolve, reject) => { + let results = []; + let pending = promises.length; + for (let i = 0; i < promises.length; i++) { + promises[i].then(result => { + results[i] = result; + pending--; + if (pending == 0) resolve(results); + }).catch(reject); + } + if (promises.length == 0) resolve(results); + }); +} + +// Test code. +Promise_all([]).then(array => { + console.log("This should be []:", array); +}); +function soon(val) { + return new Promise(resolve => { + setTimeout(() => resolve(val), Math.random() * 500); + }); +} +Promise_all([soon(1), soon(2), soon(3)]).then(array => { + console.log("This should be [1, 2, 3]:", array); +}); +Promise_all([soon(1), Promise.reject("X"), soon(3)]).then(array => { + console.log("We should not get here"); +}).catch(error => { + if (error != "X") { + console.log("Unexpected failure:", error); + } +}); diff --git a/docs/code/solutions/12_1_arrays.js b/docs/code/solutions/12_1_arrays.js new file mode 100644 index 000000000..e857c914b --- /dev/null +++ b/docs/code/solutions/12_1_arrays.js @@ -0,0 +1,17 @@ +topScope.array = (...values) => values; + +topScope.length = array => array.length; + +topScope.element = (array, i) => array[i]; + +run(` +do(define(sum, fun(array, + do(define(i, 0), + define(sum, 0), + while(<(i, length(array)), + do(define(sum, +(sum, element(array, i))), + define(i, +(i, 1)))), + sum))), + print(sum(array(1, 2, 3)))) +`); +// → 6 diff --git a/docs/code/solutions/12_3_comments.js b/docs/code/solutions/12_3_comments.js new file mode 100644 index 000000000..7afc21a9e --- /dev/null +++ b/docs/code/solutions/12_3_comments.js @@ -0,0 +1,12 @@ +function skipSpace(string) { + let skippable = string.match(/^(\s|#.*)*/); + return string.slice(skippable[0].length); +} + +console.log(parse("# hello\nx")); +// → {type: "word", name: "x"} + +console.log(parse("a # one\n # two\n()")); +// → {type: "apply", +// operator: {type: "word", name: "a"}, +// args: []} diff --git a/docs/code/solutions/12_4_fixing_scope.js b/docs/code/solutions/12_4_fixing_scope.js new file mode 100644 index 000000000..1b4c6e723 --- /dev/null +++ b/docs/code/solutions/12_4_fixing_scope.js @@ -0,0 +1,25 @@ +specialForms.set = (args, env) => { + if (args.length != 2 || args[0].type != "word") { + throw new SyntaxError("Bad use of set"); + } + let varName = args[0].name; + let value = evaluate(args[1], env); + + for (let scope = env; scope; scope = Object.getPrototypeOf(scope)) { + if (Object.prototype.hasOwnProperty.call(scope, varName)) { + scope[varName] = value; + return value; + } + } + throw new ReferenceError(`Setting undefined variable ${varName}`); +}; + +run(` +do(define(x, 4), + define(setx, fun(val, set(x, val))), + setx(50), + print(x)) +`); +// → 50 +run(`set(quux, true)`); +// → Some kind of ReferenceError diff --git a/docs/code/solutions/14_1_build_a_table.html b/docs/code/solutions/14_1_build_a_table.html new file mode 100644 index 000000000..4df385bd3 --- /dev/null +++ b/docs/code/solutions/14_1_build_a_table.html @@ -0,0 +1,49 @@ + + + +

      Mountains

      + +
      + + diff --git a/docs/code/solutions/14_2_elements_by_tag_name.html b/docs/code/solutions/14_2_elements_by_tag_name.html new file mode 100644 index 000000000..45e9dbc3b --- /dev/null +++ b/docs/code/solutions/14_2_elements_by_tag_name.html @@ -0,0 +1,33 @@ + + +

      Heading with a span element.

      +

      A paragraph with one, two + spans.

      + + diff --git a/docs/code/solutions/14_3_the_cats_hat.html b/docs/code/solutions/14_3_the_cats_hat.html new file mode 100644 index 000000000..92d7444a4 --- /dev/null +++ b/docs/code/solutions/14_3_the_cats_hat.html @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/docs/code/solutions/15_1_balloon.html b/docs/code/solutions/15_1_balloon.html new file mode 100644 index 000000000..8adfeb9a1 --- /dev/null +++ b/docs/code/solutions/15_1_balloon.html @@ -0,0 +1,29 @@ + + +

      🎈

      + + diff --git a/docs/code/solutions/15_2_mouse_trail.html b/docs/code/solutions/15_2_mouse_trail.html new file mode 100644 index 000000000..1e9e48855 --- /dev/null +++ b/docs/code/solutions/15_2_mouse_trail.html @@ -0,0 +1,33 @@ + + + + + + + diff --git a/docs/code/solutions/15_3_tabs.html b/docs/code/solutions/15_3_tabs.html new file mode 100644 index 000000000..04edba1d2 --- /dev/null +++ b/docs/code/solutions/15_3_tabs.html @@ -0,0 +1,33 @@ + + + +
      Tab one
      +
      Tab two
      +
      Tab three
      +
      + diff --git a/docs/code/solutions/16_1_game_over.html b/docs/code/solutions/16_1_game_over.html new file mode 100644 index 000000000..5b6579300 --- /dev/null +++ b/docs/code/solutions/16_1_game_over.html @@ -0,0 +1,29 @@ + + + + + + + + + + + diff --git a/docs/code/solutions/16_2_pausing_the_game.html b/docs/code/solutions/16_2_pausing_the_game.html new file mode 100644 index 000000000..756261f33 --- /dev/null +++ b/docs/code/solutions/16_2_pausing_the_game.html @@ -0,0 +1,93 @@ + + + + + + + + + + + diff --git a/docs/code/solutions/16_3_a_monster.html b/docs/code/solutions/16_3_a_monster.html new file mode 100644 index 000000000..a9ef810aa --- /dev/null +++ b/docs/code/solutions/16_3_a_monster.html @@ -0,0 +1,62 @@ + + + + + + + + + + + + + diff --git a/docs/code/solutions/17_1_shapes.html b/docs/code/solutions/17_1_shapes.html new file mode 100644 index 000000000..f5d6fb481 --- /dev/null +++ b/docs/code/solutions/17_1_shapes.html @@ -0,0 +1,66 @@ + + + + diff --git a/docs/code/solutions/17_2_the_pie_chart.html b/docs/code/solutions/17_2_the_pie_chart.html new file mode 100644 index 000000000..c3ac4e408 --- /dev/null +++ b/docs/code/solutions/17_2_the_pie_chart.html @@ -0,0 +1,40 @@ + + + + + + + diff --git a/docs/code/solutions/17_3_a_bouncing_ball.html b/docs/code/solutions/17_3_a_bouncing_ball.html new file mode 100644 index 000000000..afd954165 --- /dev/null +++ b/docs/code/solutions/17_3_a_bouncing_ball.html @@ -0,0 +1,36 @@ + + + + diff --git a/docs/code/solutions/18_1_content_negotiation.js b/docs/code/solutions/18_1_content_negotiation.js new file mode 100644 index 000000000..bd203e052 --- /dev/null +++ b/docs/code/solutions/18_1_content_negotiation.js @@ -0,0 +1,14 @@ +const url = "https://eloquentjavascript.net/author"; +const types = ["text/plain", + "text/html", + "application/json", + "application/rainbows+unicorns"]; + +async function showTypes() { + for (let type of types) { + let resp = await fetch(url, {headers: {accept: type}}); + console.log(`${type}: ${await resp.text()}\n`); + } +} + +showTypes(); diff --git a/docs/code/solutions/18_2_a_javascript_workbench.html b/docs/code/solutions/18_2_a_javascript_workbench.html new file mode 100644 index 000000000..5e087fd76 --- /dev/null +++ b/docs/code/solutions/18_2_a_javascript_workbench.html @@ -0,0 +1,18 @@ + + + + +
      
      +
      +
      diff --git a/docs/code/solutions/18_3_conways_game_of_life.html b/docs/code/solutions/18_3_conways_game_of_life.html
      new file mode 100644
      index 000000000..a32165ac5
      --- /dev/null
      +++ b/docs/code/solutions/18_3_conways_game_of_life.html
      @@ -0,0 +1,89 @@
      +
      +
      +
      + + + + diff --git a/docs/code/solutions/19_1_keyboard_bindings.html b/docs/code/solutions/19_1_keyboard_bindings.html new file mode 100644 index 000000000..0e4b84bb5 --- /dev/null +++ b/docs/code/solutions/19_1_keyboard_bindings.html @@ -0,0 +1,52 @@ + + + + + +
      + diff --git a/docs/code/solutions/19_2_efficient_drawing.html b/docs/code/solutions/19_2_efficient_drawing.html new file mode 100644 index 000000000..c0c8b2f5d --- /dev/null +++ b/docs/code/solutions/19_2_efficient_drawing.html @@ -0,0 +1,37 @@ + + + + + +
      + diff --git a/docs/code/solutions/19_3_circles.html b/docs/code/solutions/19_3_circles.html new file mode 100644 index 000000000..04d96bb19 --- /dev/null +++ b/docs/code/solutions/19_3_circles.html @@ -0,0 +1,34 @@ + + + + + +
      + diff --git a/docs/code/solutions/19_4_proper_lines.html b/docs/code/solutions/19_4_proper_lines.html new file mode 100644 index 000000000..4cb9e2a39 --- /dev/null +++ b/docs/code/solutions/19_4_proper_lines.html @@ -0,0 +1,49 @@ + + + + + +
      + diff --git a/docs/code/solutions/20_1_search_tool.js b/docs/code/solutions/20_1_search_tool.js new file mode 100644 index 000000000..36790d50b --- /dev/null +++ b/docs/code/solutions/20_1_search_tool.js @@ -0,0 +1,18 @@ +const {statSync, readdirSync, readFileSync} = require("fs"); + +let searchTerm = new RegExp(process.argv[2]); + +for (let arg of process.argv.slice(3)) { + search(arg); +} + +function search(file) { + let stats = statSync(file); + if (stats.isDirectory()) { + for (let f of readdirSync(file)) { + search(file + "/" + f); + } + } else if (searchTerm.test(readFileSync(file, "utf8"))) { + console.log(file); + } +} diff --git a/docs/code/solutions/20_2_directory_creation.js b/docs/code/solutions/20_2_directory_creation.js new file mode 100644 index 000000000..741bc127f --- /dev/null +++ b/docs/code/solutions/20_2_directory_creation.js @@ -0,0 +1,18 @@ +// This code won't work on its own, but is also included in the +// code/file_server.js file, which defines the whole system. + +const {mkdir} = require("fs").promises; + +methods.MKCOL = async function(request) { + let path = urlPath(request.url); + let stats; + try { + stats = await stat(path); + } catch (error) { + if (error.code != "ENOENT") throw error; + await mkdir(path); + return {status: 204}; + } + if (stats.isDirectory()) return {status: 204}; + else return {status: 400, body: "Not a directory"}; +}; diff --git a/docs/code/solutions/20_3_a_public_space_on_the_web.zip b/docs/code/solutions/20_3_a_public_space_on_the_web.zip new file mode 100644 index 0000000000000000000000000000000000000000..c51884ffe192aa48e2f4101ca32dc3cf6460961c GIT binary patch literal 1448 zcmWIWW@Zs#U|`^2aNZy0|7r2Y97aY4hGrnvWRPJnGKe>hPmC`pP0GnkjxR1qOiqo@ z&x=UYXpbT)12yP~!2!@Ai|-fKsyC``C9v$n_W-p!LARFy33 z*g{V#Z(@7=#plG`Lx;OpX}v6a#cMasu7dgZ3f@B|Ox7*>Z&K%8dD8jna%EGAi9p*! z&b8_Gat~T;E05{EFOdpY{r(TQF7>CH?vp2U!Wb4E+4E;o&lRQCEobJ0NY^f45obVk zDd&X{@^rG%9|6di?i=cUC`oSfqr$%`lK)>2hE`w0wQJ zz@no({Tz%J!{)W_{rn*^|-4vNa_Dh6ceJ^r} zFO|K+uh-mv>zPppci)_EOHHTTtL?fibZSP~j8$?cdA7a^x%}yJfchEbm|B*d+b`|S z>l0i#QT0m*;}=86y)&9_Os~%S{6hBXtMmZh;1ULGwq0_DQ^{D!6y8*iWUh%v3CC zlP%d`;K$Op{==%;=&!*Bt=H|3rhiFIJ}*#q%l}9G>F6?c{k>kFIAdo14G7h5S-baM z;qPd>1c$oYXBHf0UUl_nf&1m`x3-sWzUf|m*5!!Zqc;Ut_-`DNbw2-e@x$*kt{
      QOhhBD!nQ%So0vW+r*`s^VRMC>aAMq?*GYMbT@t;<4nyztO4GPOmfV) zN)QQP8fRbtrXz+WjUW~YcTCS>bUfVL8_4moQPXB}#`K(>yPiJ)~D WnFN=etZbmLVg|w>peGzyKs*3G_&y{6 literal 0 HcmV?d00001 diff --git a/docs/code/solutions/20_3_a_public_space_on_the_web/index.html b/docs/code/solutions/20_3_a_public_space_on_the_web/index.html new file mode 100644 index 000000000..e6ad51515 --- /dev/null +++ b/docs/code/solutions/20_3_a_public_space_on_the_web/index.html @@ -0,0 +1,17 @@ + + + +

      A Public Space on the Web

      + +

      This is a self-editing website. Select a file, edit it, and save to +update the website.

      + +

      Files:

      + +

      + +
      + +

      + + diff --git a/docs/code/solutions/20_3_a_public_space_on_the_web/other.html b/docs/code/solutions/20_3_a_public_space_on_the_web/other.html new file mode 100644 index 000000000..a9b89b115 --- /dev/null +++ b/docs/code/solutions/20_3_a_public_space_on_the_web/other.html @@ -0,0 +1,4 @@ + + + +

      This is another file

      diff --git a/docs/code/solutions/20_3_a_public_space_on_the_web/public_space.js b/docs/code/solutions/20_3_a_public_space_on_the_web/public_space.js new file mode 100644 index 000000000..ee321fc42 --- /dev/null +++ b/docs/code/solutions/20_3_a_public_space_on_the_web/public_space.js @@ -0,0 +1,31 @@ +// Get a reference to the DOM nodes we need +let filelist = document.querySelector("#filelist"); +let textarea = document.querySelector("#file"); + +// This loads the initial file list from the server +fetch("/").then(resp => resp.text()).then(files => { + for (let file of files.split("\n")) { + let option = document.createElement("option"); + option.textContent = file; + filelist.appendChild(option); + } + // Now that we have a list of files, make sure the textarea contains + // the currently selected one. + loadCurrentFile(); +}); + +// Fetch a file from the server and put it in the textarea. +function loadCurrentFile() { + fetch(filelist.value).then(resp => resp.text()).then(file => { + textarea.value = file; + }); +} + +filelist.addEventListener("change", loadCurrentFile); + +// Called by the button on the page. Makes a request to save the +// currently selected file. +function saveFile() { + fetch(filelist.value, {method: "PUT", + body: textarea.value}); +} diff --git a/docs/code/solutions/21_1_disk_persistence.js b/docs/code/solutions/21_1_disk_persistence.js new file mode 100644 index 000000000..8d7c36c6f --- /dev/null +++ b/docs/code/solutions/21_1_disk_persistence.js @@ -0,0 +1,30 @@ +// This isn't a stand-alone file, only a redefinition of a few +// fragments from skillsharing/skillsharing_server.js + +const {readFileSync, writeFile} = require("fs"); + +const fileName = "./talks.json"; + +function loadTalks() { + let json; + try { + json = JSON.parse(readFileSync(fileName, "utf8")); + } catch (e) { + json = {}; + } + return Object.assign(Object.create(null), json); +} + +SkillShareServer.prototype.updated = function() { + this.version++; + let response = this.talkResponse(); + this.waiting.forEach(resolve => resolve(response)); + this.waiting = []; + + writeFile(fileName, JSON.stringify(this.talks), e => { + if (e) throw e; + }); +}; + +// The line that starts the server must be changed to +new SkillShareServer(loadTalks()).start(8000); diff --git a/docs/code/solutions/21_2_comment_field_resets.js b/docs/code/solutions/21_2_comment_field_resets.js new file mode 100644 index 000000000..57bbf1338 --- /dev/null +++ b/docs/code/solutions/21_2_comment_field_resets.js @@ -0,0 +1,76 @@ +// This isn't a stand-alone file, only a redefinition of the main +// component from skillsharing/public/skillsharing_client.js + +class Talk { + constructor(talk, dispatch) { + this.comments = elt("div"); + this.dom = elt( + "section", {className: "talk"}, + elt("h2", null, talk.title, " ", elt("button", { + type: "button", + onclick: () => dispatch({type: "deleteTalk", + talk: talk.title}) + }, "Delete")), + elt("div", null, "by ", + elt("strong", null, talk.presenter)), + elt("p", null, talk.summary), + this.comments, + elt("form", { + onsubmit(event) { + event.preventDefault(); + let form = event.target; + dispatch({type: "newComment", + talk: talk.title, + message: form.elements.comment.value}); + form.reset(); + } + }, elt("input", {type: "text", name: "comment"}), " ", + elt("button", {type: "submit"}, "Add comment"))); + this.syncState(talk); + } + + syncState(talk) { + this.talk = talk; + this.comments.textContent = ""; + for (let comment of talk.comments) { + this.comments.appendChild(renderComment(comment)); + } + } +} + +class SkillShareApp { + constructor(state, dispatch) { + this.dispatch = dispatch; + this.talkDOM = elt("div", {className: "talks"}); + this.talkMap = Object.create(null); + this.dom = elt("div", null, + renderUserField(state.user, dispatch), + this.talkDOM, + renderTalkForm(dispatch)); + this.syncState(state); + } + + syncState(state) { + if (state.talks == this.talks) return; + this.talks = state.talks; + + for (let talk of state.talks) { + let cmp = this.talkMap[talk.title]; + if (cmp && cmp.talk.author == talk.author && + cmp.talk.summary == talk.summary) { + cmp.syncState(talk); + } else { + if (cmp) cmp.dom.remove(); + cmp = new Talk(talk, this.dispatch); + this.talkMap[talk.title] = cmp; + this.talkDOM.appendChild(cmp.dom); + } + } + for (let title of Object.keys(this.talkMap)) { + if (!state.talks.some(talk => talk.title == title)) { + this.talkMap[title].dom.remove(); + delete this.talkMap[title]; + } + } + } +} diff --git a/docs/code/solutions/22_1_pathfinding.js b/docs/code/solutions/22_1_pathfinding.js new file mode 100644 index 000000000..b9907a859 --- /dev/null +++ b/docs/code/solutions/22_1_pathfinding.js @@ -0,0 +1,21 @@ +function findPath(a, b) { + let work = [[a]]; + for (let path of work) { + let end = path[path.length - 1]; + if (end == b) return path; + for (let next of end.edges) { + if (!work.some(path => path[path.length - 1] == next)) { + work.push(path.concat([next])); + } + } + } +} + +let graph = treeGraph(4, 4); +let root = graph[0], leaf = graph[graph.length - 1]; +console.log(findPath(root, leaf).length); +// → 4 + +leaf.connect(root); +console.log(findPath(root, leaf).length); +// → 2 diff --git a/docs/code/solutions/22_2_timing.js b/docs/code/solutions/22_2_timing.js new file mode 100644 index 000000000..d37c83806 --- /dev/null +++ b/docs/code/solutions/22_2_timing.js @@ -0,0 +1,20 @@ +function findPath(a, b) { + let work = [[a]]; + for (let path of work) { + let end = path[path.length - 1]; + if (end == b) return path; + for (let next of end.edges) { + if (!work.some(path => path[path.length - 1] == next)) { + work.push(path.concat([next])); + } + } + } +} + +function time(findPath) { + let graph = treeGraph(6, 6); + let startTime = Date.now(); + let result = findPath(graph[0], graph[graph.length - 1]); + console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`); +} +time(findPath); diff --git a/docs/code/solutions/22_3_optimizing.js b/docs/code/solutions/22_3_optimizing.js new file mode 100644 index 000000000..dff729050 --- /dev/null +++ b/docs/code/solutions/22_3_optimizing.js @@ -0,0 +1,45 @@ +function time(findPath) { + let graph = treeGraph(6, 6); + let startTime = Date.now(); + let result = findPath(graph[0], graph[graph.length - 1]); + console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`); +} + +function findPath_set(a, b) { + let work = [[a]]; + let reached = new Set([a]); + for (let path of work) { + let end = path[path.length - 1]; + if (end == b) return path; + for (let next of end.edges) { + if (!reached.has(next)) { + reached.add(next); + work.push(path.concat([next])); + } + } + } +} + +time(findPath_set); + +function pathToArray(path) { + let result = []; + for (; path; path = path.via) result.unshift(path.at); + return result; +} + +function findPath_list(a, b) { + let work = [{at: a, via: null}]; + let reached = new Set([a]); + for (let path of work) { + if (path.at == b) return pathToArray(path); + for (let next of path.at.edges) { + if (!reached.has(next)) { + reached.add(next); + work.push({at: next, via: path}); + } + } + } +} + +time(findPath_list); diff --git a/docs/code/squareworker.js b/docs/code/squareworker.js new file mode 100644 index 000000000..58a5bed9e --- /dev/null +++ b/docs/code/squareworker.js @@ -0,0 +1,3 @@ +addEventListener("message", event => { + postMessage(event.data * event.data); +}); diff --git a/docs/css/ejs.css b/docs/css/ejs.css new file mode 100644 index 000000000..7b3dfca8e --- /dev/null +++ b/docs/css/ejs.css @@ -0,0 +1,461 @@ +@font-face { + font-family: 'Cinzel'; + font-style: normal; + font-weight: 700; + src: local('Cinzel-Bold'), url(../font/cinzel_bold.woff) format('woff'); +} + +@font-face { + font-family: 'PT Mono'; + font-style: normal; + font-weight: 400; + src: local('PT Mono'), local('PTMono-Regular'), url(../font/pt_mono.woff) format('woff'); +} + +html, body { + padding: 0; + margin: 0; +} + +body { + font-family: Georgia, 'Nimbus Roman No9 L', 'Century Schoolbook L', serif; + font-size: 20px; + line-height: 1.45; + color: black; + background: white; +} + +article { + margin: 0 auto; + max-width: 35em; + padding: 2em 1em 5em; + position: relative; + overflow-wrap: break-word; +} + +nav { + display: block; + height: 0; + text-align: right; +} + +nav a { + font-size: 80%; + color: #aaa !important; + text-decoration: none !important; +} + +a.subtlelink { + color: black !important; + text-decoration: none !important; +} + +pre { + padding: 5px 0 5px 15px; + line-height: 1.35; + margin: 1rem 0; + max-width: 100%; + overflow-x: auto; +} + +pre[data-language=javascript] { + cursor: pointer; +} + +p:hover a.p_ident:after, pre:hover a.c_ident:after, h2:hover a.h_ident:after, h3:hover a.i_ident:after { + content: "¶"; + font-family: 'Cinzel', Georgia, serif; + color: #888; + font-size: 17px; + position: absolute; + right: -10px; +} + +@media screen and (max-width: 800px) { + p:hover a.p_ident:after, pre:hover a.c_ident:after, h2:hover a.h_ident:after, h3:hover a.i_ident:after { + right: 5px; + } + + blockquote p:hover a.p_ident:after { + right: -15px; + } +} + + +code, pre, .CodeMirror { + font-size: 18px; + font-family: 'PT Mono', monospace; +} + +code { + padding: 0 2px; +} + +h1, h2, h3 { + font-family: 'Cinzel', Georgia, serif; + font-weight: 700; + margin: 1rem 0; + letter-spacing: 2px; +} + +h1 { + font-size: 130%; +} +h2 { + font-size: 115%; +} +h3 { + font-size: 100%; +} + +pre.cm-s-default, p, h2, h3 { + margin-right: -30px; + padding-right: 30px; +} + +@media screen and (max-width: 800px) { + pre.cm-s-default, p, h2, h3 { + margin-right: 0; + padding-right: 0; + } +} + +a, a:visited, a:active { + text-decoration: none; + color: #467; +} + +a:hover { + text-decoration: underline; +} + +ol { + margin: 1em 0; + padding: 0; + counter-reset: li; +} + +ol li { + margin: 0 0 0 40px; + padding: 0; + list-style: none; + position: relative; +} + +ol li:before { + content: counter(li) "."; + counter-increment: li; + position: absolute; + width: 2em; + text-align: right; + left: -2.5em; top: 1px; + font-size: 90%; +} + +ol li p { + margin: 0; +} + +.chap_num { + font-size: 60%; + color: #aaa; + margin-top: -.7em; + display: block; +} + +blockquote { + margin: 0 0 0 3em; + padding: 0; + position: relative; + font-size: 85%; +} + +blockquote p { + color: #333; +} + +blockquote:before { + content: '“'; + position: absolute; + left: -.5em; +} + +blockquote p:last-of-type:after { + content: '”'; +} + +blockquote footer { + position: relative; + margin-left: 1em; +} + +p + footer { + margin-top: -.5em; +} + +blockquote footer cite { + font-style: italic; +} + +blockquote footer:before { + content: '—'; + position: absolute; + left: -1em; +} + +.editor-wrap { + margin: 1rem 0; + position: relative; + -moz-transition: margin-left .5s ease-out, margin-right .5s ease-out; + -webkit-transition: margin-left .5s ease-out, margin-right .5s ease-out; + -o-transition: margin-left .5s ease-out, margin-right .5s ease-out; + transition: margin-left .5s ease-out, margin-right .5s ease-out; + border-bottom: 1px solid #4ab; +} + +.sandbox-output { + border-top: 1px solid #4ab; + padding: 4px 0 4px 10px; + white-space: pre; + max-height: 25em; + overflow: auto; +} + +.sandbox-output:empty { + display: none; +} + +.editor-wrap iframe { + display: block; + border: 1px dotted #4ab; + border-top: 1px solid #4ab; + border-bottom-width: 0; + padding: 0; margin: 0; + width: 100%; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.sandbox-output pre { + margin: 0; + padding: 0; + white-space: pre-wrap; +} + +.sandbox-output-error { color: red; } +.sandbox-output-warn { color: orange; } + +.sandbox-output-etc { + color: #1a1; + background: #dfd; + cursor: pointer; + border-radius: 5px; + padding: 0 1px; +} + +.sandbox-output-prop { + color: #444; +} + +.sandbox-output-etc-block { + display: inline-block; + vertical-align: top; +} + +.sandbox-output-etc-block table { + border-collapse: collapse; +} +.sandbox-output-etc-block table td { + vertical-align: top; + white-space: pre-wrap; + font-family: 'PT Mono', monospace; +} +.sandbox-output-etc-block table td:first-child { + text-align: right; +} + +.sandbox-menu { + position: absolute; + z-index: 19; + right: -13px; top: -1px; + cursor: pointer; + font-size: 80%; + overflow: hidden; + width: 10px; + border-top: 2px solid #4ab; + height: 2px; + border-bottom: 6px double #4ab; +} + +.sandbox-open-menu { + font-family: tahoma, arial, sans-serif; + position: absolute; + background: white; + border: 1px solid #aaa; + box-shadow: 2px 2px 10px rgba(0, 0, 0, .2); + padding: 0; + font-size: 75%; + color: black; + line-height: 1.3; + right: -9px; top: 5px; + z-index: 20; +} + +.sandbox-open-menu div { + cursor: pointer; + padding: 5px 10px; +} + +.sandbox-open-menu div:hover { + background: #bdd; +} + +/* Toned-down CodeMirror style */ + +.cm-s-default .cm-keyword, .sandbox-output-null, .sandbox-output-fun {color: #506;} +.cm-s-default .cm-atom, .sandbox-output-bool {color: #106;} +.cm-s-default .cm-number, .sandbox-output-number {color: #042;} +.cm-s-default .cm-def {color: #009;} +.cm-s-default .cm-variable-2, .cm-s-default .cm-attribute {color: #027;} +.cm-s-default .cm-variable-3 {color: #072;} +.cm-s-default .cm-comment {color: #740;} +.cm-s-default .cm-string, .sandbox-output-string {color: #700;} +.cm-s-default .cm-string-2 {color: #740;} +.cm-s-default .cm-tag, .sandbox-output-symbol {color: #170;} + +.CodeMirror { + height: auto; + line-height: 1.35; + border-top: 1px solid #4ab; + overflow-wrap: normal; +} +.CodeMirror-scroll { + max-height: 700px; +} +.CodeMirror pre { + padding: 0 4px 0 10px; +} +.CodeMirror-gutters { + border: none; + background: white; +} +.CodeMirror-linenumber { + padding: .5em 3px 0 0; + min-width: 12px; + color: #4ab; + font-size: 60%; +} + +.sandboxhint { + position: absolute; + right: -15px; + font-family: tahoma, arial, sans-serif; + font-size: 70%; + padding: 4px 8px; + background: rgb(220, 220, 220); + color: white; + border-radius: 5px; +} + +@media screen and (max-width: 800px) { + .sandboxhint { + right: 5px; + } +} + +.sandboxhint:before { + position: absolute; + width: 0; height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 12px solid rgb(220, 220, 220); + top: 6px; + left: -11px; + content: ''; +} + + +figure { + max-width: 640px; + margin: 0 30px; +} + +figure.chapter { + text-align: center; + margin: 3em 0 2em; +} + +figure.chapter img { + max-width: 75%; +} + +figure.framed img { + border-radius: 50%; + border: 4px double #666; +} + +figure.square-framed img { + border-radius: 30px; + border: 4px double #666; +} + +span.keyname { font-variant: small-caps } + +@media screen and (max-width: 500px) { + figure { + margin: 0; + } +} + +figure img { + max-width: 100%; +} + +div.solution:before { + content: "» Display hints..."; +} + +div.solution { + color: #156; + cursor: pointer; +} + +div.solution-text { + display: none; +} + +div.solution.open:before { + content: ""; +} + +div.solution.open { + cursor: default; +} + +div.solution.open div.solution-text { + display: block; +} + +td { + vertical-align: top; +} + +td + td { + padding-left: 1em; +} + +table { + margin-left: 15px; +} + +sub, sup { + line-height: 1; +} + +sub { + font-size: 60%; +} + +sup { + font-size: 70%; +} diff --git a/docs/css/game.css b/docs/css/game.css new file mode 100644 index 000000000..bb777788a --- /dev/null +++ b/docs/css/game.css @@ -0,0 +1,24 @@ +.background { background: rgb(52, 166, 251); + table-layout: fixed; + border-spacing: 0; } +.background td { padding: 0; } +.lava { background: rgb(255, 100, 100); } +.wall { background: white; } + +.actor { position: absolute; } +.coin { background: rgb(241, 229, 89); } +.player { background: rgb(64, 64, 64); } + +.game { + overflow: hidden; + max-width: 600px; + max-height: 450px; + position: relative; +} + +.lost .player { + background: rgb(160, 64, 64); +} +.won .player { + box-shadow: -4px -7px 8px white, 4px -7px 8px white; +} diff --git a/docs/css/paint.css b/docs/css/paint.css new file mode 100644 index 000000000..159faddf4 --- /dev/null +++ b/docs/css/paint.css @@ -0,0 +1,13 @@ +.picturepanel { + width: -webkit-fit-content; + width: -moz-fit-content; + width: -ms-fit-content; + width: fit-content; + max-width: 500px; + max-height: 300px; + border: 2px solid silver; + overflow: auto; + position: relative; +} +.picturepanel canvas { display: block; } +.toolbar > * { margin-right: 5px; } diff --git a/docs/empty.html b/docs/empty.html new file mode 100644 index 000000000..c2a57ea02 --- /dev/null +++ b/docs/empty.html @@ -0,0 +1 @@ + diff --git a/docs/errata.html b/docs/errata.html new file mode 100644 index 000000000..6bb107bef --- /dev/null +++ b/docs/errata.html @@ -0,0 +1,98 @@ + + + + + Eloquent JavaScript :: Errata + + + + + +
      + +

      Errata
      Eloquent JavaScript, 3rd Edition

      + +

      These are the known mistakes in the third edition +of the book. For errata in the first edition, +see this +page. For the second edition, +see this +page. To report a problem that is not listed +here, send me an email.

      + +

      Issues whose page number is followed by an ordinal number are only +present up to the print denoted by that number. I.e. those followed by +“1st” were fixed in the second print.

      + +

      Chapter 2

      + +

      Page 34 (1st) Updating Bindings Succintly: Where it +says counter- it should be counter--.

      + +

      Chapter 5

      + +

      Page 91 Composability: Due to an initial +mistake in the script data set, the results of the computations on +this page differ from the ones with the current, corrected data. The +average year for living scripts should be 1165, the average for +non-living scripts should be 204.

      + +

      Chapter 6

      + +

      Page 111 (2nd) Inheritance: In the second +paragraph below the example code, instead of “content +method”, the text should say “element function”. + +

      Chapter 8

      + +

      Page 134 (2nd) Error Propagation: In the third +paragraph of the section, a function promptInteger is +referred to. The function is actually +called promptNumber, and the word “whole” should be +dropped from the sentence (it accepts non-whole numbers, too).

      + +

      Chapter 10

      + +

      Page 168 (1st) Modules as Building Blocks: In “each +needs it own private scope“, it should say “its own private +scope“.

      + +

      Chapter 14

      + +

      Page 234 (2nd) Creating Nodes: In the +code, “edition” is misspelled as “editon”.

      + +

      Chapter 15

      + +

      Page 258 Load Event: The description of +the beforeunload claims that you just need to return a +string from your event handler. For handlers registered +with addEventListener you, in fact, need to +call preventDefault and set a returnValue +property to get the warn-on-leave behavior.

      + +

      Chapter 16

      + +

      Page 285 (2nd) Pausing the Game: The text +refers to the arrow binding, where it should +say arrowKeys.

      + +

      Chapter 20

      + +

      Page 369 (1st) Directory +Creation: MKCOL stands for “make collection”, not “make +column” as the book claims.

      + +

      Chapter 21

      + +

      Page 373 HTTP Interface: There is a +superfluous closing brace at the end of the example JSON snippet.

      + +

      Exercise Hints

      + +

      Page 414 A Modular Robot: +The dijkstrajs package name is misspelled +as dijkstajs. + +

      diff --git a/docs/example/bert.json b/docs/example/bert.json new file mode 100644 index 000000000..c943c0942 --- /dev/null +++ b/docs/example/bert.json @@ -0,0 +1,4 @@ +{ + "name": "Bert", + "spouse": "example/suzie.json" +} diff --git a/docs/example/data.txt b/docs/example/data.txt new file mode 100644 index 000000000..2a5a69639 --- /dev/null +++ b/docs/example/data.txt @@ -0,0 +1 @@ +This is the content of data.txt diff --git a/docs/example/fruit.json b/docs/example/fruit.json new file mode 100644 index 000000000..b54ea36b7 --- /dev/null +++ b/docs/example/fruit.json @@ -0,0 +1,3 @@ +{"banana": "yellow", + "lemon": "yellow", + "cherry": "red"} diff --git a/docs/example/fruit.xml b/docs/example/fruit.xml new file mode 100644 index 000000000..d932c647a --- /dev/null +++ b/docs/example/fruit.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/example/message.html b/docs/example/message.html new file mode 100644 index 000000000..cf92826f7 --- /dev/null +++ b/docs/example/message.html @@ -0,0 +1,35 @@ + +Example form target + + + + + + +

      (Note that this page is just an illustration. No actual message was delivered anywhere.)

      + + + + diff --git a/docs/example/muriel.json b/docs/example/muriel.json new file mode 100644 index 000000000..921f40d45 --- /dev/null +++ b/docs/example/muriel.json @@ -0,0 +1,3 @@ +{ + "name": "Muriel" +} diff --git a/docs/example/submit.html b/docs/example/submit.html new file mode 100644 index 000000000..6607f2275 --- /dev/null +++ b/docs/example/submit.html @@ -0,0 +1,22 @@ + +Example form target + + + +

      You submitted...

      + +
      
      +
      +
      +
      +
      diff --git a/docs/example/suzie.json b/docs/example/suzie.json
      new file mode 100644
      index 000000000..1e47fac94
      --- /dev/null
      +++ b/docs/example/suzie.json
      @@ -0,0 +1,5 @@
      +{
      +  "name": "Suzie",
      +  "spouse": "example/bert.json",
      +  "mother": "example/muriel.json"
      +}
      diff --git a/docs/favicon.ico b/docs/favicon.ico
      new file mode 100644
      index 0000000000000000000000000000000000000000..382b706926693fa007bfef3425deec7a3e2f265f
      GIT binary patch
      literal 1406
      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
      ioKjy@tG?8}^JEySv6`q>eW(rXdrN%-I
      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
fP
      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ⓈAG(`XLk_xti$97KqkN|EF&
      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ⅈO7BBkW?>C?n<%zu2>Z?m$#jLPcK&+5u5
      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 BnDki&QyBIXKqxj|j_`l;P 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 M07*qoM6N<$f@STvga7~l literal 0 HcmV?d00001 diff --git a/docs/img/blockquote.png b/docs/img/blockquote.png new file mode 100644 index 0000000000000000000000000000000000000000..a085592514aa84db7d056a2cdb230d91379bf4de GIT binary patch literal 15042 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 eC@*FG_o-cW80NzuA$EKLPG8OZkXO zwQ33?e)COG^VQVQqH3y3Y99K>_dWMK&pGeeYp=7{+Rxd0z3)!6voRMve(E>?0HP=h zQwIP5MeyEhg!y<+jSaOT9*6{6xP$?KSo_fpdafZR%QGIsqpXp~CWU0hQ~|@802A-ZzXtOp zM@C1pxlL4RTil6+KNy?#lBmg$_7ee|8xBhFnu)-OZ9lm#?v5cU9Wl=rzt+G$IEb;A zMB2-FYkbiRkvlVX7b0xCijJKKxgf_NBTUu`VNLw0RB0xw7f_E#qhH;?!>&v}Z zGJI=m%O$HOKtq(m;c#@VAHPnFb+(m4reSm52fZJytE;OXC(}ezSl1LcwxC_vX_#n#?x5?TYn!bqzgZlVpd z7gi&mj(+T!l3CgQHds3J|P zoQLZ%al_R(ptc@8M&Rza22S%PfWsuBTF!bH6`nlF#HQFOM;3I&?EU0kUKqB!0)lae z&Z&bD4CHD<724ST$bc;BSsyl;$Fe6N}0;t;j;1-6DmVgSXJ)0?zEMCq-pc zs)Fz$y};%_T~{>a-9N#ujcxSyT4TmMAftq*9sBLRyIPhOi6VAzbx$_0gH!7Px#5T` zh|5gv4B6k`e_b;fSocs8Wz# z@2q34i$d!6lXPhTnOEKKjUvHfm{Uq3}OQ z6sk{UdAX?EjmC93U(Le4Q^}S9$%N+A1_T8LlHpjF&JeWtd6OF-wi#2pq5;~F2v}G| zgbx{&E|B~-Nk1h+DJ?Oe2z0g&J4;%Kyp0*Vc~{X_Du82V|9)x}rQ^}wK>nl}6cqFp z^2@-@smaOBHUTXoic95H=g1K+-xiyqU@i3mwUu)tA0}`lt>s^3K6todb2rVDwpvOs52| zO3Z$zD2WWpU1Y4PqOKb7>(R;A*V*v~@nA(5|EMF{$;s)}c#4#@d8DQRGbA*JO3ife zfZTxxN?MzUtCbMlPdd+PE1I94{|bJ0%sTKiAU&t;|EwJg#CA8vK(qD@gp%W1X|AQT zyPikegskIR`9MLd^FuC3!<7`8SR^t%+dwYbSd!CznKuo(QBM|@po9!!%d(lrDdQ{Q z4m~FoD5;?5t%4A8-F&v!$b}_SdAT4nY5#7lQn6TD>bLsGU=8HSym4N+vsBlU)=vai zfZ@z>R}#+uv4^&7H`ZOa8#~R0wcUZvhe3C>gS{yrfQbcw|A9L&>Aa_*$_oDUCZij~ z%+u!g35QDmdD&=|d;!dg6K++G5@m)&a=#^G^>;JBheG?glF1+Bkt9bF z#2tcZ{;Zu>_EpRO%>B=VBUhVW{zq3wJmnS)rMHM7wq!_SBeJlzfhw^!lP6kDw8>y- z3W)#_Bal85HUeSQQ>%&jQQcMPEM_YGOL-7Q@a!*bON%R)R9w{Ao*a3EkghP_RS;rv zF@TbG8xEU!b&9xu3H-XE4r3M@-{aqv3hKbRyJKQ9S<~7!)cYOUT2-{kaA;22y?gf* z6JWpb|D~(PbFbL?_dcm7FZ?5!vHY@YGBDUaf_4?t?&yLLxj<~s! zM!_)t;B&laVwF%IVr*>nVZ(Ghc)PGNrtlrpTqf1H<#5jA@k|D>qr>HDmZht<4qTsyZ8lu(%tkp(A* z))d=#3sIWiXJEvw@4$NKx;h~)&KBL33@!vKGyma5U3$fz9B(da75jOazPqObZm;+T zdL{|g{}rlVOfy>2{&~1}Ax-|Qc5pp7&)%spG)F=BV{OSXLxXk0Ada3epQ}(`c?HF` zYTQ55;pUPIL~78Ir<#gOJQRJ6iSggo!UKePX3+;(6$k(J;m0TAW zc*v(YFd}oVwC#1z*1<|Rt51dF%h~+;wG)DH{AKgo;+RvMLduL1iVPpNmJa>51cIXQ z8*{E^1lmDJmgPIq zHlA#u+2s)}u!!K_r4_4@W$uY zr5lf3CX~c(bF0VE>QYGiF#?$V4<4gx3(D{HHsxO)e!jai^5Ny%Km9xBT5n>?3tI$5 zbKg>W0o9S`24Ar1C8>MvnOJpr*QeG{Ou1)sny&9RffIMNKI*Qjt!}u%@EY#YG{HXu|_<982@EIYw!cUa@H?Wk?!rSe*tbZoLCw zHllrOH+{mLk@oL7D@AJOSn0h{<{}GtgUTfX;RHHOM4fEm)MB$%%2<^_{x)_c-)6in z##H@?Jel2i!XO%Lsakol+QxWc-|AM%RS`3)24OH_^(kp#CCJL;yOuMU?BujKeC7io zA06bW>sAtkQ${6pFo}c`(oTn0>uJ_!W7`(ogOUiXa7=ltQTgw=JJEglJ2T6+;q7Y3Vcf3K}@N#u3gRVns6Vk_5h-6$C0mVU9u#eMT| zjQPfo5Irk2QyEhpc5oQ8@u~*KT2C*r%PM*8`qGk5l~B^RJ4+(>Huq}2ZRIetcj#9`ms9`oY=E~Vns0L_04G9;fBlnEoY&msBT2iMbF8Gx z^#U06iaU}5_uJJ{JXM>&{?qg2NPr=Z!%Ut2swJMMSgF61cj zM4rl#zxQ<1pr5<8sL;Iuym1a|F1Z8L*Oc_HDw13uMg?EWNq+%gnRSt znG3=9=?JDEX@6LNFI6KTZ2sA~CzyI|iz3<-cxN z*B(fxvA9icuM|+r{(4bF;F8J!5VcKxqF{ZkZ%zC(#q~q?Vi-%_(3+gZ{E`NEn^p_yzlIe0OZK*b3PCBIhcgn2A-SdK{`uO5@ z!ZDaXHI8AvT2H|{nn=X*RtnV*yS4FFt)fMDpq ja71Oi77wNqwl8SUc~t2yd|%|9*8mjK#*}u|J>mZW)-1|| literal 0 HcmV?d00001 diff --git a/docs/img/canvas_fill.png b/docs/img/canvas_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..09965aecc4144933bb1329ca782ef9cc02a851de GIT binary patch literal 5551 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 literal 0 HcmV?d00001 diff --git a/docs/img/canvas_game.png b/docs/img/canvas_game.png new file mode 100644 index 0000000000000000000000000000000000000000..611ac8d36905482854d666eb5dfcdc1ef0399b52 GIT binary patch literal 8382 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 literal 0 HcmV?d00001 diff --git a/docs/img/canvas_path.png b/docs/img/canvas_path.png new file mode 100644 index 0000000000000000000000000000000000000000..14231f2c752fc1f4dcbaeb253874fed6c687227e GIT binary patch literal 470 zcmeAS@N?(olHy`uVBq!ia0vp^OMrM82NRIIEx%b7NO2Z;L>4nJ@ErkR#;MwT(m+A> z5>H=O_Iqs7d^$RB&);YR3Q3l@MwB?`=jNv7l`uFLr6!i7rYMwWmSiZnd-?{1H}Z)y zFfayqx;TbZ+q-+L3);Sl1axN6*T(|?( z1JMa2VMflDHOt^M%dj-d$b;&EY2F4i@+_b978dC(GSVjIs4fwVhMAhy&KuRl8`Z}< zYc{G&I&@*CZa&PWdyq}{INRB?Fb_Z-BL*||W@7WSgyw0<&Ck+c9)LQg8)oX84UXO$ i98Yg_EZ+QcvAFPDD|60=FT#Kkz~JfX=d#Wzp$PzL54QmT literal 0 HcmV?d00001 diff --git a/docs/img/canvas_pie_chart.png b/docs/img/canvas_pie_chart.png new file mode 100644 index 0000000000000000000000000000000000000000..022289cc2360a384c996e7e0852cffc334bb7104 GIT binary patch literal 4052 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 D2)mc; literal 0 HcmV?d00001 diff --git a/docs/img/canvas_quadraticcurve.png b/docs/img/canvas_quadraticcurve.png new file mode 100644 index 0000000000000000000000000000000000000000..121cd4a643fa04c7a71927403534a45eb6dbdb99 GIT binary patch literal 2642 zcmYk8dpOkDAIHBl!x)+o88u_1Cf8+XB;TFf7UPoLShiY1jCI$rVcbfZL6gf6Yu#@# z<(4#5Y_>EaB@DB&zUiihklRm#a{rC`XaD$o&gcDl&gXs3bDr}&=cGD2*<<8XX`wS%r z&#-l4P=5~%^$%kJ)ChkchQHtbm|#ZGetQQ;XIGZ|9*I}4gN@aR*pb=N>DRU`HkvJrzaMM zvRD7|G=L|DEvEhiwBr>@VE;|@Tnz`T?fRQxnWL^hWjygE+T{7C;0LzSDs}qBX8k!D zETgLzpNZoicnwZ(B%uliI@T>j*sgF-AB7xyJASSh^P_zo5^9F<;a3F5P&|1MQ{6kekJeOb+LSPK=z z>+oY2``uU=sU##I%BHX|og6JmAt9Yf0g?h@Vkq1r{8$Y&8sfz@N#S+bz1&$rSp~td ze-8skIllr)hL9x~EKQ~)VLI91SJ@l%RVH7~1CsP0rV$oE=Sli)VEj>8P(fn^u`LD8 z2=W&e#!tx@NFqX(HO}yD*@^xp?>I}73s@KmRg*s|1+vQ=pTp6=(hvZZxUG@190LGJ z;2FN6|D(tsMSuW#aXsc=RtBq!GY25|@nFmf$7ogt(S(ZN(*O{e@3;t{<+uB*NpM0( z1lx(}V+!G%K_Qx4FGB!uMgBV=+>gBjJHN?Qe`yGpY;;Zvx15OCd~wa2>0<~1d;r4z z1{%U>ZgLBk#Z%Ql?G+#Hv4ppX)-nSXf(joi#Jb5~~S1SOHUgIMHB)@BaQ))NR zVZdwGEyl@{NeIc30HN2Hy#jO`lI#}7YrhA-i=shrkm9HGH^4UjYMc}ZHY(EGVBm~E zD-Vt~{py8mDeyVS=OO3-4WRStEb#!kqn(}mB3Xt{-uo4jyR}LHb~mk~iT(&p!(k-H z1Az5c@ax-9B#O8PMLg&dg_lj-7}8Cv%ych%cyTMwKhC23lEQ@e9_gnQ&D1pSx1Y`X z#TUxu5;ODKZ+Sm7>*pq7UtE)6W@~gu+|vmA*RpIvKfi~PS;)ee{$^J;AzCOfP`Wv? zoIA4+%Mno={%9gi6=#aRw9U6R%&%c_p2NA`&#oVPHx_R(5w|H8N9MwJr$3IjaQ)Q4 z)p5TyY^AW6a~vs6POF6GKM$GeCl?&k^1)RFkIckW1*@>%`5mgY%btyX*;YHZhwe6t z)&$ghnsn0YGUnnf-p)qLrEigXz+%jGpq1M*-Zbtwt4J@H<5cQQMuTl8!9}~&_B&P& zcDx&ydK*ts_4EJO8iuvicYRC;lZ;NlIi#MtGE|~vpmBax?c(K$xV{-k9U_n5?`Xar zZN=IWO+B83KO)cUz?V8{7kUTWK9w|PeTKTlV+jR8Agl51yuZ1k6iZg@QrySRYnyJJ zM_y^u0(+zE5X|hV+8NWT!CIG{*LBjYwen;12EF*~3PINBt>&SIG zoQW2#wzJ9qs~eKS*}Ax4b;Fd|6(m!%6rt+~SBrmi@K5v@B~!oRy>O$-W$z%LeM(Rx zOKyI7lWs{bUokCpV)S?yQH>yS5B?y`&IP;c4BIExr{b{Vbip=#x=r)^PqaA zGjClB*jGp;K$CCr{V+D23Oz4AXe+g$NE||ft1(?lOyWz0fTe)GGZDk8BYeAlK`fm7 zbocs5R?&g@xwXwu_7DO*7u}`KB!;#(VQLHgf7MJ4)XtED~ z1whinTTb39r1>Upg`>F+cpPoIqJQs!p`OE(AQ9B)MsR!dx@aS`Z16k&Sr zIM&_eH4Eb?sM)@EwDjdY$6G7ZdBjD?6OLXXX0OC&o`2;whAcWYg`!WMwNs`IbfC3! zWJML`eYPITm)?d6QY#-vi(fD&tDU+pE9=1sx~1V!=XSbP z2RJ;dm!j7&?3RwH@B|4RcISD--ovEln+A70OF~MBFyz&|BR*VB$XUeEAiwWDKRFo#gnjuvd+oI`#&Zz!6cPG8OburBrSJG$zX zCru>NZmqSAvIDz5rDO&sZDm?CDLu5S?IlN6^WRl_m4~1rCetlO{B(nGEi!`ufL#BAO^cx=5q@ z^XC=?YqdsnV=Xnf?_oW4-ZTkV3%Of)`Igmsvx{ItEWWsWHbOhoHzZA+Nwu>zrjUj5x6oFhe1 z{P*8~wcnIwxu=M;EMprwjuS=EoL>Ykm`@s=f3ZcMk6*C#)m73-qn0vP2WdrfOIDe!~I_z1i_&dR+eQ^6#KtE&-07e z&eeLNT1lASQ%qodI5wuWUGi z=85?psT(jpv^f6d7?wP4ba0$VUJG&6^1tfkw&yM3n1R6TC$+Jpng*I@Uk+L{$j)Qx z28INTkJG<7;n{ltH`cBy8+|iKm_fokH%|^%MwkJB@c`pr1Jb>fU?q>e=X5jYuiABO zmfHZm&}<%>n9_UEV1kJ3#`4(ej1FM@BbmL&X1Zy!)N&9uhjShkn)Pds<3V){U?jl! zheGz+LzrFm*P7FB{Cp-{AGHRi`LVCLOtq1KnMD|XUt~Uv?3Y=V_0^3Lvo{k805r2y zFC&3;hoUHwEXBpo7~jeBT;hgNJZ1;D;*r1%08&ZcW>e@Uj4u-pvMdwDcMtDj7|O0= zq6CL1z;Q+bIez6p?l_@Hj$au7qA0qB`9jxe{5;QPhcFS|`lF;C6uBG4bKn7-3Dp5W z9$PKLSHlzma=c$(9dHfCm!ZBWitf#K6W-0R&k+T1iKRfbXWOMCWa{`J2z=ka^uTwK z#xIIuv)O2^vn=Zp;pO|Q4(S{!09Q%1=`27?_ZoWF2nfM6yHVhVHg^} zlivYuybds9fOQdI27pVc0M6I=(u)_x+cj!rX0Ci0VtMv*0N7mwh;!_&9g|jpIOjai z0i2QXrQIf5+E@3@jF2t08SGB#r}3@fP)~2<5=u}wZ_+4 z=XtJ_l8I9mSw8v)mIr{dvVWFk7TI5>fX8thhT(Cvc5vetMe+XrZr!w*PNy;$VtD{K zfBP$?R;!g=@Qgham;Q602hZeGn7&m!Qb<|IF1jb z{+^AmwSIeh>mvFjNjL!u0M|iL6wBqZi~1iQAA9U@^ke+8ELW>lnx+=58`pJ{BuSEl zz6}6eD_ZO2a=BbCEz}x00N^$$%Tg(orm4mJ&M1l`@*m0g z@9*!6#loUfKb=nJ^Esg&0Ki24G)*n0_jsQ7`uf^>dduH7tk-KhU(aTHk6Z008Gn5CmD44Tr;4?<&jk z?(Xi}6%fA3_+?p+$772HjXys>(LDe_?@50$nY4YE=Xsx>pS7O;HyQuo;lV=pudlD@ z9suAFN&orzx$V1TO8qYxUu!)ckK2B<>$-L~9|r({Ju|z-Ds0>BwmLt)p7GN(wfIw# zB;or205}|Wnyo3lZhX5tRk2hK0Kh@9(`-%Y^{&ofFtGU0liVI10N|$R?&m*jL;!#r zp>F&z3@tvzVi^Vi064T}Yf7&h-)@E_#s>g6L*v(!-p}kz&-1q1Enf)$z=0T##}<>| zf0>=>d7j11w6xZC(>ehF;8(0~J6Kz+{`*xHJkM*pc8|j<3jhEIz;2a=;c%$6{(Y5& z(P(6`c8}vYKds#Z007pyzq`A$(0!I=&4u#6pPkwJf^7f*V5j>YmoffvK7JSJE2a3V z006q6lq(`)Qg^CKJLw06^PlKbcJ0&VC#YhtJQ?-}dgS4E9_SM{AuVNuK9z|N1zN zUteEqGlT#DpsCr1hX;!*t)|mynx^|`cHeb%JkQIrY`tE$ZSp9k#^dqB!vhQG0|4xB z{dhcXtNSnv*Xy-XYR^ck-@&18QCk#+Qc5Yc*=#J{ z9>;MU$6**UqaOgy8LhPuy~W#o-xu8-w8=3z+D*x|*0L?KEVEdO+4H<8ilQjO{s3@3 zme2D%%d#xXEaLodIE* z>~Gih8I4B9{+#j!fJ2byc~KN%D!Vlzecv~hKjEcFCpLc5{<2x^vcKavQ4|G15Cnnm z`wRd8U`J6Do6V*u3Ne!1he(zWg5b24KU3q^F!nEs!eYXY#DCe=K@hmE%K!iXI~4!* z^<|u67v#k=#`0(Q-#lOASJ7IV!L}$0ixa=K3c&NcwoAeRz%`NQc~vZSA2J*ctBODW zm18c$_|4H+zN(G`fT&-_0Ez&BTZr(+9^Dl$0=ca1aFNFUsR9t}T0o`X#G)=1r3)zIq({c-$b&%840Y50LO7W&yzYp9$nXEJ}AIh1wIMy za=dG;yWEOHBD^`?uWy~l>ok51GuoSdqDvj%RN6r@9#9GZ94)+y(>pe2On$HCIjQWg zdjsBt@qelVs&$|Bd{BeLf$Ae80f4pHIJYk;m@UKy@ULeNpyH$8mh$XV-))3sQ19y&OuCtNJp#$xkLx znL`OMexEc`Nnay@^Y7X*#sj!$r(0jgw2#@%BVhdV8VUO94mHPvYMe29KP@BZ7ZYVn zDmNgw$)h_ky-S%;RDDuCwBR9QuJMub+ujIdnDhT(`!!W^eM)N) zUT9V3A7O?@+lx8-nu~I@NM75FX7FbA3&w}5svCYS|4&szfOQ6U_HNbz#s}E9nAj14 zcZ6bwkfxxkG@E1lx61%9K3s6|u@4>B&)SEkbAN>>4HzE)08YanHUR(t#s>fZj1K?+ f7#{!t&X)fJhYx*FGJxnM00000NkvXXu0mjfo74fY literal 0 HcmV?d00001 diff --git a/docs/img/canvas_stroke.png b/docs/img/canvas_stroke.png new file mode 100644 index 0000000000000000000000000000000000000000..2a4b382479013cd9beb7d27a633c75603fc5b7ad GIT binary patch literal 513 zcmeAS@N?(olHy`uVBq!ia0y~yV5|kQt2vl}q`2wv^+1ZVz$3Dlfr0M`2s2LA=92~r zvX^-Jy0YJ6ljbup%-Hu5q+GJZHKN2hKQ}iuuY|$5C^fMpHASI3vm`^o-P1Q9ypc~F zXnM7$i(^Q|t+%%v`C1it92~QE{Ga;znu}OcfZ`dMh27ty_f4EBeW1NenW2RJf-J*b z#uD}gj)qJ-x(u4_R@KkAUCvN0w!oU9R`mPZ)#W+~_ZivlmH%nGpKi|bn-3_yi}}xq z@24)D+5P-x^LO9hd=Jhsm9RUM1I@W03#341LMgD(U>Sl6zN aD+Zq?0p|bv1i686#o+1c=d#Wzp$PzDF0TCm literal 0 HcmV?d00001 diff --git a/docs/img/canvas_tree.png b/docs/img/canvas_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..f0b301d40ca7d9bd342c39677008bc79df5ce462 GIT binary patch literal 42573 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%0D|h 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 bwryGwXfEZ0mg|BS0bl0EmXvBkr`Uf1Su4g_ literal 0 HcmV?d00001 diff --git a/docs/img/canvas_triangle.png b/docs/img/canvas_triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..50bcd14cddbc5441642067ac5ff5d0b20ba8440c GIT binary patch literal 891 zcmeAS@N?(olHy`uVBq!ia0vp^D}cC#g9%7>yMJE?q&N#aB8wRq_>O=u<5X=vX`mo` ziKnkC`#m;kJ|o7Oz}-4PA;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4k_-iRPv3y>Mm}){ z2Bxo`E{-7;x8B}8n7brEfbF4~;DkddE?h1glD-^!c+&W%>12m*d$W~&*X~t)cPi{_ zgw*5Y`rRegpHwcWD2Blwi{Ad;_ul&3xA+padn;aBt9Yr;Rw4)|~L zS zy3&1?nu+O6@jbt6PK2}Xm-t#aL7yM2#<|w-$?K;0wqJ8jh_ivsSzu@XN%Ww;Lv7s? zZ?Ij#!SUQbqaG|@womNS(E}hqc&=RET_gHoa!|bZPb07=ORju(u(SX1a>e_`8b+|2 zm0tZ;oGMcFA)7 zdKvfk{|oJz|2(!h#lK&tJGyRrYGf9_{-?}?{6JgfL2hvYhH3{eas+_BQ3P7-3=F|` uV3-XS1@j-}Un;Ac?Ec`xGa$US$$oFh=gv9X40wRKgu&C*&t;ucLK6UfvY_?= literal 0 HcmV?d00001 diff --git a/docs/img/cat-animation.png b/docs/img/cat-animation.png new file mode 100644 index 0000000000000000000000000000000000000000..4241c967644b95b9386682365834fe9d0c642b87 GIT binary patch literal 9226 zcmX9^2RzjO8~=tVl?o{{i8B&HmvJE>WGf?kozan%m5lt5E?f4<4k2Wm8IsOONXW`2 z*(+K9=lXwo#ohUi&-e3rp65ND3(-)!bBdaY8bOd#N{Vt?2tvjQuk((P!Os}>l4J0T z(){inIpmP^?{!^nG<Lr_ zMk+1!OS#f6>>vTB;xAJu`$haGO!#~)rDvYAIV-|;S8c7wt%sOpE1V*#nxl$pPz%3g zZ=1CDx>H)oRF>?x?uX;o1{w$A2R>DJ|9Zc)>vQN~dfC!o;8U#q{O(S3*^gK5g`0K_ zyO`bI>t93Up23sGtMdPbUorzQ=O#+{VMdN1E@1&i$O&0~2D}_cq#`E^O>nbWasR@m z*Hfvq{)JP4vid7W2!;Z2?bE9smd~2US3EE?R%;w=;lYzf&Y+PoWlq}hybo+iPMjrw zyrtGfcC6M|5_3vwOt-$;{5?6}PSIXATH@>jRYL%a4fjFM30}II?@)4Dtly|XVX%1$y34;mDa_L7h^HZC2OKY zIT@b_lug%ou98*D`FYTuvde_1`+c(M)h7Qr0quisy8V}O9ML9M@hyowu~8x+I}MW; zj~q+F(S^4LG@7!XIYvQd`D};C**0}JpM|;QaLG|D*(rArhbDaEil6_@<2ZGXKRa)n z^A3%LGEuJA)wzW=t?T?T3KgzY8TOy69TSBsY;Of!kGsI|pO z6DXy3xVF$Lf(aav$F#ADmUS1CIvI{p$fif^B&^Xsc0a}(UnqB+f=ntce2O}OJtXSD zomhE~lXi8-J6+?mw^4$aS=rz~a|AI!oP~xe;8xU4l}kxkXFVBDQa*VTagrTL9Ht(e~$3DBi%sNKL&8ex!kC8fhLYf-g0AEbB3ERQ!hN zsRN_#?hxtdo`&o3GtgluJVW@*|5HW7*-~j;ldDyRMN?YsRby0%ImB*trZwSyD_ESP z^nOaz6lBEJ+o@ZdUUhWkZ>Ai@rtY>z5G~=aPiW0Vio>0Ad*?U3j0Dc}*8K6VciPUk z#8fqr#ovU95d{2?r#mg5b`ndD`4g#}P3Di(oHY}<(LLcz7gre>x(vc=8oaf_MGm^^u$-b*|+L`E{uou%lZC-Lg@{F0t`kky! z>}7d|q)xQM{dgI64IX+mfJh-=utNsvwbX37Oqfpi|^Ho7IRVeBcFAA8pa+6 z^v=5y3@uK`aQdt{z>REPguWvvi87TBExOgD$l=?kl?gA*O7uFeWohuNt*trYkFvK0 zHkkMZtm>tXyu7gm2vMjf0|q!aCkSg$ZK^zK_%xN zU&17ERUI)^m0aQ&(qVBu#$Y6AxKr|0t+jv6&Wf! zy-tt+OT(?adl`1;5Uc_F8OLFnL?$jlC=JTlQR2K-4uZQ3z30IizHS@5HX}?pvtq^Sx3o)R~=~wN?$kbmBUWHsrtdX^tZn=p=G~ zr0jX~_?vrtC{xF=qsUB|snBN%^|m_8#GG@~fe%Ql5EvMEqB=YvkRHA2F^nyXoWS?l zsTN(FT;Dl*gNL5lb%P8!5i_=Yk(IUlL1AWQrd!R;ok6Jt6&*djH{CYW)YJ@yT%F#h zlRPjGQBar$acr!ts`Yh(40xsLH+^;l!!Qeaq*R5Up==Nb=QH42aA@xEldxB!Orbgj zS9y5!&fK?O7sYu?r9q9ma1vW4PN29P*PcLo@>eUu?jCXF5u7TV@K)!F%XBBU>;yIS zi+{sXn6YW*JESgD945H+wH;K$J-$9Wn5a&n&SZiz;-|-tmXNR$PI#%1IpScgHfdf` z_2$i+sw%zA?v6^evKcA3>)6W5%Ga+EeRfuM+~O=@3Yolod_8vMbMD6wQx2#lmco%g z&Hf(6mJM<~eu2l&5=T-9$CKnZ%*r27QBmDdP>4s(@-q~?el3#;^JmJ|3K?ZZkerW; zZm0hZgF+o_j&e31FE6j4VA{}$ZI$5A{)NS5(r_mjs(i93|4~|6nvubQp&&;bcOgxc zSfhy_zg?J~ofUlW{>|IBI5h_kYaSe#zl;fKTyzmpU_TQR7k3^8J^kz3K8WAonGiVz z1qDhfs#BEYy*aO6zxK;gLoGk+av0#GJ)YS;(Az5_EG*mes>S@=`SZH>?$Mt+cgX@l zgcBsl$;l}L{F6g%69ssAyXRdW6r#zyP+xQg#8|?XmX;bDZ}m7PbYkOPk%n#g1Iv@h z2gBpR%`o6ph0Mt0LqHKo<9XLIFKz3fW)xd^_{*2B?(Rt{DB(M(=y}&_*s!A`-DHSN z>~YEfue$o&++adYdbbVVl`D7e-nGtrEn;CdxxHIcUH$j(U&U4_C`q}!SB+~gbZwC% z9o7PI1;xe1m6bOD&&u2%KYpyL+SxJMo#6k@sb(Z4CFSYUr&CMODky_$*ZB;2d}&UZ z7UK8ze5eBKkffv}D-D&rrlCM(etv#V4mKyTXhdgVU~#LWq9QW#>{@A6RTaE^{Tew$ zBYsEQY($Ogyj~4cLruXmTzydQO}NLGlJsuXt8Q^|(W@?X&kJ(e-o8*P@8jWdg#CmE zCbg)j=wW|RWX{8L=gt-A6jrAgENjywDRMgRtW7U1IjrHWtgK+x)PZD&%!uD_eDW3D zq6p`saqUJGIDL4Q4TngTtGErX{eBZ))kGvFHuig8-}LnK_~8*Gq9s|)`r*SX1Pcy& zYTY73eND}4eQYf#X0$-u!bA%mTb7W)ldVN?TYT_JIS@{e345ok9zYQfZ#5t|!=$*ph!n`@TLA$9=LL;VQU>7APx|uj_x1Hf zzYH+=PnDC_*3ORU!iCp7cB>ODr%s)^ph=1RbTh1WRpMvR)zwvhatiW}h327XqPX~_ zVA=H8ScW=dbPV#eloqcr==vmL}c#Zhm=L+V{X4!=_u%$9%(q z9^WD+1v!>mP|%J;-{FX?S*}m|CYd7jw7RIsLHS05^xjHybv0ni4RxuQuGxn;9I5n{ z4Kkx~JXfwHIS*`XY#3HKUW!zlbFGF&$yiW9{1{h>+-Z;&g?c5=GTw+-f58owS_wBy zZ|_PU?ya**x^+~B(*YMsOjLaJBj^+}A0J=N8FXD;oypV9bMl%6y2Zw&md9ym;7R}` zG^`REQ_<>)DyV?QuV249wws7Q{k!qK&>%s3qXmE7HW6E*7pZ8uyR}&PwkgpbLd)LX zUR3qbXf2_t%0tV{qC`(E6~h?bx&Y(XFGoED=@5%+w-QdUv$Io67Avu8j};A9Qs9Wp zyb~_9@%1)8!@@v`MN3Nyl=Ss?y6?V!{~i`bgHC!n_H;XRD5*nrUop6OT(JHRkk0;Z z-`H&t1RG)~(BQRcC&n`R&Mcs7_U`%@So#!6_v(?Q(^7wa){N~t7#WT1{q~K*oP$TN z|1*KN5%t2qvvqLT+T90OjOP7*1o`m(=Gy1Wg@uJ={;vrH&x~zY+FQB3;wfsG-G4q` z4)0i3e~;~CA{Z{Y)d(6@#|Yq()pCuCtxNP^FFQV*NkK4Ml6$`tv2V>)um*-fg~PUR%SvFhrFz;EI2ZQn?pTC;=ZVB19F3LIk?0(;~7d!WQ=HZC?6ef@f&epyaKgS1s- zw+%lZpPj93LU3nFZ$V*Uwyl?kN4)D`G7}mCoGl#6?C=-IqX-hg!GK>JD9MC%DldQh zyJxyR?qT_Zxb|1VW2QoS{IQbDwIWvaL`$#9&>KH~{FqssJr^Pe6_qVKdc0q&`IC8x z27Pv)os%VOCp$VxwHM0dcK-bWpWUG`N;3bcY>AYj!otPuIAgEf#Y#Yzw>k@r0aVM? z3q_ajgh63Ine0;qG?G)%0Bo4-O`NxAwl(JE=g;D4vnxk+)gB~w4whL52L+++f5g-` z909qK&zY>$XRq z5H|{n>;L|ESnP>mdy(ccHl9;O|0l_)~+nN#5-d_cQ>@5zG1g9tkO*LO#4SsPo#ay4t2!DKY6)XIwm+lf}!!Bj&O46LVNQ=|=FpiBn8KWv_zZ z>Ua*z-&df^-9?Oc{u}nzd-YBW-F{oxsTXCw1^f4Vm z@~k%0h|V5*?lZQQmroqlKhVz~_at9E+&&C7sqp-~7iZ$*nLez;9h$1`yy`{n-_#_F zPO>k5U?dP{DA0PA91-F&@m_72o106#{xItDrRgPk<+P+UpXncK8#k;1z4M;;-SpTO zP%u#cIhyA7X)?x1bokM+YmW}r(+>B(-ag%VxDVnA=u&XAl4tD9&?QPJSzcaVt`Z}E zHy50S5k!R)wpSV2;ce^U!b6C3%aci+qqUx8ji&LVI#7sp_4PpzWNXcPFV#oM5dmhj zQx*5!&IEGZjosMV+S=T_ud5p_$prD0<#D*btDA42KvHy3|(y_yn7Wj5%Wu^OYYL^2OrZ77@ zyR1w*T?02(f3wpR%DhlfSXh|9zyJLL9VO1Fm}hFLs;ZDQ@B0l642HV9BZYgXop<(q zYDPx;`W`RWZGW-9(bjFV@n>~XEw>EtzBBPU@7+hrF~BSU?sIzwhlgLbi6VYP<_j0* z78i|xGgOT*?s`kDy_tNkU-(^JdOxOh5{M21zTBppLm=)7=sIVDP62@RVBm@KP2-MM zR@n-f#JyG!h9*8c*}d~IL`#5lmffIlX6dXnt*eu*79|&g0x6OBmmV0Oxv%L|Y#uV{ z8d=G{Al)M`-vQNaZ+{i=zutT2eP!vB?Ip-)AwlWCbC=V)w*O3qmks`&s@&Yzco`Ok zt$*(bJOZx2;!(ReRM~D>Y7l%%UK9A2byq52^c9`ARVhZ~NW`XqQ8gz6z92t8>WQfn z!7$DDz|+WxHN5qTz`<5tN9Cy3!?F6O(I(=my&-buu>6l|SA*1Y0bVj}y?S$0JD7n# zKJoIp$9Mnf(|yB5e>sls;o;$tkxuiH3i}~+ceKn#)jsvy=HK<%E^gY8wzf6^PxGH@ zu$W+IjCrrav=+V>GS_U(=cf5U0n2^gWYHRCKUSaOIlsNDo-C$mU|^uHpI|6Ex4diu zsRc^NJ8ub!P0zxnsA{oEgAWi5I?CB9v)h{AzJArPu(-@Pg@w7zWzr$+Ae_Dr3{-ip z&xDMUKovlOw{X4f)fJ0JWG$1cfP|>+J2INztka(A=<3E1nW66HmXvfmRJw>IcP8D) z9TVN1jx%9%_cs*iw-LR3`7$ueh6a$op$4a@5QJVH_W#S5FQ=H%DjPv{Pqx`^db#Eo zu(Gi^*w|>~-MjIM)~33)c5j&Ak~Jcd-aD^?(!O)&4)8%rsp3XcF_)Q_{R`9UI|dcF zOBMrJ*UX4PyrtLC=%{Osz;v0Jm}Wftzk^vc@q4h$edKY!++E-Gq_NtKp1ZvUF7op7 zCMIK5_=DZCL#eF+3u3NSL7xN$Q)}0kKe(GU?pRe`zWJm4A-()L1_p=WDktIFm@y6< z&kOL5jEsx`e?h?WGZY)upm%)0D~r&cmdku^2>dlY{SsTa`lC*eDK#|)xxJw^8&kw+ zqD4&h;a!JA5t@@H-@ktkiajw0LLv650h*Zfw~~grX<-vEGIFU86f%$-kc&zyHqC7zhnGV88U-@|>8KU83(l$;e768btis-}?j-9{7wBB@6t zEBX|3lIj8hajn<6QYh#;*z&blS_B-wPuGRpAQ%d{a{Y*h&rGVEU= zyN8eZbz&dd+j9k;@|V#7^`oyZ-q+Mi-xTy*j)Rqzl@xR#QYmV>1_oCm6&*20WE@_| zagY)tuU*-=ZF1GY_sT`@l}0FP*1KhY_74C?f#gN*sykHH6c*Z>n)+q%tZCRie!RIM zAp+F!jB#;gU7b;JKR4}h2K;?Z%@RvuR7|e*5yZK1HqEylc-NC9!fZ(^n?Z?rpb84# zI$2&`4#r>3JY+?#Z3fzt#GN3?v6}&)4Z@}VKF6%~K)8Yg6BVToRj^Gg%FllQ#bj}# zN~CiX%|%NDcqVx7tU;lxudg5dH=aFv_UqTLo*pd>1vNtWFz;Hw{zbA&YL{PLlNBBh0c4hhqnUP@4HpH~c+m}T3@(*V1 zg@9ZF&f0AglfmOJ^Uh<{JVh;d))`@MuNrY+z{@+Z{2r=&ba=4uFJqcy*$?7zC5b`*N`T7axz@HK99X=95+;<%J*s%dzdc5h?e7|o&QHx*F1_&n?KOd zc-=b>B|_-I5yX-zu00!!3*iL7QaA0H`JJ_PLG7T+NsXr;Z_H_;PGpfB8m`-%_jGi= zd^_&_sVNBjJ9q@+;?u#+^zvYYL`Bg;jo`n&Plc?u%4zc<8c$e!0qUc@j zUkRs~b6)q3jg1WrJuDmioLpOrfxs{}4s-a9n2vjqsEEfzNXH}h*e`vIx4X8eR(8Di z_xJmYj4o$7YSVK?6WiL`uki8BxM_$Zet7^4TDNcCo~bl=@IXRL?D;!FIT#AU!Wm%b zQj+J*W!Zw$K$69=u@giMB>vD)K-bCXn#)MC62RYGhhG(EtmeMoOzIT4LK@%?Rl%{qr^88nRr>;2}L;_-<6|;Y4%j2kDS?@>Nh&=SI6kx6L;knmJS0_V*djl`#( zI4>~l)XJJLRwSD)=KV*AQ4{s>>xsG(6fZLNd>RIE75^+RwOo-MIwvs}xT#LTeZKqP z>}l|P+@?ZMm+7gA(`y<$G2@FbS|-3K$<|7++7)4LLg{1J+-pXr8d;M%V-1HCO1T9E z)jb0Y1@4T!20r5`w)aJH8S<=aVH1nX1o(hzXjd+=yJcMws7hVjFX;uYrwZf!XI-=0 z*5Ps-x3&%e(d$s&uM+5w;OM;o*Z-5F(q(C17;K;BD_NHgG<^F-(fQpl^lNhWi45G;8$mb+yUnb|N)J4yPQc7r$ z&`OiCk706L+s|_v#7_G_Mg4Z&vK5?r{Rb|jby8(iNONk{Bcpq+Y8l!UVDj@bMDvNE z{`0`FxuIlc?0>w^D=E1qGSn8hE>x6beFEPSr3}#_YULwzK_%>>7A3g$jiy2uPUg7Q z-By;JSbcqi;&a~J8_p6rwuzX~2!Cr@2?+_|^?9F-%!|`a(r3-0ZNc1QKlA2Ze#}B5 zG)LBVzz&5yN`*GgKW8oMlt?<5-_%FRj_eRsBGTT(1q5<1g$Bu9nu)q{=GcF->5gJ7 zV2iMXY4F^G;{s(lX(KZw$dx8Hy}0xLi+8GVTa+D;3HmCk`ok=IF$d0#`Xv^1lUFka zE9WW@C#gvftrNEJ)@kRGS+k0}oSbM`x)T&+7U~P4s*rmyyI0C^^CWO!2LbGO2RqOW zV`&NR#vR4(#`519_<;EXorbB%o)Y}$>+A%!lbqo60d|lgG%{v%T&qn1LbP=<^0!$I zxcp#6I1@M{qQ=LL5P72%#8_x31EN_RU$&UTG-i|);{i6cLmGv;G83p)tvk2uuA&;`*}}Qc9E0we_;sP)DVAHt6Z0AT(By%z z6LR5~STf9~m2%)to;LN3PTl4G z5QJtYh0Geq8ttS>N7T&BOmpyQYplV8LIyMPh-pSjp(2owvy4MJ&i(koQ>TIvr2g@* zZ+SrOus9d-o+UiP>?_#3h(iWRJ6Pd?Fxd^;!S`(f2w$uBWU#BDMSV>>GoR>-bz+J zhlVI}W3c||lTQ^^=%lmoPTVfz%eS-WW8j`LMwj?I08qGC#`O?-S7;E^QtMADo08m- zieU3Ytt3(+?3H_~;l3RM6}868#Xg%?ekm}|-F&jOH?Qv7)+UT7!xj6%<|@k0wgN%K zIO+g$2YLWQi(xJ1^zy7?j;7m7Bcz-C2IDF*Ha2!=CGNjwGbkRay9|9cB%z=QxO0dh z6`q6rWnb&TC3)xvYtYN{(!bbQZv}ov2G1Q~FZFjWGn!ldOIpJ$PA2;!=}#n3Oi)11|4gT z5^$+EtlL2efrm6-Seu#IyyzBfZ38`{=5Y_u&QSBBxbD`2eIv)Qy0=aedDf|((6!rJ z36^#hW6`g8q8Unw$Y|1Uejq>10_rFun>w(`ROnb>6Jx{h#mdTOAv1!8HtY!ESoeaz zvuA97$z3E>^xMb%2}}JR4bmTE;9fxHK-;)QYVd6A?hnZG3<7ZxU<<$D0NnL4@HC4< zb{zPVAAmK>;dS2X`R|hgE&d3|kQAH9vm^192ttk}eHQ@wV+Xfj={x))BO^l?Iu&{m zQZxxb-J!cm9o(#Klr*-y{QEcOZAwzN{-;E!f=D+9zD0OvlOmuI)+?lCBKvNfvsf~? zBn=0f#RGM)@yLseH=>iRDtQJf|5Qg8wv5;g&5*RcDeeb_-zQf|({naT`iB zem5|vbl~cRX4<0LWztL%_4re5B0I5?E;Ah)I=irQBE4X){$5&AAe~$U-iOJ-S{z8s zNr90Pi#r&$v`=;kv9|Q`;4z3^zkZ2y5nUgjgNMa+Zv95?svAMvsUGG`1vfp{EZ5qH zfA3L}(wJ_Bps~bWB8V2qq!77`&4vCUT!quvJBY>G!89YzH)p%j4!^k9M?2?ogFQE2 zhyX?PGt~U2^%73Fob=3rdxb-$CX^T_9j~E&< zFfw`y2L!Wuy#X7`YXR7WT{#o17gFiAG1tB9d$?=+A-fi3tJY282%8GBJnuCx*C}N@<7St#(XakZ|pH1SZGkm zaQ=M3JwCuY+7Lj_tqY_JCIX97?WLDLb@JrK<|cg4q0abl0xw8%xeY35cDWhX7!cEH zU4U~Gs2mu?_t35d|K|PB+1HjZSs~TABKSHBsrdTA68-yv%ZK4z>DLwimHyqW69wZH NQIc1aE0Q&R_CE%!>puVh literal 0 HcmV?d00001 diff --git a/docs/img/cat.png b/docs/img/cat.png new file mode 100644 index 0000000000000000000000000000000000000000..e365386fbf385fcd677b71f22f77b03993cb729b GIT binary patch literal 2265 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 literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_00.jpg b/docs/img/chapter_picture_00.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a3f4730cfcd8e4724cd9525a848a8960ff3feadb GIT binary patch literal 17489 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 ThDu|fB8LC~H^qg{!}9+FXQ<1_ literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_1.jpg b/docs/img/chapter_picture_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ebc0689f03693822c2ccdd4a8a369e06d1670b3e GIT binary patch literal 62684 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 literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_10.jpg b/docs/img/chapter_picture_10.jpg new file mode 100644 index 0000000000000000000000000000000000000000..40706b183984e21cd0d66b8357bade078287767a GIT binary patch literal 33013 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

      ~6S8J$j`!RhCxu$(tEUH^hI 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 literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_12.jpg b/docs/img/chapter_picture_12.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b90572f1ce91b923a55a4780df78327bf965f2a2 GIT binary patch literal 17937 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 dlId5L(*FP)HA?4HFCKH=&Jw# literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_15.jpg b/docs/img/chapter_picture_15.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85462c0bef8b9123bbd35ea69d5c858ca211456d GIT binary patch literal 16624 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 aNw3fo>Z8Gag0S0{PAQil-yz|rh5rE%u&=KG literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_16.jpg b/docs/img/chapter_picture_16.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ed2b4e6dde7608fade0ccbe3531856a6330c7618 GIT binary patch literal 21599 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

      x_N!9Slh|@{=+|4n zyK^MjNJgzBQa>`HU;1$xHm=^|@A*pUq6i^-WV2VS@9qMC>3pNbB^Z1=r;oMm!vzof@LBumj`-2l~7Rj*m2O!BL z1WiOpAQ|x8&46&Y+MT^t2;N-mQGN?W$C6^D_V zhyp}0c5+nnk)apCnRipeS6|pR`R@jO2xXHTOT(20jn6TjaGghZo(R)VA z#PwdC__y(08U&muttX_jNh)`*vZaO((ZcAbvFwSO8yq+Q7ri?k`sRf0b2TR092wW* z91_LG5KA&VxAf<#Q$tmqfO? zbg$%tVN*XGy3dyc?P5%X?*p}#LCF&(dI$c7dGanxp~@qQ9os?0#Kg(%W<$%DinA`( zPmcFXb9=eh?td-8eOH>mG1qS}G|6T;LD1LFvlJW<%aBH65%2mXc9o<@67w2u4>yfy zeN9%EQOC+ml7B%kdS&mY#z71O$);RA#+wXPAj*Uy+zgiqT)A4&S~&HN?#&wpl>C8i zCLo<$nKa?*sUJ}sIvu(0*tlPqk|MF7Ph!bx8XY1;LaL`=F{VwO!;TQ1^+?m2u2bie zWTs%=1zT@K^y)XY6`Z8BHNvGgwwC6F(q>Dt5m711y|WKM)NT1FdL)wCfqMgdD4gt9 zS~M`4N&r4Q)w6(rsW*+I{Q~D1k(;Ub_=F__pPDX^wXD27S7Clhb2JjCY^EcGLBV}t zuoqP^g;Iyjx3VUp+ z0hIxzbCQ7Xyh6g$qE9YOo9gi0ZF#U;J;&i@kXiRd%Tr~dv`Oc=P}MZ?oc9M;u7U9GFW#%VAeOayLde*-#OOl+ZW^U$1ZS__gY6zFA z)>-S=m(+=ZX|PuF9srhxrLY5oC$E_~lA6Ty!=m)&l8q(MZ6K!QSHx_W5O1&2Do1Lf z$xNeg9dZ)9XMJB%LXbURT%4jQr0+L#l7qcq!?^AFZ<;6a6yozhOY1Nn!~Uc^@!Rf8B)R~xuzfAaP>>kMeHjjmgs_P zzi71kYtACTjTJdu$g(&!MaPH|*wwuReeDw5{tt)V&D$}PzBtuAKG|>RJ8;%;bFVJ^j*UK@@J6ej& zMs(+s4}_>NL($wwji&iF|ztHxqA`<>=Cs}(BAm|cb3=-Qo6EN9f~l%+Dzz>teg13Wbc zX1AkxcHDUbH-0r*5SWY1L-SulziecmE5 z{6$$4lKOI@(G{}!05s;3y$H`>W)EzsJJKxV+Ry!k^E-EQE@ib}OxbN;$`6KBHaC zrl?-7C5`X`%xa6=*?rS7?LJNArcC?}PB%>usgJOSTuTFO14(ko&}ixPFsy>@82PDA zO=nVkPeeoFh+|HqBlF2sEot{XGe%^k1jpDjOx{XbeU1{d9R)ci_GFCwfnOFBQMH=>}nHjb3(5s2m z24?y>m(5&q+bQzigb+}U4pc3IB#Gl$0qxR3&TtjD_G=tAJDbjHXOZ%x0x3$vxqO!~ zy!0NS`+a3%S#z=mUIRZ?-RRXN$xdTpGaU5M2LQFgUoG*oaJ>dcDY1YW)sp-wSx!e2 zUj9M2t6ZRg)K3@nFa~aH`)%VRx+_o(x9Nc6BC<*aME}SWyrlX({4$A#%#^p2h(94-9M>p1{8S)au#b}b~ z&B?EYBPVs}QDwI+t}kjN!a&IgO8g1v(l3mbGhlQ*wwaeNweo6c5|Vivc&ljLsQyO|`h-viR4mJj>K08MHbBI_gAK3RfrRj~{ z-zmzJTqI$>r<|mNHdL$p{_`*|G8LCvK*D7ZZA;qOG~a~E6gwnpS`;E@g^>R28ZWTc zuu#LDbor~Y4urK&4u0lmeOVyhSK~|X?5Mi-6Q8PF_$PjBPis-m<}Vr@Cp|Iu2fRod zUb7N*)W*pVKF5p{k7G$RmI;Opui{&!wN~8&kuA@8vS~x!P20cnn z+@F{>_10_x^?T0uC&dxO$h~75#^R6jsD)@aTXoaG>Y)SY*_02$8%CV$Idh0( z#m`IxC%IVPxZ&k(D!eLBv0zQgxL=ftQT{rdyjvc@zD4}$DWxQUuCKTl&Z}?Wqe8% zh>Td`+|V+KH*$CpPvtQBgE+0Q5l|sjG_zQFsyQ6U5`w=dqf!%dtd5itWUFhE@gx>RgH|+a#UX*b0yn@`j&vnH zr?5ac-^;im=ciPdoCIrJ%pR%QnNfmdA+DnPa2D_C=VuXnajBrJv4A z1hL^a--NZ#g(y8<$8;f9^iq=AU*}rG$O{RNG?bLD%3srQrbn2h#kwUPX^!;vL{ekn zu2xw?MACC$aK_K?f2wJWYB{zfD@#B5m<%?D-ic7~L z1trDv*nXrd^ewe#K0V&&l*Rm-#Zj9DH-Ph8nX{w1^=P8Yeh^V=^F0D)?}lIF4`IHX zSovnys}dX}U~uK7a`ka8rKE>$kzLTj*_WP(C@I8xWTjmNNr;m9yUztUWrmjdd96AE zG%06q3d}jB9myhlnSj)jZ5Q~rK4U?3g;n&4H&Ugt=zR=&_2dlZ{66D0}L&w_H z?-$W!cS<~(ml~aO(k43a?$W_>S4MR)wluir>|?5qDmc>t1-O-@wf6i4d$#9B>kiu` zB&;5MxrFkZ!A=8L6I94ti%Mejw%Atw+0&H-ZBu^2F;BQv7pZXS5*JSQ({$KWC6X~Y z!Wryi@iie7;NjCENpW=78+M3RF==ok+Ou$tj>ZF(I;GoOnWzu_O zXecF{ewlcoIzm#;ZjD2}^+{vId4G5lwNs`WYB3i4ftX&r76|Fw@nTl{;uyi&OSm#)u&H!N|9>;fdJe-m5?7mL7u#0N4rgwEvg094+L3(+LAn2K{gJIOKmiAP4@-pCGw^ z(#3z%|0hwN76PJ8_#epMRwy6Aaq_@ls{Tg_dldPN`JY@klt1*pN_kq?ztsKn+kxL11q}BGgZ-#b9DtzwA^sKw5>S8y z@!v6l$^TLK7yeHfBnN)f9|!&0!^h?$U5*xp^1D-U|MH(U0RTUG5(K5hmIs1>e>(%f z#2>8ykD4KJ;KwrI-}uM6_#^l)AAm<~zt#SgQGR>!R~`0u<#!Y1?~eS3_*>`WKs=t8 zi35TEtszJbjQ!}tV;n$W$YbSScuH(J+8|`wAaU?xWd3a;L5}hjPTv75+`J^DK)e{mm;LcstsCFFOP|2tCxk9JXL|E>8iE=-&f z@_U#7$Unf3p`*n9?ctwr{f9*vCr=3jz<=FN|Iz;#PdVVRUl936)_;t~p*%YO7_t9Q z{^tH3&A;pato#=^@&B*`^Y3FWf(#h14hjk!5nfKjK1{j0;u>waP-&)i>)7Usj#_e zxJN3c{qLyz!%Zdf(s&^;@37rAgZI2!U%U0{WOK?8ywMA1&Vfxiy{|n(rsqIN2=C|g zxb@azG&3-60l3UHAt|sc)hFGzF8H2gNHUn^D1!@#7mQ$eFs!Z#LqJFuJRRn_u?|fl zdr?9qh&&)Z#NLe#BSl5P_l7}I%p_2-hOh-coZ3<;-24K%UL+)=hju;=Gh~G#{Y~>L zL|65tMfq*{k{_&$lpJ7S4-*X#p}QxG$LwZAiO3`&-5>a*gd&!dhPsv;;e>DLmf{*z z1Xqk1AJ@cRO|u&QN|P*C%B0|s}!+6njbeVro;(%^=Dh-FJN)T z451aS_|p4~#9qS+BPper*-d`H?w| zguWeiq+*8Zrhoy(oAW#2kW_-1!qh4z$Y|>}_e;t1l~pvohWIejs7&FK$wX+Qy8^|# z)sC+N(ge%{O&MzNRNv{|j7U!r3tJ$F>fF75GgR5Q0kAFW{M6~&`!@ZCx_+pMeM&sy zWlpEi#r*l&H%DqSf$!=*&vgp1G6KSIG1f<<+~V*!4JyFAnUoRHY+9M^*ww1oaV$tO z*}hxerbm$)=+BYH*0mIf@PW(cx0e!~Oc$WCYItpUW6a_*Cm&i`xH;LFz5=SiI59?i zAI<0nHdk?xQ|X(_EdeQ*BhmY{Z#^L{@gLbPKqg3tQP`U~uo>Dt@2xO@hCy9*guXDR zLwgqdUA3WBY2xFQ(m^r`XW8W3)Q$xaWFlx-thvuunV_o=#xbpFk9%S~ip3e%mP}{+ z*0F~K$brwH>OW7-W|3)hXE;2Sp5Bhb^jV^*%&3V19o{CP^iWnn!|DBvP24izvIqyk64F`^Tkb?2vD!#%Uj38WF}YVK1m`xw;FLAnL6R(Amju4b-1J)xv_ zENp57Y7r^4&CDOL+AejV5)V#Gw)kLc$Ch!Uox;Q_3q_v)O*#TKCC(N96e=z0*8Fbf zU6MM2Jj43+uZ&AU`&<}Y`S1_(Kw3Eje7xdY0<@=HpYKcA@I>t^oHmSD@Jx%m#E35W z?cL(wlAeI__hW>FYU{T@It2ctix7@~XER#&)x8ykh%xLcms^729UYo3NE5}>J+f|X_Ho1RR!{~uE52*7tlq}CseOv4*>j1( z_>M-z6+VaABa$VBV)kuOJx6Y!-Lo$-1-{@l0-)r%=Fg=d^f-Nt3f%+qq-b2X$H6_R zfF~-e)%7|Rlvf>hkKIke#*oW-W}x!X3*uX(uT6_rz^nZMBWC-c^CMYZ-CzgI2)?=a z1Jl$m#=9>8l+heII9|LbdvO zlJSfm_fWqNzRZ+j7L&wc&(_7%mIJj5DYAe@C&?)ac-OhIC6n|uDJE?a3-xf}+;%VF2e9e;35_MK{-U3cF%*G<(e_ zw^~i;+t<+W!UecUY+C)}$)E6Vh+-z*vCLxSJim8U#C|OmjTkYBb9hk#73Bq$qg@4Q z3$syGvP=6VlrLll_uzeuY$!#Wu4hcr&BB0_CzM>3(=NMMHx0^5;L8o`?kZzEkOrjk zkWtrldtiQGixMEClAjIlgvdiHjG{|j#oNK0iW75^T;(n@t~bYC#1VJ+T{R9wDS|K7 z0vf(G*Cy-rQ&8|%zm!FeT-a@-F~Uzqk^_zzzXzc30eDNKRSa5O>ki&+`?8$^cd*j( z02~hhiuT=T8^;fTzeI%Ablfw0mBsgm<0K$dnF#p@K=dtKq0#fE-L!RNsE_BF>674W z(BWZgISH5wfn;zJ6D=dWG7M<|5;W!&2GRPMudV%Z{gaKAKg$BsNpoOUuJ%>?QBgS& zR_j}89@XqPgnplmAS?m=89yq;NdCHSrV2x{;me;M0CA}u+-0Z4Q31k5RC@O+PC5y48+RfbTAFShA-oFAdXQsGuU2~FnwP5B{2V`Sr7vE z6q}ZvZZ(PI>318&v5g>%u&`dJRt$0+owvZgLuM3IO8;NdES>C$fH(;9K9m#(G03?lSe*4gzV5r zBr*EZlnP2YZUMdV#qpsS&_Id-wk|Z71b^IDq$(y&v*u6goZ8O~Mv6F1t-f=J03!TU zjhOSy55Vjub}^)jVwOwQW*kduN*dQuS(NSk@T~+G6c+wLPAqC$6LsfT*{f;M!DrYF z>00;40+X22D3cn{8)$Lj-YLY|`jCDA2l@wzUv8 z@=oByeKHVp@>Hlf^(fOir$ppC3h96wuP&5SU`7NpfTbi@?oD1bl(BwPvRMxiJfk3F zwz4^;HoJ`i%@aaU;8uKtD)X{?Elbs{)7mbnvyx}}iw%VFZcA{!^Hq4CnW+qgE}6o9 zE0zD_PL0zGQgj)J1o8Rq&vgJZc1aLQ@%$ap5E=H7r>x@4Q<@v34`sBV9?Vbhk&7E1 z#eEkeX0^&&kYLEU+DKDv+WvefyN;>75`jke zvYN4epSE~x5zVcD!9?2Q8jvf02~qyHt5AIK!DQY?)P5LT{4~mzb}p}=iko~q0_ay@4)|00HY3E@!>!*spI~!-85Yz*HN7_7-yA@5!038DKUA?Ko zV6X%nFoOHorcDZR>HUb1B4Q=+vKte?6_o%DSFe=?=_D4g7`pb6I|XhQ65)uKVxilogPF#X8a>#G z8Spt`6+w`|28JaIT=AP{Kq7k)(Huvl{qose;*|J@Pn@2(f8cC{1_{!^5ME{2D^s+F zb@S3aa+1L-RYLT*@~pt#03$<)09I9I#}tV2Ulg=k+GL6e2-@-#fdyXU!I5Krfu7yF z$3ooUv;yWR4MQkXq@zVXv43c)v~<9yg*^WNoidjmk8j5ixHUo`1|U?eGXST^0Gh!? zP`!WW9<3PMMl+-PB)5~lXt?x8KfiA(b`9bHQSpixuKU@_|GQC%nIbcmlNs49?x(XPfKkFeA~F5ojI6L_rY#F)w-?{qPj$X8|{44w@n1 zJ~&6A5ic_MDk38j>_q#VP{Xqo65JTO;lbUsfml^%L0(m6Ou(UfqeqX^ECp&i9>1D+ z;7bEneS&ygAet=Kj-I^*RD8tX-Br+Lih2J4M+Ol{oqh76roqQxo~j;;w_GTI1XWK! zZt=t{u?;ZFAhf`EdDkcvH64$Hf7!$!Z5tjNH1pTz=wcv1`g%(9TUwr+G8z01Tk+u* zJ2Ze%!yX8vhFAd>?WevhPL@&Hz5p-6m4n?FhbHLgAU=>+7(YT5?lT!xc-EkwGq z3Z=jrzBrhVvbb_aWa%Iv3^5q^P7Ky!3VtYF2n1;KpSvoR)1&%$wmDHi1r-HL(~(+2 zR>?=%r+zsAR#W8Y=I_J9URVl-rDc0fUz3EzxUWeROz4eXCD($#008*>oN{akBv^FP zJ;#H>q|P3a2H_%hV7k-tf_AOUaN)UzlHdS5jK#;BkPG9}=4^bjaU<6xMo8O!eQ*U@ z(R@6q&}&SJpwkL=06L@2p80Bk7E#3G4EP4O3awP_)qU`d9%vKP;GV<=1K{J*zS11; zNJfZ>;T<@HLN1mkg^UPO7B815&+waA^U|0=Ecu7#~r74i<-GffZ^!&<6 zO{ZZ=)|5|U!jvE_45Vov4-PDbNUjZ@!MtWC;Dr9(Din+3*Y0eRDcjM@f+%C;{-E;4 zDh!}Thny#cz^60?e!>Ug7QtzjOR0#rnh#!RDTttF`h3y6h%iq$IiA%NSd~oE$ zRV-=6@*&6apdixzd&>&=AV(Dn zfE1_%6r~SRLn(xUX`asU$rntoV+4wU+(kl!6p#;hixr7v6u024<2so(!qrJdFS{8^AJ%Kk$c4BAOZ|;diJDpiNXv7DiVS`Va@okauT#`H&NHFcU=Bp zkjm2Agc`gMjAC376vRd#@kmS%h2G-3@pOrk_Qr;0nKBhS6y zq7Og-@MrF5J<5tclTXJIE|S0NvB0d!nu@=7#}xpg6lPG=9RnD>96(5wsx+gxhxNjS z7zuhsC&yfQ(eG>T4g{wa5YzxgRcfm7b!4Z9*0J$;_i}d5#ezR>8!1qO@E3!L3Mv5* zDOIg^mEvlxD+R%;aP`TDfpP+%rP+LN?iW&m5hZqppqd=2MT5ygt&me{I&w|uVi0fv zd$J*1;GHB}o$#!E(tPo~5FiM|9XlpN*~?eEq+)^F2M$g8%me_~8Vg?}1lLypa))v) z(6^ST&;XK!)l#aet5tZ!Fes#GkBQ2RYuNj=U(YVKFfAcetV4??z?>8c5JZTX$D^v3 z9;&lk`Q)jB*!u&8d0o?#Rt6Lw9Qg8o3Tc$_na#phF#IP0<$wq{8 zhwIZR!txZ%Md@dx7Avds-w;*|Xf#viDf#2r1&UT>l!_+jU5B@{5OJqoDf!|=AyHKf zjz-7NljMpe8%;9eRgpGfFe34 z5&lRFRaXE!@b>s`5rxu~2i5WPGZi#(oj2(J0K^>w*q%--6f5ZS!$T*>@Y~$9u*w}$ zm=}XqMF#66D;|emO@a%!8zkCP6z>KxEC}|%V6g?@Fi5ft8!T0z%l`oNBvk-Li@DZa zilm{0=#V7?+64+19k{^FFcS(E9UvBUp{FWI8Ym`+G2}stfy`Qpw?YU7K}F+!z$&T6 zfD?Es(%9*C_bTVh!W`YvP-N6e81I1iBI$J~z|;yr5E!CmVIT!=JSunrv%$>^3bP{gDgJ}U24o}_v>PAv{{UPq6UxAXsCWnN zybqTr>3!mi^Vc20iJT(3Kyh%u@8c0m%tj3WSKb&}o;lffMhU68MPwTk_V)>1hSC zQh9U%=V4S1EU6-vlF=kQM(uE#B?!Q$i~>d1?yldX+SE`XXAIOgs!O9Uh`oZGV(lIZ ziFF_h0001xjqPen{Q>wnb1hKj40*GSQ(VhEGe5J6!;TA0jiYZu?sv6Nm@4mfkUavL)$pjXe*^y6_ZKbJ@&~WuZ;Nn7)Ag4h1^}U(E%49%Y`G+KMtAm30P#5Qr zlq@AnRBT8|TB)LPPN|`zrHTL~-=RkRBK??l=uPwJ;2pFJV6s#lY8A`T00%{UivEN1 z#r;Ra?w!F3dMYW2+e9q6`V>(5g=x%@^QEqe8Hn^uyQ z5kIGo1cD|&#gK7x0fkVf)jnhC%kQTbji#(eP$R&!y~G4u6j>6Y6AzZcefZ?X^_Org zAONVaP;pVzB8UN$LV(e<7vNHei9tQ4M;j>JO{Vq20A~V$-?OsLOW~x4YB-snCoEkZ zqNJq(%%j6p;z2`D@x%%Ns`%$l@0;{TWGgp_AsJyod{=}CFCQSg+MT?<+<*fPIU3{c zuL2wf##BH(ycAQg@kI+g1R93$i{goa*98SYu}43MEREP;d>X!kR}KntxJAW0eVzBi zQ=uLu0tin2IFD4+JcN8o{psuI`xykZEG;I#K68GC$djlLE&G|htOf7@1Z%LB_uK8h z_?|+>!2v+&E;+;~?l-O;Z@#RU%_ zqInmG^2KY2f93nH3V(WGRujS^APNaQGY~+e-+uo9g9_z_5Gtr>M4mxcu(BvFg$nCl zM;;0dGY@-rnw!#bZ&Dr;Opkgl0AV09zqY7zKzMI;(Hf%aB&gAy)oY`Ta!a%KreV{{WBR;!28)2JcoU#}|R4 zR6O>l#(D5*!^IVbW-3F-`=61G0De2H*sv6YTJv2zCI&`80}w*o43SRs6qE vQ9w$g_Ykq#{{T7yR5A4MAP$e;{{a8lKBltO literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_17.jpg b/docs/img/chapter_picture_17.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7445f048daf75e691f529f78bb56aa7ed40ddf1a GIT binary patch literal 27283 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

      o;yxudX%ap7%LNH_pXl4Ywx@@qm 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

      Ewh+neUK(9%IQk7>_2C%QnLQ7F^2VO! z_#$KIsn(}jd=rZ_%c*m|d=rM+&IPwrpBJH2*L9W!slq&D(~YNN{U)Ia6X(~f*EHh$ zJkhnDFRj9on5#U$Cwlw_?6T2IeggzztSb?ab+``A73DeJEiA`2c(UlvuXEi0_Otz8 zC%C469fA-*pr;@rj{l1V_#X?k$1@ff5sZk6j(~;&MtSrC0wVy3xKJuCJZeb|ByJ1$ z(A1J<9DEoJr-YQ|;QSZMg*^gV9xV^gu(VQMK546#q05kT8FlOMjMl~foWYQvKAyp< zVIS|O3{c{V?2U)$1tmLm%bS&QJCZEjiX+}CuphrgWC5pl{RTeR?>=B?LM!b|-QsIs ztUCV!A$DZ64UC_Y4r%Rw3frrQL<}Oc*rG43)Tm&i?3|+4i_AKZjY$HPEYte z)_dcAM3b=)E7y!|HuRx-Ow$DID2MMiAmdj>m;0+S-SYAd3~g^Z0`hOyW)~ocMowejW;mp414(K{5-TiU~o@K8DR*?gocdvt2FLw z+{vZNh#a7J^`N(X=x7TqNqup4XIPC;SlO2NWA=2;A;i=na;e& zcAmG<%+R_(O&ONS%xcire_JQ&xJ!V~J)n(j{Y-0D2 z_7&slG9D(G?n@ua2&&k26!lg|PZ~&(w^cCXz?FrWJ|1gE$+=Wq!!TRFc<(tB@*=E& zG^05RG90v7EQn`$>O=g?mb$%G+GXQ@;%Z?s*IKk5!C%t-B$>ZmyvQ$WY2SY!v@|0p z=ye*d+A%es&DsM)%x}QSyjxcYg8zAx>jS-p$wj;Bm4&VI4vB3DRcpvmm-Va0A};3X zr>~tDrt7IrbB{@#Ef1l)E9ChHiV=cY@9?|A!u+wg?6L4}j5I#s#<+{t+Dkl_)rz^} z8a+=4IeIHNa0U7?Sj==vbAYP7QLCA$j+3cfcYXjZfqQ6;wdcB(#)!Wa{EQooKX-(^ z$zthMRzQDvlWbt~{v;Z@0ABD=uM9TorjnL1@{bhM*5TdHd!~}At*TNSP$n<=i!+l) z{6`!9c}GIi;UVs?-IAFFtGiELLDNpbKZh!K8wJNH$MtLm0zv?;M!rfpzDjzcw?`sn ziCn*dNzc!+4>at0pKF?QRv_k;ON;@ev&P$-CZ`8O*KT$CR*XB(i5VIP2YI{9GTI$h98kUF$-`K z;a&7su$`AuXR%v@es!AaJgAn6E7DnZmXyoU&8(nuG@3+3TV~NI%|G)K?>w<-ujq$= zoOPDESx?-5?a!a*Ka7%4-j>i;J{N?8zXp+S$utj&e#@=LmBe%{K>(;qaVpmI?vc?X zE+019+W7Dmwmio?Cf|LF%hK1HsnxwKO>i^TAoiviLgFZvdg~M#@&ug!Soz zB#|cAz@9C+MfBx<=>dF%)J1bo5vC!?>WVAk-T*Cd$qeGu{*E%;;4te?W!lx9t81iw zQ%`Un4JUHAtU2e-<4Y7alr6uL%9U!XMpaxkWPvbqx?4PTk`s$w(&OPj)3+eEsbyD+ z%Sz4Gm@hv>=?$9SLL;T2p)qPSdlFc6etta|g?~y~jRsz1V-IqoX618POU4hb#rWWL0*O6Mu3(=)&t`!ukB3=5m-O zv!#I+Thu%!nR_2;4AV`6$W=7$rYr)|xNTPX9Piuq2nzMQ>u)54DbaB`&LjL~ifQc# zYNsbPYY?etnH*(42F_87&5nksBHs`2(plIWTI!J2w3&=Gda}MzO8=zG2HViK7nyC` zbARCC;EpK7N|kFNJn4FMfmrxZ&GuvXEA77SUB$C6C)Yw)>BzANSy2$FcJg7&R8cHM zLu_$z$SKpkgw(alvMy1MYPoj7+DbE{{e4t|lFjt1xC{pJ34Cpbmo(z< zKw630QyDm)*AxbFb_K3o`uE>wR-n=;epQ4Izl?G6tzkAa>^m-El5F?Z)Xm%HsSLEr zP;u*hUs({*mX%~zGJUWJUG&$Xkso)-5mMY((pKhUGl5A|z~9ZOsJ>K!FH>B#z|YIk zK9RU}GPc{pncLM~Z8Ml}twfqXxP{>eRtu1NqN0#LkMk$hWQ0hr=~v)G8}1&Ol5kD^ zhuTk*9Y99UKW(5ZNLF-xd_vL6dTb??k-|x<34)Z4&F3ncLyLWIFB4-AuYJ2h?={M> zE5^mf+LxE8#Mvj^Aq|b0v{c#`A0uaaBWMvM$m(h}Hu36qpn;TCvg9`7h3=K7c7zcN z?dt(l5Z|6RXwHhvjhK1vKKuSO@K{?TvvbzR`!K{E$p!-GrOzF$kQ94KmQKXi5;Pl@ zf!$E5_V9+Ce_Wk2bDwd=T_}q_SI6xc8jT((35RcYpCs4#myCCt^F`r$x^5)|rZ7(GgOa zJc()$pbOMNFiBz4P%*XBVYnX|OLgM!d~sA+$)}D&Eu%8qGhh*Qly#mgKEaGty(w8e zQaL9=6HcGXtyyj#cFjhnV!TWP!H=lIx_3n9(PCd5L!9fSeM0S9*=|an@iE$VS$Ln^ zw}6evCzY?EJfx_edk8H3i7tha#3crPMsN`)yj>uEK2q;9-ZTv-A3&|1o;!+W3NMo2 zvOLo$Bzvw&A>tyu@ojl`saRx)Ibd(SO9j{PcBZ2br$gOQxoKdqc;O~Y#? zujRa=Kh(O_ld#V~%$rLvZrszl?=8h0sCg!}7^`LIRSwM{((@7F<37@(#X|pD2y1J| zWIl9<%(`rhRCHX>H-EU-iXgPb<8Bi!&0vk1Vj2~4pono&&f4;_RK$~6N#pgG6==-d zjMGCfi*OYETIFZ*bP;|aHh25rR+W*J`@O(KL?KaIxbJ;i<9+diUB_?0xBMP0iS;$Y z3#B3-koG|g#km3;r5J8CI$~EQ3Z18J{~D4cKe0K;W>l_ckfv4VG}ous$Y(k`H9qD?3^RzxID7a%0<)9O#*6?rdi?{1QIsVfSY zwlCSWe)hghj^;pyl$<3AzqYJ!7&ce6C$pHswh_Eq*2s9qUTB10Ar+r2SdV2nxOVw1 z*JKb?g(wmec@>scr`9GO0jZAr416C0y!`Oed3YSm+jb=~u<)RtA?CH^?|Bv%PE$v|PVM7#T%OjmyqYS+9B@o-;P{?T(3Hr3M2Tswf^ zr%L%uQ0bj$1c9NkecnQ88MbuQm1P)-T#XE3_*IF3~qVg&zfLFJy1h=xS^$W+W$&r41{sg5!&Pkh9z>jB+FpI-0qa zUO}^#6$Ld~(+Z<8tm4Y%kqr5cx<l(i0`_+#!OOk5$fKM1IA9`t&A1rro?a> ztJta)W$e+pb>xi4TLXQxS)&`cf%{Y z-l2Oo)(od%OkfE!%O%p7A!b_pK`k_GnjM(@r!#bUM;IL3-=%ztT637SW_r$%QuTqt z!=CBaBLDYv$_<4uPb=&)4ZMOI`RZ)8D7tsixOy_zBONdzdZQ{;?+=(Tli}S)sr+0 zZ`(?+w7LvkN?jd-O{w?Z`hCTGJ1<~BfTqJOnkuslp?F8kP&`l@`l}w>HO-8pn}>ax z(YO|eleyjKdCIG^Dqe`SctWROT(MS>eEv1R-htv zq%+N`@@iX=W}IYdEhKU@ZsUChV(}A9m%A79fGxf)WPeWo|;$ILLOOFsDikv zzX5~neuF+z?~V?8$ijTEYy1yglvBJ(*R3H&BGa7vz>o}D!mx$)&NM|i@|C0=v=?9H zhSJjfmQcpPCIPY@3+s^3(n$jRFlz;G$G$#J()v`z8l5e}@#9|d^9gh(Hm^~TRF$5; z2h9tCA#~QRV3E?uP(Irt$FdGOQ@@?rwnPLDW5{)QI^{6ijPj}ObP^Q{QvIBRYJl%} zVI|J6jcl8Fev2dW1{-ws%aK3J zjTB9kgIc56@COB5Mm3B}D@Khbqx~Y+WOW$g&3=5#%G2d9cPI$U{VMAzH~P#KK2~Hz zqep=46|R=xp5b>+7RWaB+T5;pOUtJxQfu0jYfF(%f1m)fmzNfpy;jVO(I_dN+6;;_ z(mL!nNB2~7R$10zd?|nh@9^*&pR{K*bVt=t;i~XC(6}in?hL_HvQ@?mmCU6uE} z`dc2Z3+C6wXKcp9VsrcP4vJtErrAiY=(9c?N}gq2iVy^@T_WvqN59 zgv9A2oQ+1ZjM930#r+pdv~#YJ_P&ZbbV7-Y)yWq&&pvX$%5ZbbSU^M5o4Y*^dkI_V z!5K&D=3-U&nk*zCcc%XNQ^aZhOZ3XVj#N_>yWhY_@`m$MLASC9Bg7<@*!l0nYwWed z^Dj7ldGZdAE7=;h>gr>xB)*ikMLV@EY95AC z?!;#Pwls*LMzv^4*mRvy5-ab9H+-CYQeh~hFsASE1bv+9j9=d2u0VsS()LQ3iP6V) zi_{q1r1ZMBjXLL!!HdL-*_Pmd(elG4%K`Omd|eB1@R)X%<^g6hp<6MuV!bR{aG1PI zxym#@Xv5WOH{)8fh}brgJ{!}xYkcm(Q*ao?GYPOT*2RBs5jsJg%=6}7%14lkJbxo7yf zO_wYDxbYYa@v@hoZtJNmY-M%PWS-LT@Qa!)>j4?ED>h^C)ThtqDLW_|B}r`TAecS` zV|?S89Ru&232$h_$VAWc4yJLn;rA#zsrlM;aB+_1(!!t;CW%1EsJqomOy)JM6o!BQyfWAF&JN_oEQtRgK#%(9lYr(Ef@V$Fn6@CTJh2+kS@UX z57HlXQXHIx>$+=eJblJED)t7P2?-BHW)9KU`|0OxL%H!p?Mvr(tFfi);Uw*%1$D3| z3?o^9m6k2#TUR1~pYU$jQS4^I?e1zGUhcZzef$UdylgTKU4|UzSlc6G!wR})*fN!a z(BAB}rCIosT%&~vxDz*C0e1rzC;nz$1*zVTRtEW%&KCAPp`#hf<>`35#{u5HCPqui z16{k>SMnF&HagH&i~|nRe$;%^bG1_%zn(OZ|3cv^E$nIr05` z_qV3B3fH4A>U!MXi7%ACo|czPX>%v&`ctWV2~1Z3AZ@Kv^lc=D1$~@#KP1PrnxPL) zSoIdNbOvecf**D$8?r=wqTafg{#ckMn>UscoZvK&YKy}>g``JvRR(-LJBr&bGuyvN z7;*h#m7s%Jfs*1!H*Ty1C#kUw!zb3P%Ho+fPhAKi2+OshmMyyK8ZRIA_-Op`aOl*3 zS<|t1NN1?kF;D+DfL5UGsmHTMI61|`ZZFH!re0U|RDu40-M4&^kIBV~%|V+~HyVWM zEQ*tE4!zmRBU+ zq{b&90euZ=R<$*#P@FjH=e)ZX;PLjhq5o{70!5SWcR|M?FsB`<9O*TxCt zurivnNq7yQrN1=T6jA3N8Hk>06>fH)l^$v#aN4aRQoWt=1N3Q>)v zATqE-Q4OqusHe?Trtz9oiqu8d#emra%4;F@x;hAlbPo&L8GY5%2F@JOv6+sfG)P?~ zsAZRxAxSZZ6#OY%p#al|KVok-g7+2CosHwV=^kQltRF`3h)>n6BgE%JR7Mh(@=!;kU8WDY2+eFft^|TTK?^6ySFEGR;*zRuErMJ zAe~ex!|^kpR6-N7r(7`B?&)$P(CElKjL3aUFz!I&xUV*Z(@A?wVfN54>r%c4;$Eod zm2ZnmOY<-8t1?ZR_QEGz*`8`na<_^_S6D64_zh^8JtnSMa3253e=L{-qx>nQ`;*&# ztXiYulGON5IvU0)@tBIR5cUI=Kup9f49u_Z5%7;y_ zTp^qxC)@&?1@^Oc2D>{HAkr`LR_B_6GK8_l%Zwc#s%o9#@Lt(zDHwS38l zE6c(cfYC_VLtQ_DChglc`JTa$2)?So<=~CLJso(CDJhjIfhh0VaN;$`b2ap;`AQp+ z%@w(q;OU$cLwzxB{k=p$Ew-j=d*-enF-1$U94ZEq&ks5l|4+hNBt=r;Hb+zjnqI#k zeuBRNStXp2*3|p;rSVL53EYHKF?aQxvr`1jNsGDBS!Q(Ut8ws9uQ6gIb~kE*4pM|f ziow|`(y{tzHm8#ydpmPYp1!gM-(Vc|&X=DdilMVSTp8%iOb(Bg5{nrAbKQdouhwTx za4tS3HBxTNUusD=v7rgYRey__X!dH7uqpTM{}Fx^*DMtq($nVr)DwfY-)Y{7-eP<1 z6aI$?lv(0Xrw={^eDuSSjJDoL9I3y7G3q>oO;WhFC!-VG&EyGJPDN?)lc{>YXo`$c z%Nr_myQG+#GAlDwvYOKuZx_>SH!MqPVKAI#EA~thTQ^rl)8#@NetNt==+e(QmY%YafO{KmzM=hTN-Qz_t~qPQYVslXtFUO<`^$P z*uJRm9s6X3bPPA|I!TQ2sIyu_NfgseaGInRS!g&WC^wH@*e0!6S~)2B9a8Yy!nRl~+7@KC zB|*A$hRsOpE7r`iPV13@sRGE&h@C1O7dd}T2CKz*@V5rkb2;%OI|%`Qo&JcD^I*mP zXANpOUV)OKl)ynltQ-R4D#n~3l{qm2%oe&ejNfWrn{P~fCP;RiF0H(4$>e0T;5>>Ro?q$_%vbju{Pi5Iy3~j6bXLlUu1$?SKw0_4 z?36nYo?*x??xAZtK(8yWrmz2*lhFlA;ed>%en~0REkX%ELfC_RZ!%s(2hSEl-2@|F zy1K3EFV~p`m%g(f3uhu3uccg}q+LFn492f4)RsuymNo80EJ9+ zxquTQ;kM$}Tr;z9kHcNbe$CFZDhx#XWzZe+$3+=#fKEPqt90;;k5yWx)d8Ry_>CK*Hg2choNFhWR zlLJ;Dil$_%YZr4EJ*o#M5q;5UtU>yvyM5g-f`Cq(qZDL&&8m|_8_$p}b}Aje8FVQ7ws75IPw^3E>6Ac039x3&3Y@+mwq6HjVbi0mM-^1<4(>DvG(u%tVSM^Pi-j zzw|Dj%Aor(K7~Eai-E8YT3_x~=GdJ9a%NIYk?sgW)>ZlelzH+&YdKfiz0%f9t{mO= zx zW3mzo!a`6Zs?)c)EMWXLx~xklPr~iOnu%vo1uZ8?CeB#yW{f}vjT1N3)L)w3Xl)hC z_DsBs?n$M~!Q{h3CC(0z5p825^uM_V>1yzyP`vloL|BxHXW1~zjk9vtB_I=rWDJ}j z{CwXmSdzUT!w$9+A z+_)B9Tr7)D6$9&D!c8gLHmTU^HDl{m+G~+OCC%j(#K;c(Hq_a{>IprvO~T#n&`UJC z!WcUcr&kGGFD!H@k8AjFa6WA&t|k4|Zvd0g)Ze8v(G#IBtdC~r(54~9ZT^A8{zX+_ z@2_@f?TvN$gDhFGB35Q;tsm4&A!&y-Ox>HejIemkU%NGSxy-!NzRurG>ZS(W1y1DL z_Ej=hNt;++s!$Qj7sJ@MPe4zhO$#riUq|98*Aq+**^BQOqqRl}t%7jNVEvThT z`Rq|CT2QO3KltYsUyQ556&JhuvGFvgY`kunD;W-$L~cB+#1psY(hWi;Q*DPTp^F+C zzfF&+$Y&=m4V1vACRK+PX)lxyAdE&eEx!TC&BYS22eq1c^k$V@Hu|um{7ac&Rsx8Y z5YZ0LDAP%4Pe%!TzEPlAgQEBs`$9R2lsUk>^BsbEwRJQSwI$J4MInTe_}G|BDcKqR zMYN#L>f52#P@3|!jVIMf&yEyPp7)A^6Zr>mPQ8Bv{cfc?WfyVOXzf47sq(b(Eh0Z! z9+VGXH>*!Ye|}%~o-K;vT;|)4o$$oTQW*}abj{>2!b!EuU3(4o3_}lVAEurqWdO{? zFU2t#8*g0)-UL}UH<^i86j$q&O97I<;#!3x>95ZvW2x1X%wJ5siNdMc?;?T~E9B4#$XMzU6aO^A=WH9F>hGfq zjKU)`G7R{s9FZ^X7w*F8K4`D``IHamP#iL2ojRa&N1K+2Xx`g+&p4+vx>0_ZCCp3~vrEk@#_S;$RUdAVx~)Ph1VSEbGJ3a(Dr>Lv z6WtyUQ(%7m&`?%6H+t)x$ESj5ru$fR9Ebx`S$}X`ET)QBK2ocaK2wfj9IS?|f@}I3 zTjGd@ZI?aDM7@d%X})?gCwi1J6`JA_xDe6ad951!-X*rG2#Q`^ z7l+7EG)oe^Rc#W~F}QNfT=$%^HZ0Y{29LPW!a_!J!Cjv^dTzaNE^AEoBaf3Ly!-!vU zpNQys4>_)DPqJ=7?2{YdH?>odU$k~bt3G+B3RNH#jU-Ba*=K9!9Bo5A$A2vs`_vgv ze-+ywab=*af7o>f9TzVQdrri+G1J|?Ygc%3UzN9p%`3qw@J=zXgOx&lfKTU03? zi@p_zXUtt)WdYG-`OS+CW+QM^tHJEnkKQ@kleX-A0@JV$oW-J)e8o@p4MQ~Ac~(0s zl-kYq@Lu6+befwR4|wacAY)~?N$HdJG4V(THX!qjeJAbrD>I^<&+;CNM5IR255-Vd zAU48oVfIqzJv|H1iKSZ+G~9^({+)U_J&mrZBg-p6S+ka||0}u0eE$4EzWk-MD-y}j zdOu;QfAXhxtcM^=b6afubV6M2{g)OWj?z#@2&w#0_zF*`fhY`p@i^6Xtad(L*<|KH z<2Iu+1U{>nNrr|~8#pl$Fh_@1=ZRnJQi|HtF={>UXHGD<#K4CbS1pKB`oHPH8s`%fhDGkk;ccMO}KJ@@Z&P z83Ni;rkS6etrQ$^*2ODL7}2BuiNdTDGsJu%fs7W?lr?GsqfZa-oJo^K3*QsQ`}LUf zy8mJP-C$C@d_3D0I;_OA?;xfTtF=UP981%ibS1m>u_;1l?Ov85g;_gj;#=L!caHNP z_<}Od_JwS}s%u{RAdl2F>BOcOBTvdM;c@wvmS~V>1<@Y7e-$BDG|HFeg1xvOfhXZ0 zKpNcdk?}sYyu(@9ti*7M4BLL&CN8{B~60b}R5$h=Zl> z8LP}pC${v$t7uy*Oa*f>`oey%h{DrN2g47xpZC5>S2du~;W2vAo8fm>QfyaWJ!yqQ zM@eI_q1Z1R^h356XE_Zwu3-48%5bNiC*o_Y6&57Nm3m+O!=>6;^PtZVjo&C}G#v4e zkW13OdoE8c7}ocAxqI?NV(H$Cos6`6*S;wZu|tE#y~JS`Z9OeMsdxL78VzIOY#DrW zYdPQ5u9S{PRAS> zU+gyHU;Fq9Pk4Pz0E9Gom*V8x3oxR?B?l}~`Xh0k?L54wAtJ{1f3+>}>%oMZN= z$@5|e^JGW7uoF?uKhf=d6Azir9DwOY~u61C# zb2bzD^p{kp36tgN6;kugHEI049VK^>CQm7(FOwL2xX8y}Of#65CJ(xYSSaS1vbI4B5PVM5zWvqqa$#sj%nlVFE<}h6viFW50o2k zpE!~OqH-(Lm!kaDMNL@Fc*c_viqeu@OJXuh-mLYXZnTCi1tr)V6gzlHo>T2FRy_MK zu^Y(`fFta|q_!~g|uGN~gGj>lKr#I!CWi!|!PuwnrLdLhCZibxkA? zR%J0V-HZ7ev!u>{+913a8eo){d-H-ZCKo_?DVdp*EmxEKSlJLiip5LLMN;@PAvMQS+dG9pc;|LPuO?B% z?qHta%vwH^#(GH(aB4)jCs+}x!IaI^W~B7Ck zM-NL=KukmX%USYq_}GVqNWcIz+OU7Hpg&3+G+HPC15p1OE6_q84M4Oh613n)An=bK z_Q$ydBKT1cf`$C!1P%lL3z8y93zh)DX#cTK0T6NiV0s0#g1&4+com{^9>`KX6!z1Plj`mq$vRGk>o#N340`fMw|8+*hh%uzgPd`C*(1V zsI*XtKm8O50C=={G=RgPaNtq@Zw`+OK_8Wmf(8SF5dUrbXZVjiZvRpK8UF43q4z&P z(*G~T%zw(AasHp>&i}S}%qu^>yZo40Mg%{?{$!T_TV5GQqNEx2g>!IzPd()FKc&o% zu73m9cu&6Bd=Qav7}2a431gz4sY}+*4b7c@}J-KfqUgwjzFLv2)-26FwlikaLM7 z@2}mC!MG%BW3~(9Q-7hSs8bXpZt;C*k{RyoRL-@^|Dp-w#)Z$JnNEnW6<@+v%n>C| zD|tWyLrm~_5n-yi-vH-hC7k5dAtS~N^ce*3M^`z!%DChzn z5`bX`ppK*31s+GGaePEX0K1d)pU2vx^p5qqX8yub-#Ao#<2H-CB>nB->v|Z&p}Tp7 z-wVsc7BTF+c1+r{zW6vaaKNe!P@j{f}+aq;t zUyO{+1bM@^l?`JLeYhs04l;`}u5~;Z|F8$oY+??_PYD4Z z5+heD2b?{isr^j5GBY{)NKBk?0-V()+>PQuWN@K&@>nmohk)?*#Cgs=bN};+r-EI( zL=z*sS6v=l5{zH6$;Ds&I=CIH!y4;fe2bCfd5d&F`2MJ8yAZiC_7iBNWbV1SNnENl zg+v^WE~1XRN;%U`;wYvtrt|eMu>{yhG}(QJ|L0xQ^Q7YgD2L+b8~&9kbKTYSDN8`h#uO-6c7ji0%$(h~B6fcZ<_ZI}1f(W{{k z<-WqfkvgK_zG#jPVRpYQ5g86m(Sz2t=f45s`gyYYHysb$?l1RnP|S;@vqI*}pIU;o zLo7DrBqn~Od}`t*Z*ysnJVDB7s70H%4oQl(Xva5B56nwDvEPu>EBTuG`LZBVM6dwn z+h4CCfi}T$;EU7G>2=t1SoMaFat3cAP8xT&)Jywwv=sGXmBn3{v+oxIxIf<6%P;eN zd5o@gE3_FQGrXAk^T79SsC1PRlf3)=_*OCc&i&laVXL$u zQHWmqv%sUK=6X!xI$58yVXVXq8YzwN_T)xf2l5DPqk0QwVAiU*b0xReg4b>pvv%l| zX#wA?(`o62`$x|&_KWVNq^Ko{wTvKCCM3Q+C@&ecFvljxha$nr>pj=yqp#t}*}P8| zi+<)7-@m9XLddkHqWGk{52Y8ZxLb+R*7FJV6iGy3B8wP~Lx9CaRI8SiGc=ov8@@kv z!)14S^+RP-rW{5pffe6n&ET@%#)#2)5D|`|vL>N|U#RiS^PJ)*wwYNe)uq|cE zTbFNnjPf6D!F}n-#i?Y)!S_YGR71J4dBL?8i%9R%6;VTTP-!)6{UonRalFW}hf{Ic z2lOKFUR}O<^%g!yVeGi@B&@q@a4q^X5=gxs8kNfsYv?}FHGkIG z+zveI6)7qr65ZuPy?SvTa_D{(c=S#1P@7{$Xx4T9@^8S8*^NGc?&ko7d#Z`)Q1wuJ zUG<2`76C`wy3`t2)Z&M%^iocP4+MEmD7Fpa5r*p);q=w`)z5WtJ^HSk&F}?QkdAbq zMWmC^14Z$-**>+9T!H@Su<}cUV4*qrso%gR4mN+$OZn3wFn7(!-m{V^68kS1^xfLiA$-ZZzGE za=!&dR?rz{aW+cB$W3IAFtmY!CCccwY_?76OA{}3g+DVqXB0Jcoey70zZyVt#Uqp~;Vd4nT{2R zS-+>F5=0J)a_i2b|#Ny-J!VuKZ&njRt(}Fc%{XpCX?)6f87NbrzZG zjM4q`D6m$#!vY_|J^nMe{%``d>wpJa*ZyD|3)}w5o_LU#!1lmF6rkkq73%2VZq0-; z@Z>~?#RMcUiLP4IJ4-kNv)pBvLf0WZvQ-KpMdf9Em?(A~fmJgr1T{GOm{O&hFI7p%>MXq7yW z>E{rKgWCjjU6>>Zdlxq$;6K?6J6h>;(kAAb1qU!thy$$Yp47v|@&R!K6PS!B-)74q zRfgl(>GEWNAZzi zQ;_Z%%P)msa<;VQbK>m)Xlqp)In#XDagHQig0SR~v2<~ud+~)JQD~5(Zo7@rea6G{ z+a#Lq3{!23T_RMz@!S(+9m*?|xO$Q2FEbO^;UqS559Xh&5ZUmI_1{Zl@UX9g0xbk+ zq?l0=Ae*+JvjxpsO2CUL2g$`7b~T70(6{tBx%A0x&hOkXB!L|Xd4G$8sbiv4c75L>fkNh z1Vh}p>vGcVtrThscouf@!TWJUr;&~T^rKg%27;IqsJSqbhT_E_1whHAzHG1&S@5ks z**XXfLUN#Cd3N-Fud2$LJP-* z6}2h1XAdz-P@)<{L{H?Vx%}a-3~y|m$HqIX%UJ9x)ANl`B6Qxge|b^}x)kma*N(Vu z$u{R|{{Z~v_%@T2QQh{QL&*+NBtpnsSQR$#Uu=ag>I3IoVoy?siVuOvvCLH3t|QWp z?uz@A)|C|-tFS}~R6xab(+L$6Km*B95^UgC-N8HF9PqlvDl=`zvRnuUe$|K282~D- zgB2Ii!R8VDwMCt@JQ6XTIanOpxc0O%D|$a8&}-MSg*jOATWl6APpbMLl?s5 z9~JLH9|e)uC6nF*(e7tf)WhhU`C>!ZEBIIOg0HS{KNR@IC8occUZ37EDWd`ikP9~} z+-x%(Tr!BvH5?PV8EO`;1A{=<5T}IkkD{Uz&mArckAEPl2=u<0a@3;?m6R5*Q_J@^ z)eOsFT2unff*>+>xG9@JP(c)dY6XQX*b}p1IOG20lGcxear|U8^lADrtGCdT>7Y;m zNe`ePYEg`1?vi=bjTIA+E(DYd^unZ+q!2Y(3h1y55<{*RsmN304pnGTJ{PA?%zy2td5%Q z!u{K--6S4(0tAt5pIh+$?$DZR+J4fLV9P zbSMwxkf6?+2>Xyd0a^!b@~xO0(6*Ui=eN;{xRYUxTY=_k44Yh z{8R;49dL;-CJzBD(4bV85+vyBh!L1o5R7%^Ehe!k0oq;P@TxWr#s<1q%kzLZJ`0LZ z{r>=%MfRizb$H3@fY1QC29u{;X&o3rLt_%5M^v!NB8{}HSv!KPqQhc@9?u2Y0XSL6 zY@}&T3N;Z>G&q=`f*KJV2pfIEoKY4Pp&=7(aX4v#Nh&RX;0kiQI^wesoIBG`P;vb% zVK+gd>%sGbxsfy|G(%9Wc1Zog0Lm2bwum?hHrZ<*Zwe1xVWBp5E-KA#u}H8TcNAWC zdFo(~fA?@(G<@glJO``)0Jx}ebAW?HO(x8J(5q0W2&kw81PY~RqyQBGvyG&+u44EF zBr#1}5A7X@uxKL%I%0j3B12mM9IjI1viE;LY>N@J>Lh^O1wwSKPDvg=kTS6&8IsH` z=H)EA$IL;hUI2-D0bZEwx=~R@w&SZp?*+wncQl#1+J|?qJH1XYhVTQep{cUyy3Ys= z5YWpKFeMtHc0vf~!_elQKMAWkE3vN;+r8ut;uKxrUW{ONfK;DXW%A(aAUU#a~@hoQtDu8eG;Jns_QTc z(=joc37rKdju7B5IwUMW2HNB-2>BF)P$C;vSRN3ivMl99$U78!`$p)bfYdn`lqepK z#kx|UZz3%$4uB0G!Qbkz2}DMNQDDiHlZGFFLJ@3sbl^-#**XtR8@-=W8ne& z3*X0UZ-`wFb)eyY#BQ>*D^(e(!vR{0JlZAY;CFY%Gp`4%A_stXxFLhs1G5YKx;lLZ zCs7i#yJ&j}aQ0qeT@xL(bj7e188oQ^LU7X|l6OVaen8zr!$!78rk>41hR6P@#-v@AZI49f|p!W8guu?WuXMT!?>4pkP7^(y3<6(=!mz zZL2z%85Ri@saUoJ_JXlQxoGJ~nl`&iI$qX_pw|Ne%xRJ2cZzzUckbKgR84&ox6@eZ zRf2+`fB-(llL~{p(`Z5psPr-dnu3W;QM^ZxHfnGKNFv<~vFRp$O|53L?UgFLuvG^}wrjC*WR2Rq3@p8~^|T zz+z&rJPsdti`4ZG;yj}InV=bW)@FJTLg zXn+6#z-F}u1TmvQ2B5W-SL}9+1ga;al88@S2AqLK)@*xyQLcd)8zUOjIS6YM0lJSP zaJ2*`LS0_xLHH3%RHh=9{743h?SED@lsuNV{KNVEn4moRdjVq5Hzi*-4P zaI9gn1BpWjNU#-{#c6dA{Wu{uoR2?2s$yvXEW4fZEIMaf6b}i!6RS{&@(#^5QIet) zxH(+upg91tpqH5_1FZ(r&1sTafdDRTRXjy>-aQXOJ99+$Eja0nBeg^b1klwN&-u{~ zjn9CH#+4`%3mERwQ`}w+*IFL`09LStoa%(cYDH`$X%27#ETHmOUr(ZIS-%1Hz?}oz z>{1hZ?T4O9pn|tUXsfkcS^`+!-3aRRf3Kwvr3nzO3q}wMV4+F{VzGfhHPa&N0XK;z zlms|twU-gpXwd-Zpp>u~jFQ3cfMpYJY&`?foI#sHZ7M|`c(u9;a1=Y19qdHP%DPn4 zAh$`N2yaUU`1-(8z95ThsPEcsw}J<9f}qgyiJ&M~K$}g*+U-{%WBQ7^g4H8J;ZcKQ z$`ri@5Lc2UJ1>0djUf?0kyqLn;i?jfX$1HC(EB1t5*29GKv-J|wTzDOJI7~-Szbfs zE+=LLL=^%Pf!Rp!5dnFz@*)iaSV$aD+SH&*0nk%{F9pX|?rptO>4*#tK|n~^LOM&- z!J5eJlNE9hyt2E;+i?lm)ngrD)v>ok8UXbM6!Q#EG@k(|2*eY}fAms_@ZbOjgZ}`{ GAOG2%U?0-} literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_19.jpg b/docs/img/chapter_picture_19.jpg new file mode 100644 index 0000000000000000000000000000000000000000..742981d298c881766b603b228294c4f66b62b6dc GIT binary patch literal 20579 zcmce-1C(UVwl11w+qP}nwv8^^>auOywyV2r+tp=Pm#x0F&pG>?|L*;d`^FpNz84uI zGcx8kznBqg<%)pFg91<2978V8?1_llu84(U12_6Op5gic;1r-eq4Hf|d6CD*385IrnA0$8^Upk;* zP+(wCsBkcFsQ<6ipMC%mBoI5$83+&(02m1f1PSQRAOH^l1ONv4hq-`%CUA%^6G4E1 zpuWVYf2sd3`JZ(FEXbD^jtHg0&dK$zYzn#xNQ*z0%Bemxknp%{{}z+fr_07Fu@LdrM)Zg{~G{i z*gTknjw!%pFB8vzP$J0xcL2Z`*G}b#sDhL<_*$T_ET4Du zCe#}_=;B|IK-YfUa#(eIGfAvzc(10YNYi3YJ}Ab&xqJT&dFzZdX+}grY&LmfQjIX5 zPQ53uAgPpVAo#CHSmhWl(HkbYFByahCMe=+!mY)(1Sf6Vf8)I4dOi%~mJPQw#S_&H zQItVId0DlakUcBp*7_GLsAzFicv_}%0@vaiii(Ha*qkY|>w&oa3OIjDekIB{|^#Lacy<>PTk&b-TuiSW)V6?*nWnn!lt z{f38Y>iY!5j2$q?omqrU&65dfB#ZDku+)NWZLKmcZ z;t*5qgW&(x1OOm1VtrCxplO%`1rVA}GV*2@2ut8~h>nbAN91r=GN#q|<3L6eb3A0E zuK%J1fY^;SNiPv}+QsFEVp1G-fO3$^!Wm0(VWinf_9;fDRo)%b^^CxLlD4W)4d zcYIQo$_YUzQ91xn1MPFFV)IwpuQI~GbstHIl?@E&6Z;51ZmoQ5E$smAV#+QHx#7P+ z0SGLED+!4`{PJeR!#}2(U;rRg>NCI>_k4AMS_1F=Hv|CSgIU`A zL5-3Q<+yaPI{G(&uTh4`sAhq^oBccC|BrQkUy}v{A^->s01Whx`R=RmmB4_2K|sL) z5XeXQJv&P(PGCK6;E-=C)pdE>#rui#zbW7}X zy-f>MgV%;B#_6smq&}g1n;7QBTK>A)7S%0~y(l5R#bXpDK>^3O7dSN>COWnuFSYBX zY&CiAM-4|6l>BP$iyljr$nP$iwsJzAI0Q!f!Q4~-3db%*S-(Eh>J&W)5Wljn&Yg(sZ!9_dEYq6c(<7+~) zZ{_E0{qSx9ENGa*2gu9L-9eJ<7TgspBJ0n`epM`UCxO2z=%6yN*AE)tEy9@TzbA^1 zb4iH@DD*o!Q2VJ%@G_5}duc2p2~WtWe1~du><*ZTZSH1ksv@yQX}Fr?pG>SyQ6q|b z?_blRSI04E!_JK%@5-Xr{M(prP0~L)$Sw%`EC91xL)( zXdl5=g$fe2wKVJhJwK_uJCR+JjE)bCI^g1xYOTj=JB zTeI8E1a!Z@8?B84FInF_ES;=Z9ZV7$bV|<^J1tAxYFG6ao*~&*)Eqxcbl@g8ee}vh z1=@>R;OB{DY_2qRkY@r>-ZvoEF}`3}M7j|^L!9qxlu>g`>r$R3GxR>z)?PPCj^W&g zea^nrhBmEWTXQ?&wmCr~;$A{4I;|L5m=Pm%pmRWR30~l?u6XJK_(>}V%{d$lO^Blx^icivEbI&BQgcmz$y z!@{QM%w$johB5cNYN-ZWF=NHWS=oo^gB=-hP;ZPm#i0htABjJaL%zJ5V$ATPPZE7DdfC@*aR###?n%5{E8T=UfP3q?r`woI49HTE%Gzh zz#;gsB%BBE&{7S3+7&5*YZrVju~W{xuLIVX974^wiJMbNB*oaU{(77P7l*qK5i4v9DLMS+fwP)J2`7iY5+@S(aKP@Wy-do=9Hstxrv)!NBGK&f#rOlbo zC?7=N%8WHVUxTnq-vElB3y&GfF~}DFRa~_Pf⋙`u9FgudE#v_ijF>75CoS4Qz3> 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 Ul8e}MKRx~O7R~qn0AoM@*-L{U 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 literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_3.jpg b/docs/img/chapter_picture_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..53923d98e1f3fe5c4515c5c4a581d0dcf978ca59 GIT binary patch literal 49660 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 literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_4.jpg b/docs/img/chapter_picture_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9deda2e6f8e4b0c78db5a84ab5bfed95b70791b0 GIT binary patch literal 23307 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 literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_5.jpg b/docs/img/chapter_picture_5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..76658b8bfb10e6d252a7bfa7d257942e19f13b2b GIT binary patch literal 26086 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 literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_6.jpg b/docs/img/chapter_picture_6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8af17dbc5777ee992850be7f6fab2115b3cbc054 GIT binary patch literal 27982 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 y%XBuCIzW52QF|5?ykWtXmH8ou9>;_u6e&d zQ+xmDUG+S*dP{e8^{ziFe|7+v3NrFC04OK`0P61n__Gd>1i-?;{Og2;frW#Gg@Z>% zfd4CKNXUrD7-*Q77-$&iSU7~ZSl9&E=ook;cmza5A3l7*#3dysB_<~%{y_XMCs43( zaPV;Os0awC#8??8Vana~xi;{Sv=jNuj{Q5t9K$)TMDJHXX0|h0MBc5)90fe>hNx?5M$6A2lZ}##r3;_A)u23{z`OuRzPh#I(j(l9 zM|=_x3FS5W4^?Y`kQPb08PutXaISk5hyntjpSZB_Bbi- zRt3X5rsqFQTq0u3m~N#5e0O*fZVW9QuX-CVV#uoL_shz>P$wRm0b+c zmBf5>!4z7JcV+=FPg)u^%HWIV+6gh44Bwd|9< zifO~u2k-x;WAv5M!q*gz4-_ET(FsRTBNW-HkkEb2>*Qy@v>fm`BPgBU4iAGpgpk&!OqgzdQ0oQmP-yT?5jH7rqsGJ%JXK z9_Qbaj8IjJB>WzE-DgYx;ii`*e~#BWP7w{spZo_H&zpxPOaOxywq@<#OaK%*=j13c z%~2|XKbZ%~xu@$~VQ3$-Wx?d^6uoSGh@4$S?~FJ=Cc-rdNuK&YGyt+%D%QM@_lDu% zj<>L$Y;oa_O>$@x3-V$CTxyw>xWxa0{;LU{%NaG3lXdToHFp@X(?1V=BZlEfSQwEolwHuHI#-{(*wxP#m&R3 zp{?ukH+elmUU{IT}<>b;zMO zWcy>Ez}2npFKGJSXrSzuGW9PDmc%6`DRTeJvNhqq6597 z=Gv8B5I`7#r}h!0rQM8Y-)!jDQ)jjX(0_vnYD`)^U0Q_w(&Nc0ay+~&+M}4#c}Bd4 zQdWoTQxtkoEZ{;cMVfeos8*IFTOuA)TthgSU%aE*KDnp%7soT+l-qiL^zAepLTyXG zKHxxv9;Qaz0$9(@jwT1Bf<5A-y&|7WP)DQ$6BH#$6pp8*q%kOsMB+(*&#WF6I;=KsZ)e>}2s0=U z$#SQS$If#ZDvVQpvuNL-kP~W7@>pU6%fe1ZNt+9PjWL-RSjD|-Jq9q!ESfG<{!$_+C)=Rc7dDQqYt6A2lAILVu(}PHLsi66BVs

      n(tZK!Aizy7} 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 H7cc+W*OACL literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_8.jpg b/docs/img/chapter_picture_8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e61d44b40edf3fefe6ad111e8cb87a7316205ef4 GIT binary patch literal 15702 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 literal 0 HcmV?d00001 diff --git a/docs/img/chapter_picture_9.jpg b/docs/img/chapter_picture_9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..efb3400153ac0c63f45f8da4329153c96bbc251d GIT binary patch literal 26842 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

      M0Py2NaKEjYVMMcG_}Sz3>Z^FIoy?5E@*iVNu&} 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 literal 0 HcmV?d00001 diff --git a/docs/img/colored-links.png b/docs/img/colored-links.png new file mode 100644 index 0000000000000000000000000000000000000000..ab4efaf0c9cf1a1af060677e9bad3a534743f1a7 GIT binary patch literal 3098 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 f&ifaUvA@A + +synchronous, single thread of controlsynchronous, two threads of controlasynchronous diff --git a/docs/img/controlflow-else.svg b/docs/img/controlflow-else.svg new file mode 100644 index 000000000..4faaf6b12 --- /dev/null +++ b/docs/img/controlflow-else.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/img/controlflow-if.svg b/docs/img/controlflow-if.svg new file mode 100644 index 000000000..618e410e6 --- /dev/null +++ b/docs/img/controlflow-if.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/docs/img/controlflow-loop.svg b/docs/img/controlflow-loop.svg new file mode 100644 index 000000000..b4ffe3f2f --- /dev/null +++ b/docs/img/controlflow-loop.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/docs/img/controlflow-nested-if.svg b/docs/img/controlflow-nested-if.svg new file mode 100644 index 000000000..f920ba22e --- /dev/null +++ b/docs/img/controlflow-nested-if.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/img/controlflow-straight.svg b/docs/img/controlflow-straight.svg new file mode 100644 index 000000000..82fe554d6 --- /dev/null +++ b/docs/img/controlflow-straight.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/docs/img/cos_sin.svg b/docs/img/cos_sin.svg new file mode 100644 index 000000000..bfde16fb7 --- /dev/null +++ b/docs/img/cos_sin.svg @@ -0,0 +1,11 @@ + + +cos(¼π)sin(¼π)cos(-⅔π)sin(-⅔π) diff --git a/docs/img/cover.jpg b/docs/img/cover.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8faf785817f13d6cebed3fa52a24fd37e28b3b38 GIT binary patch literal 99962 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 WwVsjmcJM(9fSFwSzT$$%?Y{xY!t>n# literal 0 HcmV?d00001 diff --git a/docs/img/display.png b/docs/img/display.png new file mode 100644 index 0000000000000000000000000000000000000000..0e56b88517755d95cc6b8d26de33da32ce4d810c GIT binary patch literal 4040 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 literal 0 HcmV?d00001 diff --git a/docs/img/drag-bar.png b/docs/img/drag-bar.png new file mode 100644 index 0000000000000000000000000000000000000000..aebf6f56b529b8f92fe891d6f7487d4658b38b5e GIT binary patch literal 4285 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 literal 0 HcmV?d00001 diff --git a/docs/img/exercise_shapes.png b/docs/img/exercise_shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..7028993322d7cf67f4ad5b2037872a5ac190e6b1 GIT binary patch literal 7904 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 I(vuhe1(sT$*Z=?k literal 0 HcmV?d00001 diff --git a/docs/img/flood-grid.svg b/docs/img/flood-grid.svg new file mode 100644 index 000000000..4cb0b075a --- /dev/null +++ b/docs/img/flood-grid.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/docs/img/form_fields.png b/docs/img/form_fields.png new file mode 100644 index 0000000000000000000000000000000000000000..667b3b6fa31727beed48f7cbc310bf625d975ab1 GIT binary patch literal 8246 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 literal 0 HcmV?d00001 diff --git a/docs/img/game-grid.svg b/docs/img/game-grid.svg new file mode 100644 index 000000000..954b028f7 --- /dev/null +++ b/docs/img/game-grid.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/docs/img/game_simpleLevel.png b/docs/img/game_simpleLevel.png new file mode 100644 index 0000000000000000000000000000000000000000..3f00e008fcb4d14ca94713f71ce219fd5f241b1a GIT binary patch literal 799 zcmeAS@N?(olHy`uVBq!ia0y~yVB7^{Z{uJBk_rmA`+T+W|N`{Sl8*zT7s(aHTnyhQcr)x|K_Q+9Ip{GW6X*(d zvY;sda#2hG8wfQ)p?$)~TeWlQzV4F!^C97#@r}95?3;fVXP)2pl01cmL_{>ks4S|Nr}^a4`c= z56D#wt(EC>bWeYc+xGpSFGwLw*B+sI$>*Y9FV3x8ze`r83E6GLcwb`UwO)!nP@!0Uf=+hxvpm z(48=)P!`NTacyDd9L5>&7$nB^z;Nk&^D5HsE*~GG4|1pwbo2>T2e#c)CIvG{ bd}V(;%V>|;`58ZeDVf33)z4*}Q$iB}WJDY% literal 0 HcmV?d00001 diff --git a/docs/img/generated/.keep b/docs/img/generated/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/img/generated/control-io.pdf b/docs/img/generated/control-io.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f3efa8892c161cdf9b0af840d85f1b58436e4243 GIT binary patch literal 7759 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 B)&u|m literal 0 HcmV?d00001 diff --git a/docs/img/generated/controlflow-loop.pdf b/docs/img/generated/controlflow-loop.pdf new file mode 100644 index 0000000000000000000000000000000000000000..beeffc1e3160910eae8ea638902beabbfc37c217 GIT binary patch literal 1332 zcmY!laB(;MvDf5m`j@AL3I#zDR^^(s4y9GxC*C~%Rv zpitJD#>dS5&(7~wU)uJlmQ|~^{{Hi4-s_m)V>|5j9>1M0*}s4CWB(_1NB$bu{_OIR zyg&Jo^126{vBwTiSz@sJL5b(pl_t#Gg{3hUq(z%kUaehd{q>!}vM|YeD{t|fDQFT4 zx+b|=Z3eq@qsXhB2e_~5Xta8*Wc=6D?)!GV`<)cy&g@jqo8dY$=j{A$6~ec$`iIlU zq$idRX(<=~8*yzd)sYiDWZ_<@e3!xK5u=3YOqCz!Dy$~$sTSs{<*LjLY26`hl_}S} z$?K+-)Xr@SYR)eBdXLF~_fx>6$f$Sc4swtce4 zal;9=3ad?j+K=Blc_(mX>pJd;UKO|S&h*LkJKy{|qbhw|?#^Dh*tXhJXE&N;RsQqr z`+8D0W`oAsyRHLo;1w)wJ+!mj$yUQ@sCir%t$ne9fiyISV9-=q8!-?jdb zf2sJF)n?6!Lax-j6llx=S)e!vMKp*8Mzx8tAxIv?GlHiGa2jx}C~*%i0mi?AjSWZu z2=o&TfE)#VXGaA?1^whiAPGzrKnWlTL{M`~;pQmlhg24%D(D9!rl*3<3`i_W%_{+F zgC#WI)RfFbr~C?qXrPdRf}yF2p1HAwnW4D?kY#LSXlQ1v5DV7ooS#<$)LX0oO1O|z z>RyyzT7cv_h-F|=n1h3XPAE#uD=q+8oLmVL_Eab-Dg`?UODwou<%;eM{cPEe% zh^O>}Qj7CTi;`1|6+l4~gycn#2Vj{8;l*OGL%lOoih6j+I2TBU)VwMJ$=wgNj#uf;%lA^@SoYW$4 z*%q8xl?rq+D4v7z^Gg&!9)~0!&%CsJ1yDqQW45>?v8V*>Lqh{g11?onSARDy0H8y< A+yDRo literal 0 HcmV?d00001 diff --git a/docs/img/generated/controlflow-nested-if.pdf b/docs/img/generated/controlflow-nested-if.pdf new file mode 100644 index 0000000000000000000000000000000000000000..61e6adc0091325783143a913473a122e23fc39fa GIT binary patch literal 1630 zcmY!laB(;MoWFYL&(_x<`0l9^{K-hAv?A2VTsg-Blq zGgp@O6*(^Ue|Gt4>US?J37qPZUH$iM`twZBEB5hrmwwB4zWb-+SFiZH<^KAox68fe zym+4UW#&AOwpX|0w-$bzaP7s19U4zhEp+RjGIh4q(_!pWZwiTa`P+tYHQ*&*=Mun zux#BP<~gxwRd_pZ!IjC0&%?B&wg!v0y=_X z^Qo|nJ=Wq3Ir;^k8e$H;%MJ79Z?N>Kmo9#>`_lPCHSL!hw$3PZ`JXDZv=X&<&O zuW+zk&Us%?cgIzyY$kEZ=Bs|mtUgjtA7&Vt9roGQC$o(6o0ZtCeMzTfoR5j#+QD|> z$>OsqegD*UAHCHxZQkK|k>`JzoWK3|QJlMc&xWGE2O{2b{F(n=Z=%Alg{losj2Z_P zDV@EZ=NH_m%oVP1GDc;7fVf#;fr?QHgU#ekcVq8tyY{|s^Q~=VCk`~6^VQCuUY;d6 zEADoo@cBD?6NL{)1}*H8ekXf0f+5fI*U{V+;RTar_8R_q`=lvPMC@wg*^;%=vvfjl z-kP|>%W2EK>HDTjf8QjzqjW1%>Li9D@>SV;CGih^n zOP}2l`H%5YL)})c)VvgE8V9mKxdW6XKr}E@m>XGwPEoM2 z0SN$sexd=8qoD8Xs9>m|pPUFJf%yk00VIJ4YK|$~90mQ5%7Rn{{eZ;uRIr%=iAAY- zB|vSkEa{t?l9}j~U!f2U6f#gSG`G+*voJC?H&y_$jLa+zEi4pb!CIa3^Gbkvixogw z9Fm{ii}FhgkX#3`3@i$Ba4^sbMTvRE1t5!)D`CQ(3MECQU?+imo{^vr5lQAq4uJT;Ik6-$CqErxHZa19q0xlUQ&E(f#$}*j!DRpk3TCFJ z#-<8s3Q#daLla?Tnxt?-?R!C$(6LDs63wD>x-% zN`7wa@)KpdQ8%?QIdl8si_D+Lr^afYeZ0SW=Rox8@%ol?laaFnyMnLot*w3UGaF{# zvAeOo>-{tN>4(>zzWGrz?_2t`9Iw4HbhLKj!^@vOb;tCjmZ3+d>o3eLI*WHEo^KoW z71n<(EUWE>TMfVUJu5fneTl}U6RU@nm6@S^uiwvpUKqGJwc7Z0eE;>YUwpLAJg-nE zwP_IlNi@kL??_~XbopLBnPqSfwJR@r1_fe5EG7k98qx6qw0Juhpjia32#+uDioeY~ zp>iJ3juf&Kv;;|0FK2>q>Ie)g)w7SLY|_E_{vW4h+Mi1I47ja}2l4b8XNnMd3T#+D-vm+RXCX3jdfuSP0 zVM5cW5ZMD7mKoJFC)rWka#hCD&CT993C>bE3y1MP;m`S77jG(%B_-fG^A^U?Md%006)$Wo_pQafbccfn6aI5HqMb1WiZ?&BfIj0=7rT6Y~KZpJ9fk znV@7Yd?W_CDRN`#qoKFFuQEq>cD`REVrM@tUcSbJ>a0U2zGoGhs3C_oiWe47mpf<) z1z0kbiBRN<#l*{yOR4r}ry(SLypKv!lz)gSCSfc1P>K~XAO-`827%g?1wPv`((8C}uL+UgKRN_?4^Y}oICOCcPa`#*!F8$n;`E^u9=g2!zQuI* zLN?GKro6^=_};Jv6ZXUIpj^xZnU20%Fr`ZQls?z!j1oIlM5v{FD6VBSh72?^YpH5? zGbz~4Hc9?0jdn!fD#Rx!oUFgK7{1Qr*lwk+q-~qMUjs?+zLuG(1A6PxClaEPZrfGC zgS~2ct5d5oy}GOjz`zIJk*%O3q_uOK>Is=fips@cBK`fY-j4KQ#UZ4kpiNO*Y}0P7 zsPcE_F5Oo;b1%5l5)EaA-p!`;#;>nlj&vaXv3|?v(l0(!J zE~$yp_*2UcD`#f?&Ud3R=Stm>Ouj)zY^X_$#4PMN;qy#}AdfZ|6O0(jsniEu`BPjE zpI4!5PB+A02x^Q?Z^n^at!)qz;F>w0t<$kPG(Y z<~l3qQw)B))tX?4Un7K-y4gQCi4j>2&68F#Qq+~x;65P;jN&C#Y7y^IeBGHjl1^(m z6V1B5oD^Tli6tm7bXo$U+S|*c%)FABD3uBxF2nSJES2O^Qy?XaD!I|jv@l&%vw_I) zzha?JEG(lz9L)b%Ah4^OrFFBOZhm1aikp}1=Kf6z^yk96U22k^uF~qRuw@1i5V#S* zKCECiSOUN*E(!nwSk1t&Z`k64-T3j%Dg||L{pD#3W;SLv0O!xedsD!T2ngW%T?B-Q zxPOXZMZy04(IlQfn*?Ch@N$FzSXIE5klW^|fSn<*+JHavu_{8$t-)eYPk=s5#0CKJ z@B)A!P8JX+!0&H4d%ygBJB)ybNr>jKdQKu{_L?jtYT-dgNx%$b!J{a z#j*fbXSdsi-1JHu_LKy~-P#PICM|aJ$RGMpgSbH5oXsFEfE(Sa{n9aBSo&|99Pp;q z|9O|YsprRb2n29&a`F7O?X_$BDCke%HmwRx56t;STbeab3hcwFE4*uYLKU>D$|l=2 zJSY-QvUo>U27rp8vbI*pWo|DfR)Jltm{!@XrdUX$sk!esB&Es;s*KdLS1b0ZdZ5m& z7++YTb_E`;pnvC*Nm4CaeW__ZFype#d+fX|*yO#<`yCq)l+w+#5c0&z&guedQ#+Hq z4TmylTlnB4!A?KV8?}si;q42`CF#!DF-}9NQ+2WVu^ieA9%tt{q(@PWQBp=^grs~5 z@lB6t+nli74k-)R?8vW0PJI&Or8@F0W|j`{dNOkid>5Brk&Ee}p<<`gX-8r7M~q+_ z;`bEO^)AJ?F8T4=oMQI`*38~}RiiFOmIYmMAX$FQm=ahLbJO2Jz98FYt27N-sw8Er3ks?>efji~MK7Aq6sT5_(%$ZQ06_30C2fMLv+ow<&c%lMbgD4yKk z9UQq4iTWy*j;41eJZ9f`eQzYn<%SMdLp=sNg|BKX?2DyxLhi)m_q%Q6g^-=l`ZK^OJpYY>iH6l07I(@^Q$Zfh|A@yg6@YiL~j=>i*L*no94 zzYpHVax?8)^v%@9;E!W11XTQPRvy8m-RM$OjWjY$W2xCRFJzL+uF}^fzfn>QOq&Fw zeoA3UwHiH%i9OnUC686_DuT0)QYb>tRg*46^yUrJahf-;CTx6+D<@_1t0CIOsG+u_ z4TsTmDtz$M}rL!o!n^F<{z@Nh@B*8W0gn3_4LUl7qLktCe?e%X#7* z|N5sos^u7E3}SC37HJ1w4}ox4QX0CdSlEX9We7s$g4&5&>uet4q2JR@;X!=f`_Wvy zyTb&O68_W$As`;Uy*ta1eBIn!n>6k+;@e(1DP{c=yRg1|Kxnly7C(0A^Ur58k5&oUOGTsF+NQEok@#w9rgJICaiA%j^|5f~$+pkSKnz83lDr7U zm-^vY7)cV?qk~V58^d%7G*q@nMTD8DfLu_<2Aw>F&n_{HTW?y|Cst z@FjR?0;HerBi9VT;k)h(+_SQDz7>dO};@QdSZ~9pZZbRrtYlH z=Hiea;bp7PRMTr9cR>xum&ueSH`~OV89h=5x`QA&1DEo}o?Xepo(){pSUu;9eB#og zEESxUv%)psBKEm)i+&G1MKx^x0cmc=1%y5qmTa5V)BLQ7&45B96=vIRCv>S&Mz__S zK2L+McF%|R5^<>;rzDvy$f&B*=0PqC?Qu|hdjve5G#F<;q);9QTwgq4BEa{N|{(hVDSOV{XQA7hv3L@rr7lQkD?rX%!9f6WR%4jI5(C0gga=yNwkZUJJBA1k4b6-Or z4f@_j`OQ&`4W5Q*8eP5yFu|@pyyz=xA^S70ST}<==0#~<8OW2*V(PY$Pl}L4<2@5R zX}tQpSYFHb#2Uo)f*0&9a_BZ99_PeX>x}C-C|YZW;v$2Y4XJ`E4|p4!KXvC~f1PTO zlqVGk1_VC3lNTocMtvhUgaCC1`zz@8T(=V z2+1Y)`eJb2z%Ovp6e{esHMz@3`&sfWc8(66#2E|l)oNy*;OgN+%H9@{rsooQ0cf^` zEQt*rIXx|}5xQ$S=%k|V>#03^bmxo~ovmdaEft)tNWpv(P>w$hR~0|01lDhM$C!4r zE1FbTW2VMIC69TGsFvxXZCIHfMr8XD8Il|65MSPvQ zjpd&1lZR2u1IwAct45y!PX)SgMOgzxpMn@i^_uS93w?&jw;sjeqL+$`TUHeTff{Mx zi12>)9na-HXWaDBO3&h-F(S%|{Q zYwO`u5Gg?bp(oJ?W#}+xtpa9mCi7(5_Qh z%08e;BOMMN)*a>Js>Wfgz#*jxK^hMY{2YS2Umw_7S_!%M@VQYz;Y*q1)9>xc;Qc-j z;qHfLv;!Cd<&ad7JSJ&rehmT%f3587kI{l9YTFvM!=HSB)GPzN`}LRuy)XB=2Em%z zg-*l-8PUP!i3e>`u}2*jHnubITbP|okyg^3-_|(z_nj&nnambaEZ{L_I%Lm!zLfVQ zx^WXUNV(zX5YR6WNlRdBz?QXI)W!kSSKSUHvx zcXIJWPwisk+eL#V>19_}?{bGA8sL~gj~z`SKzCU#r|ni!ejn( ze$N-dGP>>20e71Y<`u~@Gw|fbyxRn$nZEq z(1K`0??ILQ|3@Ur~xh=HwTlmm<-aM0#0pgt=P|3N^3o0 z9C}%cGwQta`pAwyTvjFaag9`1cxgje9_{2=#I+l#ps#v+=3T0^(M`ww#n#KQ&rX?y^0 zY>U5x^dCU}28{n#RD_{wuR|-~e#1Vlw*nS+6eD$H5R>G3d$5p3HQ7_JQ>={$0olc| zAwVvV)Wp)#(ggpc(r^2vV)I;%HUbUX_)Q+X7L-k?^VzLq;@Z!=G7u)QqL z@h49j#2qXK7J7$w^;W`*2ncXjc$`w3G?PAt!EzY3cZGR!4>JVC)#R1bWf@g86rm1KW;KYVn;rPCXQaUP)^=V1Iu#AT&3*bGneD)qF!K-e zXGvl|%tdB)P5?6q_Qeh20q_EU8jP~8U^{CwQ3p#q2!QQ2kEn~;jm^o!!+D#$Y1>Wu zChx649PB6qv9`2wy(#5ZqV5W@*8=eT$bOTM`I)%s32X{MK!6)t@K@XL#$f(A?fz24 zkK)`+U2n}`4Vd+LbMICv26lnmn%@6ArC2+=xQbhWoqzUU0sKcA$is76%J13+0RJ+R zB^_Wqo3(=_fK}VtLDa#;`lsMFpSHERtChBjSX<~|Ht27S3f2HH-CSx z?bp=J^0ScTc;Z{}VVq|Y3|8qtikN3e zT=EhS?i2hp0`6Rz?Lbx{ACSZ1$TnnA+&Y9>cM6|uS+w6FV7Dd3Zn5=Dg-oIC@wRI- zB7fsC|D4CR;Bh*?2g`l9yCT?#DlCR`Dy^D)NFu`0oZenOUR(MDYqVWha9zbKMNp%w zjh8O^YvRJs67!x(+FVy)&QD3$dmD~9+w%UI*sJ?U(F>G9WIKrmQwXzWwimpcWLCr3MA+VK zH3Vd#(FYMS93B<&zV+VR^-Y=WSF?h3-U&GqWCC5Jq!uqpJ^V##uDD+UIIV)GBg4oU za_teim=N+}1&F4>&`8Nd`*wId;fu3k33}0&KugT%BJ0+RY17UznJ%jJ6vJB5YfJJk(22{)ULDcF>djDc08-qKI;(y6}~X;2ZEx5EL7IU25~?eJP+zR zUiT;DC5;ZVhf}*580zYnXRx3Qm1&f_CbmS>hF$LGcidz2GG);nM~YZla1u~Tk9&q) zQiEN>gU!~Ajk#w*qeb4s6;9-HP+lDQ4S+%Sf@-wF6kX+ax(aPg2;S@NzI*Y#ArI#mEK$(M}zi+q-8NV zdFzzdiLI3gsojL^(Wy29>Zs~+%R_hf8lx4{?0k!6uvk<1W8k`|v4etja>9M)(_Lm~ zCq7&h@!%6)KF~XzMmX7i=1&qO{6WyK=*p!rbrxU9-GOeS&+#kj#74g-Yswny@OjDA zT(sH)Dm!rU#EIcbM0W0jjOV;NwJu^a;>Wx02IFQ6szXYf=H1MJ}tS>^>~I^6Aggxm1>QGxSJfyrrkE3{5WP_%tl*m)P<%ISbU zs%y@rY-5oUf55px3;s(xFFx!6w_R_1N{1PK%qw)U1Bc#!0RcY`IT#240)ae#qJSG} z_~+sGhxZ5gCpY|GW3HGMn^v}a&%gVkk<^Pj4I7G^xF!>Sj?D9c>r7&ArFhiNyS(+f z=2al7UCh-rdsE)P7jJ3uz^i2|@4k5i-VRBba_BRqE*)YWTR&-wtp55N-|2y~|&UXJ+8iY?PJ$ay}^Gm6|;|W}|=yaNULm(Qw{u zRRYUP5bbESrWkcW9O5Glcznzl567By9Oo_`%+^FZ~_780M7ohsjnt^3f_i}+D zI#~w`=#3g~&S4k~wT7LjFyJW)bE!aXVdsslpzLf8alS!Nbh73!#Axm6_2(^hH%CW1 zi2V(YyhVwBxDI|FFPJyu&y!snpw9+FTR(A(q2VuXg1CYRQS{P z!NtqQ_8V}C)Is;u7^^h${>~oV&_yMd5#N`3k5Gn&JO<9|g#zh=ci5=+U7u$wJe6ri z8QK|UxTl9GeZNPuP@`SCs6vZ374Q$Q0wQd=z$owi&+G^z3SKdsFMJ>=kt{ zjhtli+X)uq(NHJLq3Z0+Cx)vpNY$&>5zlglw7ng^ZO}jQ=>VL?hsODe8z5VXiM!%a zSiWE{z8@4THdnAX$Zhn%N3qaZ<`WB@{&l0*`*ls3rGn5X?@mW@q7bl6(#yoe*pd_l zRGY^M9!Ib9sLhF&Uz%HD%FVx=r8=e;MU7`K6;}>XKghucTd~ zyp&US=O->NcDHggmF+ii>zO2zB2ctEaP67v+%W3tE=K#Jn8l5sTwWBspLRxX?Vkk- zmz>#5Ly9ZgqbuvD?8m+?vtB!aD!JJGq{a>xAJ3B1@a@0gMH!D^{ZRdWh&B#7J@V3c zxz^V-eoMJheYO8`HbIkf$$?_Tw+Go-L0P40F`tRz(q|r_J2+{BOs0PM;(N{WA)XNT z5p_FyLCL@arVZ-wqd~Z>Jbb!%Jr(cjLBWdHJ`SuG{MKAAP|v(|sy-z5tYK?3Q@TF< zpgpMfS(%VY{yJuS2zi%5%0BBMLza}y>y?UUhJ~fMr>M|bd$Q*BaY)&6$ z4i8v~G-PGwQ1!f$w30rIR`eU5%T|l1N#1__z|Q+4`*J%2xu=i}_pp*v-p2##GwqOO z*&^&fG4?e&{x-ps#_(B7-EVk95xS%!oR_?wQ|odY`7bLn6~QAlBOJz=ugLQ5y)8q2 zKdX%WIT$a`cqEN!%2n(7jc-!GocKQtF7(U6{Wo z>OGbx<@foC9rkSf8Gvsw$E$wT$BEO59x2-(KelDE#|~@4!5;>D5BaU|Ho$_}P06Gm zKOwIXToP^}zBHvtYLj~Prmy>JIayXQM1L~Jj{BTF$#PB}YS_>O>WAh1pvTgm@$nucTyC4y@#pf+_C%k74+%dPHHn)!Vz|U0p^TBADOEsfX8Bc| zyw^07B4$?JnQ9HNNLt{%gM34-LQcIPpYHaxXuIOz4`FI-`2_g-(1F`Dqxtm?jnIQF zhS_%L{PcpORu>;}7|Lo8%lR*A z3G{G4{L9t6WW0>M<~ZgKve%(sCHCFE^7pCk(Ycm!?fObpg2oZFs>mc3@rRsSa7i`e&(r5eUCg^9HAezQrk6E*5S zR#Wd#M&Zuw!YLgZg`D-^Z*pZLPo7_=Y7GOaV`uT2Bn~=HMogbOk8`BCEn(IqZf89@ zICPEgE^YXj6l0RC{>m9gxmsVMb`jAy_;t?{fsvzy?b+y6k(?BGMLdR8wb6D10dn{3 zFTtxp0cd(*-WO;B?MC^HUolDZsPk3tDU(pFw7;6-Q1(mTo=6@%JacDg-#5weMYoty z^vfx(NV6w6gFjRe+D@;Y%FEaC?Hesj-ebFF&usOGX!y|OPTrf{lp`3a)VL1!ERov^ zlXETYP&m7;a|zX3m^t_RkoV+Drw+J|M*>$oc~_`W^T4Eu)+*^LXxk)owAfgleI0Pw zXPT`PjxwQRR%+5DoW&xfZ%khIJc$BJKNg9#yb@<1lfSXwg`cPq;}CyhY+OTIQz_Lz zRmj?ODrgu-*=gv+Y5QYY$(`35T?6k|znWWiPs~ZKTUU*$(s#>-qH2Xt<=%awlW<~C zEUM{(!r8v{La0&W$nd3#t5Mcn8wQPHN*@kRtC}~If{i;3XMUcK%`#@e$`{ zhqW48R^jrImQ5_SRrcUSt&VNx z%5mi~-%I~toJdYqXXeqavE01J_wa4Fl7{;FU$pB;88Uc?d%W?X4*r;y!=*=XK*j3L zyq*4iLc&s{B=&1d9kbro{_hZ<>GtLI{;NoQJr~KzSOFMFaK#3wANx03iKtJ`+DA@S*d^U0wXIM2VbD}5n2tfn_?DIZQ<1=X-IkWs2t zStF_^8?6zMx$ojk!^Znd|#^@&DUoe>4ik$y;zAp)YMCPL=H`*&DzbXAm)P- zvTN&!tunW^2C``E8O0eNnsF3)pRkT4ImMRNOPLW~N7pi1K+~3N(l`f7&px(zI!yCP z9he>!g7poCS!|BN4M>wcOW5Ah^_;eTDqm6_PY}CEjm$P8uGCRYeE$4R@e5`iK`De# zrPU^0y28xFoO?%G?>tlVA}!6E+$H=ZJiEdEPj?ILHdF91X`|k~8Z|{X!m%vy+}PnC zvxxk<_=%~_SVLHY8oO((K}PqztH@=$#_smr&Y2qGW@Rl)YgtB8*9FHSd3y|$)|aY+ z6@z)74Hn?9nEbeAme4a;(+fUamlzbgxxm$KSa83}&*p-lTdaX#%_={am&DAs+w>adA`a`E_6OLC2X9JHpt`W3Os1ak zS%*QRPs>6D*Om7j3IXCnJ;2_pYX1@hv1U?}`#hK_P6hqkZyDYZPa>r!;KkB@!yW_M z64RCgxgG)Y!xZ=s)}8`TtO(g2=)l<_xx-tuD%&I>Y(5p8e+%9X>+3o~Ys(<4?KQdV zEn()ads()9!5Haqjq6X431sZhX9=wxYn!1_jCw=M(SA}BN1)8gge9AiqUKuw6gM}C zC%oH$lCZl7vTdMf^m|+XWFku?1eJv#iX8JY#w0eOVy{D@WS>1j z3G)glO!9tb~l|Dc0IC}f6Lr1vSZ3C%mWNwkzx0BZ=7d#+g=f-B;2W1#e!c%ZOS zv|e|8-+~)=4xFa!3@5($ovFNSXO~j%&dW8q#Sh;6RI)R|Gxub1j{7&sWfwoVf>3zaB|AUBU*c4}iHQG`D(^l~K3D$USl6(Pxix2b#fyjO_(N;*k{w6!GEP8H z)yO72gL}}?`$18>7yV+<wV2eKg z?moUZ7@2{uWFy+g%Wu3FaJBec$4^Z0bgyImf(@Cp1Hz*~3x|Zi#@4gOM#CF{swVmf zHvHuC_p%TyJn;cL#mI_C%(qj54>nP_KJp?s>XBkOMPuve3aq7;cVgvqBh<$8H+pf^ z_nW1$RDIU95lTxfUEP=K>1uA}6YPJ12cbXzAalBkPoFTD()|6aK0Ow`seU>%@=5Ay zNclO%HbPVf@LOj>Y1W1gXRR)|C+;E9CB1pIoKWhw3)!8$kW1%#@{u+2#OG6Vyf&Fj z^5@=BSX-)?y9np-osVoZz}TH+ue_WympIQ~MhPcK77r4;N)t}{jyaON)8@_KGp#8J z23xk3X_fm#>2Hk?PfTKnYxg;7vOb>G=(IZ)=ydIyFg0G=IWqm^6NP-P8C91u@sQUd z(~LrFr>)HtQQw>!&u!(=gHOc~3eH*+e4p>?h+GPfsxP@4)x;=#0gT#TaF3efhjcLPhX81L~(sxh!BWJx$zn6yESu ziv>xLbuj-jSU~39JLM@|<6gLPiSnm@BBSq>ectC8jaDPxu~&7kU65utNAxX7QNAb4 zXw)|xtzqli2es@gy}z`;tpz%vrc!yepq&z9azd|gc6b}R==3r<81HYbx<$g;s z)j~%p%01otLiDr?OQ#-whKi(Eo23{`FIcg_qRFM`cWpe1H$DxEp=(w`RRO+yE-3gc z8%YiC<E^eQAv@G48)uRKy#>OqSK#-?!AGRK8#o!j(0 zFiSaWFZpR~lj9YWY!!j zZNJya!subP=3I4?l@)rj*_rSl>uszY`E!!I5~U3soW-c?^Rv>3eG4!Apc8-Jul<&c z1>As&iz>`0X;JA5nfGLKYI}!+kD8IaQ-cpPGAT)`I>pO`x&<>k!xEk7Gs1D5NFRlg zH?FFKF5YNFuN>S!uAekE=%4h_qF;U&wGo`e2|RT zKAP|epp&Qb8S}@dW&q8x`G9_JZ3q6;+=}K)KFf5zJylMOHD&D8K1O0A1LBvJfqWCAHyhGwf$^nOU*4;hf1gB#Yln-9R@4;dR9FB@#s|0ZL*iBI^OjE4udRsJsH;NgL} z2!5AwbMV5v3%|*L?Cdulhu>suKsHX;Q2k8?;)0FM?=lY1&8+;pjN_lraq|A7JPxiK zH`DKVIXT$>(Pr3Q@{i}Zxc=D|E?(|`)WOBe`;WZbAg+JZ!Og`5+d6-13lPM?{dYaX zWdGD3h==W;d3kOo>hI;Tv+=;D&fjF59Di>!ke%z-ez-b=VbM*_x3BT3TR(=}%%K}U sjv5r|3IN^AMYY@Xjr$B12X>pjJp;kcuD7!e$j%M(Xi`&4DodgLFSiLpBme*a literal 0 HcmV?d00001 diff --git a/docs/img/generated/flood-grid.pdf b/docs/img/generated/flood-grid.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9489edc456ca10bb2675e926c984d14a3019d05c GIT binary patch literal 1547 zcmY!laBGk2PIo{XYE+xIz(^bA^>+S-I&x~8;FPMzXv z(kbgYuXV2Ly7`X+OAh-)o62R&)nANf=6&?J+o-DaC+lDKL$8Hvek4A4x!&n5WA9>r z`7K`yoX*6r-~S-+XXtvlNoQMAbDWZkqg_@Uy?jY(n#1;@had8!6W8YHo_p@}cDZTT zo-J)GWhXNj%Os{L>b&N=E!DfNF-|+?|Bp|G_FVk`r!@M1`L}pq?eeb1`B{zUKRBH~ ze&N1mz2daSIr?04kKLKr*AmyDtamH$q2#_p%~lS%d)os0gr>;(-uQYdu<4T5k=XSz zsw?^r$5?t@n9#PfXv%_%JjG8n9EJHnTfmO z$W{40dr;-&M482WhE5St^>a9*71vc23Iteh5vZ-5W;$2#(g7WJ(Ya;^jjlWGeYrFJ zmfFOf3nq8}U1mBryMNX8Z3jd0Bwk9^t*(4iVsLrOq=O;9<{h8!hE4^XJK5l z&rRNQPrY~fzL5KMgNHrM`Tw*lw>0vi&i-OveYNn;m8jkKb=N1>1m{$qVo&3KAAbAd zGM>wZ;_obZFhqCt~Kd5_j4~kTP1d| zF6Q|>SO44oPMaU{E&S=3$nx4aIF+>R)|&gf zTte5ai!J^gSAKcL=J)H=KCO_uQlHqjqMIuMxk0zjajXaM9W=sPb%Ng#rnV+uD%K|iFj zAXPy>ATd1^Y-T`WQEFZZP#Y{8`KG31COYL;C`1E=3>1va%=C;*6$}l*R4iDJbADb4 zP*br2C_h3npLj5Z~wrr55Lx7A2<^D}cfz2+40Cm&3{ggx`w64)xAVDF*rjr zx~3-Bo`9nKl+t8i;X{HxMC_O&IRN4V=fsl4ocwf%*}#}8hQ<#v8h6u0#wYL!EUS%M^lo9_#^GS_TKm1(?6;?V<99x0m%Brqqr=A!z4uK$(7%^G zj-2XxRrxTWtiSYFX*Zgrn}N!MW1)fR^lY}}!)$gnI`Os6)hJ%=c>6)n>(@C|2Ht^PzDbK=KI=%Ti(T!75+;{X5Q@In)?*# zU5Tf(VDHL>iZesyzcXvE`}!wFhbx!n?^jj44NVU$l?}=EnP(?jt*f^e1*}DOD6uy# z23LTuOar?l@qPYfFGyvI)0n1BZIlTEJ|73z5Rt?_K=hvn8AJuKnaRaA*vy`tJkCNM z5Zil_7{m~YVKZk45v)-bmUo~9M^sd=r{OAtB=9-#ilHaHF8K%8Y3tfR~5silUQ}Nc~6Mwk(9!^_CMl^-ndH&$T9FEiV}J(mz()y?6^~mNfR6u zB=87QVSvl&bT}ajdCVoTp5gNXf4xoZ#jIrRb6D3YaN@c4Y1X1Ta3f4-4T=ag8G4+OV6il|284gn;zrK^ literal 0 HcmV?d00001 diff --git a/docs/img/generated/html-boxes.pdf b/docs/img/generated/html-boxes.pdf new file mode 100644 index 0000000000000000000000000000000000000000..aab4128f7ab0c55353c9c974a68f1ac8c3f43d68 GIT binary patch literal 17836 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�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 S0{oya6C*r1xv-1~{Qn1){E>VB literal 0 HcmV?d00001 diff --git a/docs/img/generated/line-grid.pdf b/docs/img/generated/line-grid.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a3dda863cbf8a7e25c21f21608f08a76ee9075ce GIT binary patch literal 1627 zcmb7Fdr%Wc99C(?UL8Pf5jzMgLq@Ea%OwP&5&=W7AW9mn0tM@lTtX0X@p921h}5<) z7OS)hf>6arswNBr~b?4^x_S^kEzWwdD z$C5M)XyNvP-qS9IwKmeB_K7^IP0@`<0TCB;TX}@1So#@Jg#Xx9xkSZ?~Yn za#doFG5ha|5&Q7;ty|t%y?5wZasE)}y}c2Z6>rppMY;LRe3&?z^W-3EDWVph8r)LW zsVUhP&^%MQduQI9Qhi5JT6=Ore@jICW4%{*bLz?dmazDkJl2+6TfcDa;eNF1qWj2* z^u4z9^Y;cat^SWpg*xMYi)Uo;3ig_pZ$=*PmN%~DFE8J5!Ay=ON0?@qOha|0hVviq zp1tYxmwS>nV1u=5%BIg}Tk~szS*ue=N0XA{YFetF)Wb>Tuk99T`U20W6Lt3YOAYzP zU#^C~=9Q#EvsTajz0`hIVJI-i<>C#}$aRL(b;$Yw0>)7Z?P z?h-uQbf63f$Fq zTcSK_6yH65d8L#%(^uk9&qEySeJv28|=qYn!QDBwN~!Fdi$lx@o(Kd z7Tn5{EN#2=cAWpVJ(M!&qsM+3>+LT!cHKP3A-W~M{pj1v+SJkinc#5apHC;+8ZMw7 z?wj*&M!-$ZvgM%H|i9*UD#AvHj3SAz0ug=`aSEzB z14@df^<^Iv2YE`%MShwqljfJr8OZTaO;~hfcF*e;p|vvI-fWIj404-Vv#rMK2Nu_! zuo#86N=6mIebs)$(BR1Dw|Z|po;c7oFuXI`o@);KnsX;o%+J~W^L4s3^K|ZmlYK+w z{zkU!)T|D%(6sd_Yvd<{P|L^9Is9T2!pMT@p)kUDJZ7F5ai8hOp%}5AUY1BhwSe$& z27nF4IdA|_Q53)cD#PI$Y6woiH+;tZ@Sphs=&BqI0Z<99Bpf~^I7O&wXy%mV3PP^J zmy&w$E`&IM8yd>y@&Sf9x-^H4h*Z4B literal 0 HcmV?d00001 diff --git a/docs/img/generated/linked-list.pdf b/docs/img/generated/linked-list.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e8c651ccddf91ef43db16e8d21315960b0bb9389 GIT binary patch literal 6287 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 literal 0 HcmV?d00001 diff --git a/docs/img/generated/rabbits.pdf b/docs/img/generated/rabbits.pdf new file mode 100644 index 0000000000000000000000000000000000000000..070fadb2b89f399e2123f5cf50ce7d07cb89aa34 GIT binary patch literal 9487 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 literal 0 HcmV?d00001 diff --git a/docs/img/generated/re_pigchickens.pdf b/docs/img/generated/re_pigchickens.pdf new file mode 100644 index 0000000000000000000000000000000000000000..93c0bac292aeff5f48b06afba513461f49ae02e7 GIT binary patch literal 16500 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 ax;wi7om~D&5DO<8GZ!2Mg@lqM-2VsXrvEDd literal 0 HcmV?d00001 diff --git a/docs/img/generated/weresquirrel.pdf b/docs/img/generated/weresquirrel.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a710709b36b5e93116a3978d5f9a95b22431d083 GIT binary patch literal 315609 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 literal 0 HcmV?d00001 diff --git a/docs/img/help-field.png b/docs/img/help-field.png new file mode 100644 index 0000000000000000000000000000000000000000..2324da542a6c5dad0c0eebe664879598780b8693 GIT binary patch literal 5778 zcma)=cT`i`wub?cUZsh21R@ZMG?5}6C14=*mPkj6^xhFvT9DpCho=}KAOr{yYLsrI zNs(Se1f+xX&~7|;ocqpw081?6Q>^t z>i5vo-QB^%kL00`gRP%~J%6CH-&1}~u#Udj3kD7n5_USU`X9zYQ=79o33e+5+q+V_ zdWr_v&g%TzfHWR`g%ow1^efg+B3>A(pxa_C;M+~KaQo7ovYoP@dGwQXJ>_rAKZ`3{=VcF(ydO1URg4_1A+@~JETnYg_Mm|~V>23II(vbvaG zm+fqu)4iAXIR-43!_O&Sr=w;0zWX}>Q>_Jm=FyTVG(QwtfFay6cO3#o-L56p3PI`m z-szNACuB`q=#{+7H}Se_TD)q5S`-?A)s)@}kI43G?6DO}40tyU`yQ^gIuK>hBf_TO z-AH!^&YL5@UUy3<^1394WW=@8g!^3npY4rA4e`>5RX}bYxZdq z#K|u#qAjdIOEPIb`)m7quUsEbpMX4uG11Xd`{F%+y0cxC{sp+zU7d+s_Z~V_DYxI- zk(;hf(wNpK#U`lqZaY%vNJ?xpCmb30)96Axz%1IA|G%+KZXTrM&mh1KEpB zBjF@3Jwa28#STq5DL^da)(^@W=0D)Z4xSfhhh=*F{I1QIOU5aBnDs9z{@0oEFmumG zfz9H1;sT_d%`GD3;=+?|p1lE~me+X6=gZurSZNP$a8h?_#U>|Ci+{i34NzD)WlL>q zQoj#(|MV?>6bc9X2l7X@;fvg1jH>UtWin@jtxphRZ>J{Gy1UENRj5+aHgrMuQ<-d1 zWFET`S@ut!SdMXp3d$f3U+I06NX?{ocB?~HSId}>XQfu`4qcE?1JS)>hxaLFbzs!| zL5L&?--(hA|Cwu_jFHXCDYF$O)Gxjr!td0JByLsGHJQK;1!EH5IQR<_(6qZ!yKu}nNaCKuKD-b|tM6rI2 zY_}p`F%@gpMR^T6tx6NevYv!RbUyQ{DdGV3jB)ll(!Ka(F#qb4hbTW&djD?MONXXb zF{_!HT6rrKD9qY2J#h;<*uA0%l{`j-$J-;j2-1Dicjg2OX={w)3LAWB?2fK-*$ks# z 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 literal 0 HcmV?d00001 diff --git a/docs/img/holberton.png b/docs/img/holberton.png new file mode 100644 index 0000000000000000000000000000000000000000..918b84fdfaa2d5071570fdafa440806c04fa4cf1 GIT binary patch literal 3584 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 literal 0 HcmV?d00001 diff --git a/docs/img/html-boxes.svg b/docs/img/html-boxes.svg new file mode 100644 index 000000000..f985457db --- /dev/null +++ b/docs/img/html-boxes.svg @@ -0,0 +1,23 @@ + + +herea.I also wrote a book! Read itpHello, I am Marijn and this is...pMy home pageh1bodyMy home pagetitleheadhtml diff --git a/docs/img/html-links.svg b/docs/img/html-links.svg new file mode 100644 index 000000000..1fc0487b5 --- /dev/null +++ b/docs/img/html-links.svg @@ -0,0 +1,25 @@ + + +I also wrote a book! ...pHello, I am Marijn...pMy home pageh1body012childNodesfirstChildlastChildpreviousSiblingnextSiblingparentNode diff --git a/docs/img/html-tree.svg b/docs/img/html-tree.svg new file mode 100644 index 000000000..c6c35108d --- /dev/null +++ b/docs/img/html-tree.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + +htmlheadtitleMy home pagebodyh1My home pagepHello! I am...pI also wrote...herea. diff --git a/docs/img/line-grid.svg b/docs/img/line-grid.svg new file mode 100644 index 000000000..95bbf53eb --- /dev/null +++ b/docs/img/line-grid.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/docs/img/linked-list.svg b/docs/img/linked-list.svg new file mode 100644 index 000000000..9e00738ea --- /dev/null +++ b/docs/img/linked-list.svg @@ -0,0 +1,13 @@ + + +value: 1rest:value: 2rest:value: 3rest: null diff --git a/docs/img/middle_east_graph.png b/docs/img/middle_east_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..430e88b1448983f4facf7e7438b6322bd7016edf GIT binary patch literal 28819 zcmY(r1yogA`#pRB>6C5|kZuH|k?t<(k`xK)MoPLRrMpW>>5^`wk?xd~_}1~gzyBDY zmpi=ooWtIGt@Y%b^EqKEO48^k#3&F51YK4}LJb0eJ%B)9Dv)5oE0j(ZA>c0rQ+a6# z$P@IR>>ou5;1y&CnYSMy5R4w^zc5Km7$o3DL}yt=NyK#o6m%{`JSPhl@DiD|hK{qi zy`9~=56<9!ArNt=cP7s7%qiWhovkRPWffJ-S>GXo8$o0x-e|Zl9DeoEkKB8DGU7^# z)<_K3Qq@?PG0~Q^qY8DhkhGnz?W!&Bt~59#h!8R+IQv_^Ts_TS=Ug4#LeKDSqCSEa zPhIJqx_GH<=tzgTB?-v2%8jaNgvo4`W2RhKj-$3@Kl(Kp#d!8%E3M=U+sx z=Bne(`=kc1gNrB*y;v$os&$-oWbLb%Z!O=(U@02dc^O}E5`LnrPd>bPZ%9=Yt4{B` z0&i?#-*;bwS?xx=VQy?83NJ~IVMulIr)#RVF(R=aZSmExa7QLeELsR963eHt4Y~?B z)4P46rn(Cq%ahxcX$`D!N?%<;y0oeNhL+Hln0fAy@Hps)N2EIl^6izBBNP&P4f4~% zVVDqAkYHc*m%2MFy#aUNjxnP!7GZG3zQm311S|iE>x6VG>toZJZrMkEn}L9Hd087Bqr;y(FlF3f=!_eM}$S#S`!^5xEq3$abP_oE5`? zIOgon-q0uASf4q~5lB+JbzNFoa&~ssVaAW>9UL0U8>(1`UI5L{BUMbvB#%t+_V)Js z_wUTe!^6Yd!>QuhLD0Rz3r-%`=ySw#b`Xxq!cU$3Zp6;T#l_8?-yO7HK~t}4`ixPk%@s}zS)Jz;5l@W8n}{UByy`6k50S_CMGW~Qp)o3%q%T; zc6Y68Yz9qhBBP?n$jDmUPAaRa#tu7v54<4nT3q}a#k-PiTXA>l;T`PS@wt?usJaB% z?Zbx;;1MB$LPGQJZTx;3BYjfQ1m+fwq51q2UQzNLF$qagLBZC}&d&CB+tcG;2m%Ia zN_x82&gfSX-VZu&-~K!^tGAttW^LHdtM6b4AE$!u?hnc36`DzpK)ixiJ2R3Qx zeuEa*3HV%;5)?Wo7ngUQ&sIGTD!RJ5s;i;PS5_X2kH>~^b8~~MdS5TQ{k=M}wY5Fi z*!Z|TOmya@U9OW;RyNf7aJJF^_xi-|?@>ckRFv1{{x`FJ4CtPG)`};~stBsvq}ys2 zj5W8n7cuLLv{=C-B|Y5O=x(j7s9<4btt>6gl`ov9XE|<>q#GO_&Jghv_V16O82c*V zwl`Ve>FG&CL{y}lg~0B6e={ch_wahv@9FPJJDXwaz{Z(WG-+x=LU*g%<^IgipFf8W zwuX|kgnc~E)_aY7Z(qjrr*oJ|>FTCg7^y&?2saqp6oXhOE-7h7<0L|m%jD~B~vi?Qxd%!r~`Qa&ehuyNpUjusp2Utx3c z^QR%VnAGTOZ*K#m8opXzU-!7!=HusYyPVa-z`#JoXI{Iwg=A!8^dM5O8$;?YltmtT zo*nES9MI9wSh;XBvWZ^dH{F+bIvep(H0{fxzSq&#=4EC^6P1+govqOO`t|GI%LB{z z?+<_fJ_D;J6L3X?sB0b`9nmRg>Kho4k&rO?dbRsL43CV!v_4!dPE1VXgIi;W%s_3Lt#QG0xRyka^}6orWILcKLA0qat$$HfqiX*VL#{D(~b{SCXa zZTPX&`^qp%^7+QUu_wHIlX(p+JE#4$Qb(+g?txv8Zoh})>ct~!*SCiaF8+@FWxMmd zl3BPSuWf+bn6oeC?Vx>ayPOON_Q`OU@x>gI-_p0iyQZZ7a={U-SzMyB%%6Js%|^>t zNJt0*d;-`6LTqfTik=>DukJtP)6>(h`T04$I0bO=@VIxwjq6|{iPW>G7Kesx4`wUe zPF7w*!?+d4pni#DY%Q@t`Vo7(0Sp9lY|Wt`VX;h}1)^-5LH{gX{8Ybwz6A|(N{{?k zXZVYuf0huo+}bL`yEvD@-uHyxyITxCgt@vSKxB52ZE??Q+=WKjf|bXfnTItiTU1Ij zP_)n{rJ1uJjAIgRMR)IAV?4F)!WosH_g96+^))@5R^9S!=Z-n-P+6dfW?3FMB-td) zcJ8A3&y;C%u{k+8U7f5_;NiU-T1@3zoSB(PGmv`~E`b$vr%os8V*Sx?zZ8;Psp}~) zWl6meSj!4=Ux4$^!Ws1KjZUVrBTqKzvbYyS7Zy6;wGinjQE*@AUTXB+NdFFT2tb8= zA!FaRo1T5a-{c#bV(v{UlZZiamlz0)Rc%-o9Qha`X9!0|2+|)FZDYQ60 z?=f5cc3qg{(;Wytl)9IR_mgmIQ*uF%m}H8-gntOgd{AzBAT5>|3%<T+>#a z+f4Q+5+Z?C$(Ql?UYg45=M4rkK1z77SfapU>gOw-y=d1$r$<=)hivJDMPvuH`@#-KHl0X?vraZP64KLKEXOj($5kNU2;oLn=%CT-V>J=N7{|yFmLHN# za4M4^iEH}nF+_hK>Q26BH%zmxXA@UF65aw>UlEO+Odm$2>DJEU5L&|C{{EcX$~KC) zQq}5ssq}jr(TJ-~o=^^z2a<(JO6tAd6ILj^7K<$Q_lMAj3-=xV2XaEfn}flm=SEGG zG>MfJq#W#ADpfcS_ZMUH^Yawmr!aYJ5E~mCN{&7W1d!!(`YPZdaQ!e2SJodeFPJIu z`mU(nsiN7=Rf5n)F6@01|56?n4sO-^LeavjKbj0?seN@xf)GD~+vn~Nh*kEB-&rUr zDPO$!T~^hWt4m`9DhVdbkam#Vn@B@b2l}Qd5K0IQSwh#LSpW&--+d=m+QYNUI^xvS zRCsv!k#t^VN-XcwPQ-P1i`Ldw$Wo)@##Yk{ObTJ#b_`6+xrvFen3&ZcUe{}#f!6t= z$1+Mfwh1^wo_}^h*nl zT`A+Wi|dSw|Iq~swX_fzAOl^&xY$SgDdUSH~v^$aa zGl4ENI5;&W<^FQcNIII7&t*Rmq}Usknwpx8{+R3QYh^jP+`Bxa*Ap3_0zwh$O))TA z>Rv7>F2Mv<4Yw5E*Q=3?g4)_z~mKSikpCd08$LgqlIq@$_g>$LLj3x>Q|wz`;18Z#uiOuiflHg(`Pdesmf93!@ zAcR&Sh287wJ!EBNCCle3RfQhd3Kdt-=H{j|xX{}=iys8CM8Ox%WNw3phKw);1TrmA+M6f`$4 z^!M*f7Rcj1%X{q})|wK)fx_?p>z2JhauI79RNO(Z$l+*ef6ln2j39^LfVzT^^Vv{( zg*#SQPNS)!5AnC9?!xGWVqXa10aKcGlp0)E`cJ z9Nvx2Yhj6pVmtuLl8z+oWJcz4W&ZXld^&l9RpD6nO|Z$I2CfMAZ706!q;ySp(?(hs z#8UWw4{r*x9|2uX^Nr(>&7Cyd(iHReqa2z-K;@aqEi#A#! z)GbMzuQA4Sn39p7T;UF7j252lL*pcja;ou(>R>T3h@OAitsmpHvy0*70cCLtCT|d=^e1&5DWlexS9O z{-iFM%A!}jIyk~e&-q!*4O+vFw-&;(UyGh{|9m1ig$F0hV#nC?+0+PSrMGS=nW7Lm z_j&ZC*juF^%GWVpCJ8t)tdw-k)0mT{21f=h128N8{yRLW@m~Yam)dNdSL+ArK{KJZ zK(ryWDzJ6{QLXQUp`O-^@4>1nPM}*-HPEeM%WGsmp1n`M5fo;N>yO$%xq*c->OhK0 zN>2(%4FEqN%>IS^O-?|-g2u37I>i`L?qGj5nFxgqiI16^BG!+;?h5Zl&A!65VOWj7 zPb64N;gUt0py_3J&8tD$fQlm>Ihv#P0$O9lu4z(BS9x!4^*MqTQOY))GeY4=f#_|G zYlADMo4r$4Tw+fnt6k|(=(tot+l{Z!Ql%yYc1*n_7BpXPWL^iEv-#9fONZY51>HsNZX=_&!edX7)4pwng(^616lje1;<%D2SwFP;{JnYV-gU0pYD zdLT~j8%{iS_iT5uzD-9xe46XO{5C<#$nEq;E^LCk>oapa;fS=&!gH3Xazzyj=}(k~ zk{&P^Zi(Xr_=41$j-ECRVGagVo(Adx8?5wf*=0pP^T%Sro1|1SM)fFA>&$(t;jvj< zqQzn#y32jpW5tfn4{yEj&)$M)=vJjldd{8qYdKRAdlZ<9m`bc=6a{P&Qt$umK^xOmQn7_s zU@g#Xm>^W6mbMXUqRyeq+=}n9if$XP|6gAz6-&%#y4U4awID4G-av@&{$$)~!v9Wy zY5*86v{0T1n zpUKHkb32)@f9|N87JL;;_?=v8OD(m2K3L}Kr{?{;JK6bv8lU%Ic022sbABF3wST-S1d0d+5>s?{xjoVKeqZ2N5sr;G#&sX_(S} zQIDhl9zOZs86(KROHO9w&B`kL%_MY*2$xX3`+uhx3oa^Wj=+BH???I#on@`deJSE) zm};g;x)`k2v^k8tgP2 zj6oXTsmBx@GL;L&fY}A%Uz`s-cNrSeHZ3k&yNt_l&DTtoK>lYv0aDR2rqh*Q{(HuH zGGab8Sj0#bZCUIK-8(A2qtEy+p(ZG{$R~g#xpVL&3j&0#e+*wjPrYF+TbmI@gZ#I| zSl&cUf6D0!MWH2CF(VxZ13C1-T=wITf7bOhya;n_!HAbSpg${WL`qfs1=}eUBv~<^ z*tAGFYH7K;zPrqSHiNs*^nWINDHBEd7M}7{Fe&_;hPD%?i#R?-`(wGbPc!6yyB+kM zHwzkK`0p%Uh}mq)jG=XNm1t6isY?3f{O1}}R+}d8Gn3&5gDH^c;%Fj!O}1*7r9wa{r$zAbV3_6 zu;(P<7OgFf-|5Jteax4_&a;e;#b&uXR=e2v?*td*w>g(|q(O9G<~Z^)oo2Dqu=^L< zn?<3Q)}jKYE`aeUU7|4^h`Qm(rUh&n1i~mMZ0OYsWQ0a1i3|5-#FPbxW>CUpvjSSb zq8E~>mdZm>tNQ09an#}@Xo`7p)bWIPFK>mG@Z8Y<@5u3q`6i9SCR#_4|J%Da)Y2eq zJX8EH_J9RX@fim-%ZR- zy$7~FLC-2_3oSKrZtuF-xBFR-4McL{sKxlO6NnN#Y≻n5scN1yF8)Nk_RuJyR}9 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 MWmWFv-ZlyNAGTT4i~s-t literal 0 HcmV?d00001 diff --git a/docs/img/middle_east_graph_random.png b/docs/img/middle_east_graph_random.png new file mode 100644 index 0000000000000000000000000000000000000000..c7098f8521e84d11f74e989ce7a175dc5e76347e GIT binary patch literal 39084 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

      SZ=n%P=3`Eoyn0W-H2-6v1v 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 NK>;EDO{|#H{{WGX@7w?Y literal 0 HcmV?d00001 diff --git a/docs/img/mirror.svg b/docs/img/mirror.svg new file mode 100644 index 000000000..6f3c6c169 --- /dev/null +++ b/docs/img/mirror.svg @@ -0,0 +1,11 @@ + + +mirror1234 diff --git a/docs/img/mozilla.png b/docs/img/mozilla.png new file mode 100644 index 0000000000000000000000000000000000000000..806dcfcd62c4f2e85e5736f36d0f0e67e2e38b51 GIT binary patch literal 17626 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 b{}*5YdNBhcU7eQK00000NkvXXu0mjf;EVr1 literal 0 HcmV?d00001 diff --git a/docs/img/mozilla_mini.png b/docs/img/mozilla_mini.png new file mode 100644 index 0000000000000000000000000000000000000000..272144250d3f7256e4cf3262fa957fcf806bc1e3 GIT binary patch literal 1815 zcmV+y2k7{TP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 zVoOIv0RI600RN!9r;`8x1_?<-K~z}7wb*HlomCkJ@ZWp4xt*=;OqZEjtXQfj1t~!U z+O*VGAyz;MAMk^zf*S3Y+PDxSF2pEN>k7qqlw%3G>-?5J6SCr0Zi zKg#iQ@T<0*Pkp;Z%N?x@VO@n;C9W^ilvBL8s1@a|n!>@L{czBR2T?o2NS)FOSd3i- z!Vg=8$`oPs1Yvq!_Swm5Say)ZczExMGNY>_OjK0j?Ea$H&o&_B`3r zW-c#9Ko=J23w*DeM+dpK%zFz=3N=H$J*L+yJ~t+~rmV9!Sacw`x<8nLZtOc%s84qw z-hp@F5_|>IrsjFFJ+jjm*jZzDjjlSqfh$X_t+2JqO=IZH)1HftdIQf7@yGy=4(i%C z|rT4bd$t#-K1UA=6p9+@;Y7sLFIp*U`5 zjoU-B&kT(5@BsT#hFw=?NfX7)IVY>mrwePdSVksb&f%cYo|DaQh(E3$qL4*EV>6m_ zF?7=kyjbOmQUtg+%iB?E@?@#Ss!C?v5IbwZwrcR)kSE6HJU(y&gd^z?AHmHjcsF+B z6z^?gUWq-aE6gv&P)^HpO_^fGo+_`^FHU7 zb!Wm{md5-G^Ko8Jke{AsZUZg32&kMfImdUqd2EnZsysZ%ef`3(`&72qWS1A^ZYWfZ zAMCT{KzF6PzkgG;T74*lumaVSIX|6cxD1Q%>2W#JR<_Azm)<=2sR}nA{C^Ja#P9GF zCS+VTDdz(j<-K+H*0~3V#142?mKRmX+NMnSLZh&Lf)J)0`Sf-Q-)Rx1E61Ysd>g7HM5j)Ah5iL7pr#S?`IRHVH=HPs$u=XVAtj$7Y+n%Q}VHlbk*5ai>+yM z)zf0Gy)E)+BzAr=vwUX3mRlNCe$px|Xb_5-fN#B3PR>LL4_i(Y2D38WpQocr5r4vC zX-rf4?oGZL#nV|4$2O$jMlIpb*4pt$v1GL32C+w9h4EtO?@Ay~Xob^xI13wt^F`rO z!VJvAIF#{WY!UPRgtczCZ>DZ(5?)P%&l45vtBzFg*7UxPt>{TRpW@iVHGtPesnvw8 zG=~`8x;Gz~v7&Y?C7)dd+?PNN=g`w>xAPLXuck3G@N=47x&{Kd@QlBJh8eU0r(mg;`{h9u1n6l@C_`%^WxQU zM`C|!3gf8`UPx^1#!{?E#4Jp*(Qf=gOxPNJo51#_olnGE@p5v1V)Bjju7ov-<=J9q zSZ?G!Gl#$8Zt)^MF}*JhABf|R^6_n_A%&|?ECV5ZmlMk!xI@%`UrfxkjRM><@glX| zk^HWXqJ&Q(q`Vk17c0Q1RK+5 z&;I}%Tl;r2Oqv=1001R)MObuXVRU6WV{&C-bY%cCFflYOF)%GMG*mG)IyE>tGC3Et0000bbVXQnWMOn=I&E)cX=Zrk8FWQhbW?9;ba!ELWdK2BZ(?O2No`?gWm08fWO;GPWjp`?002ovPDHLk FV1h`DP)z^; literal 0 HcmV?d00001 diff --git a/docs/img/nextjournal.png b/docs/img/nextjournal.png new file mode 100644 index 0000000000000000000000000000000000000000..e1bc6ea522751a39635bcea806fa513ebe640cbc GIT binary patch literal 1151 zcmV-_1c3XAP)Wt?gAV`z03B&m zSad^gZEa<4bN~PV002XBWnpw>WFU8GbZ8()Nlj2>E@cM*00ZMmL_t(&-tCx8a^pq} zg+DmX+Ppz{11l#m=LEyWWGyZx>o_N{e1ehJqm-mwqD+-FCop}2p-*7V3C!6ig)D@2 zpmx(gavZr-LKUmT78*dmMx!4HJK4!jcCwRg5mD9G6_7{|_y9~*b+R=;i`0Os>YI&& zE+RcuZQT0+I03$@YQ9CFD@)X6f5U;4Ko4l#@Ej5&Qx{bbPC`IxK!0TvB`Z~_-U+br|t-t0%Zb=5@>;XWsOU_M?h|| zwYG+-k8MR%70Y(Drvh%=dw9;BZU&4KI%;!sRfXSX6{DFl`s14Mc^2lIjX`4Wz&2We z+UX8Ed?`>RY!19H`!CqlNOuTaID3WrU3Osc#n~+%8QZ_U6QGC1XK}DVF9M;t1$em; z6C1y{wfk{4UjjcHcYr=IABAPh3g}L+1xh!+vM~+gYl0qZ4-LR9!eZ#w*mFKB8_R)X zpl~_^mw?%0K<_02p1aM+U7%S)cd@cTIzY$BIzDLtTp`)ZrXJtFlF){vzOps{`#)&!IUvBV zE;&lObN(~XgW-e6@@pB*28bM@eAf5xhHLmbe8g#3YykIA{?1g@ud5c3)}8w2l^0c= z17Crj@$~xv|M+JQkrR|}?0;W9r7T3gjuV+S0DiEVzesFE+!wr*v`;yK>z0>eJ62;6 zd21P2TOIEi2ossiGla4AzLi#Go5rXzy}dIbY-iuO#5c>U@@GS&1-wl zP4~wa0DEp9=YBDNR|astTjo&^Y?=0b1o7lT>rmTcd*8R;UaT!9ZlB$SkpqjkwRQhxp#=^)GWxWn zOZ)pxl;!cK+}!2{a4zh$+@WQT71bT|HrOmIrg_lwYkXi$!EY1ONjI-`b~B5saHl>n zxP_`NR(!w0@99Ms=-VQDuP~;{)#t60{a2LXEpXsz*ADcT7hj);sk>!cYeQU>r RQYioc002ovPDHLkV1hS^6pR1> literal 0 HcmV?d00001 diff --git a/docs/img/object.jpg b/docs/img/object.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d9c24dd605e051ba52da98353b1a5be078cde9e6 GIT binary patch literal 172752 zcmeFacU%)o_Xiw6#e!f1M5?HGrS~eJA|N8YcOit9MhhsIL`6}l(mN;!NR1F65(HEP zlq$U&6$C;8SO`cU@Ge;Hz0dC{zt8i&zrS7vvODF>%$c)i&d$z$XLfyX{XJ;kMIAjI z5EJ0UnZ!Y$^;tOqV-KW1($53wEhHx?4N^X*XRz%AXv6K>JpSddnWnlK@#$we(`IOo zx34F{-w5UJjq(Q~wr@ll`}v!>Ad#?53O$5B1cE@pHxf4d8)q$;??#woGq0KFl?{q> zp8gwoHj;mo3Gp>_hC|G?t<8a`AC>*cVC;)Tx|u=zQQk%_USJ@R4P*@R1tEbyH;@p> z3!Tb?FIbw4`dBXfX5t~H<>ANs_n%qVex+=%@R#_#zv4|j z-97%Au;*6-z}s*7jlRM%`-6v#o(AX*(C2=fH~W#uW*YnEar2;g9rQ!r_HG8Zt*@`Y z+N9+95eAL|o27o+4DZ@J0tcqeTql3@*4;n#6NuiVUD%{L08-c_+|c12n=wC9mp1); zfF1a!j%E5MN1J3IX3&mJewa6d|J*uAZ>H_sFfbc#lYXPtje0k(`L4~FotrT~dlT~+ zpmzbkpLGdr_(0n>4x6+`Hhe5+fx{2ZH-ez;cY&YTW*ida0`dZSI~eHizk25e$)E0L zhuDbN$g`36@9r-IzY~5kw-H95U>g=`quD!LkSK(!-#_#;u)hqDw#o8_dj8e{Ao3q{ z2z2hZuKi%c^tUSgXpSGu6AbaQgk9R`3`~EkXZvOv5Pbmnv2F(R&i|z@R(D?{%KL9o zJCMGf?w-HxoVM9U=|-DzKpA_TQT|9>2m<2k><@7TB0%O>y?>eM-9LyoVm4Cr;O;`e z>HoU@EGS>t-;DjH@c$lT;OGAL8^R7}n7_HR``@V8!4MeC9CF29&+me{!KGg&XXmfz zzY*{9K>7xqg?YOFk`%{}#$Wgq4YZuq72@WM+BAW?0wBKr|AgG~SM=Y>_qe!gAz?`0 zzX*5#kMhsy{!Re0fDDm{4O#EMV2u7pESt-w@%kU}dw@&q@sH%2 zz3GpQihz41pw60~H~l|)+5SyG(+^v+?a*duL!ozX=GveKiCzN^8(k$8_∨Z$V7L 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/img/object_full.jpg b/docs/img/object_full.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3b3c4f12b47011febdf759fb07509cd83d33c799 GIT binary patch literal 767923 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

      -RyckKEty_ z=)TY)4S0EOt(CXc*Oa>fZbuY1xWpg^cI6s*`&t1a1d^o(w}Vn1*6l%$=9!m&A-yO3 z*R!g_0-yOP72_sWq#ITE4Yxp%Z~!dM9qG%-Idr8Uj;6NBD>Q7|4O!hJvL&q*H34Sc z2tr9V$+JabL#=k(V|V@<;OYcGXwP5~YaRBO?=5B9$WuQM*$C$hKw9v)9FbYhT*8)J z+&p&B98|v;v$7`jysNnx*1wS0abP;h#toZ)+p*nnTeasB+q-I)A){5F)ndKI_3MA; z=6!zO!%Jt`B_PBx?7@b+xdW6Kta9WAqpZtb{Cso)_eZS;=?=wKD)GhImt)Q8fny%s zTdj58yX6PyK;^T!*v04OePM4(i>b=prG?&x8N9>k6tII!7Yt?KigEC#`FLjC)v@hN z++*AcseDzdQwtaj-Ha{Ui_?An?)x7vuj)A9iBRPLI_>dIkt*&7tC-EmiMqL$(taQb@ zhzX=~HK0LUXqidYlok*nsUSBk+CAFYaGHzp^rbI=m_-gAYSc zDt@*pAnvyaGlGP8_#6Ukc<-LfH%(Z~*)-~cwPJh5K8k3wiRu33lYxAxAvASm-pYMW zl+Z#|ZnMHRmZwEm>>?i^b}K}NN{h-!ECr_gSK=zW4%GVp+s>ji;V=U@q0d~gX{h1Y z>8|)Z@_4gKs&d22-9Y-UdGz$#jQ~4M?%x+?xm;r4yZYF=2!*h=?43G*3msckRgH|! zo1B=IfAI8~bd5yl2%M2_?2j5sh*{u1K$VLIqi?hWcjB!Mp#OF9=Cp~x_k#J1_CgX{ zL~BhZ#%?xW61xRuk^a*}F{~}AfT5x1t0{ses+IzOfcWz6^9|nE{=CmO6seEYLZ_aG zmV8AVN4D_pr|TcitG%bC0jn}9wBc3~paoJfcl2l1;=b`X1yR~|SYL0=3mM%@wSM9u zeLTUbXRFeAmjO3!B!|&SRvp{nCK^crZkh+Y<&?F065%1{Z~rEF`#}skKay=GbraHp!rV41FH-8)fmEb^|MX|1 zU|23EE$BI1Jb`S!$9|X6c8T(J2T3p3xm96p)GS2do9=+L!oxKZ=US9DeJAYyR@P!j zeLJWgXp?ivMBAvbj*Yd(+NM@JymNF^-;`Q;gm`I;1|i)F|6raZ6^L@Kf4r+zT3LD) z`|iSjeO-Qc>D>jMGh<6|bUpiX9fhRcXRT(!&sxs7ZiDjGmw#Vqy$GPFDT>`{yfi|> zeTFAWzb~u`uSdMX(;x}hv#0NoxK1Bjo#dBSt6W!GU2G?(8Obl|KoCMQ_+ z33=OS(%1QH!x6F@xA^9~vx(PX*E2^`-9{N|n}9k5%f&T`GT}!I8V|ILwAnP&APPkL z>sSQgpn@`MsFAQmNT#~h1U(4WlohfbxBoja(7x{1R=w%5yabBGyI9U~rPYVEZp`5L z$Ii~8t=p)XHowJc)Z#LUa{Xr2JpgJVX?bNz|_w@yDqXr ztBL_8n@?R{rnP!)ClDKdUT2@BCm!9H?AknNCuFZ>S?x7}yp z9&`IxJ&sk!DNW1y-*~yZMQK_-7r-1Y{O4D&setNK6t~(#pp?_*nm2Iq(MTjg9`Gkq z=ouxCjsJi`qLSyh!qKOpOZ0uHC*yeU^0z)+1qHO2R@33=x({}>H8=#KRGcq&IA8{t z@h{mB2FCeFaqu+4tK(yyAb}sY4kWG`Q2H=PHmM?9K%LM}3skZln3lt5-@h#Ipzrg@ z(m|%>nBPC5Tx=Hf>me6(pUfD>Aw*!)uDc^C@P3tLp+i~xg1=VG*VTY@$K%|t-E(S7 zGlvIe*2do3L3sonD0PeF_k}-kE^7l0`zHEUv*tbgPetxmrWLaq?MSVz(SFKsU&Ql1 z#26h*x%k6$ciO#NXfXuz_L`UD<7QZZWAGmG?7IF^QpL6{;d9xdVLK}A$xU0nNvHki z14(5-a)KGN!)LSVW5A@G+h|&iX+9^p*2nbRgpdqvN7w*HYUJ6sHXsLwW&tzNgrJ`f zQ}MxT*bnQk$DJOO_IIX6Aw^uu!<2sU0S>4zO}aLGAf3*_aX1`y529|eD&#=>)C;k4 zUl=Euuhqa!cdEMU>mfjJr-YWp9&ZA<8Ws8G+peCgcHLwX@v?-zic^{Qq%7_aXfY|0 z;tH1{L};T*$+&%LUR%01B={cQi*2UG4&CWQ4bzj*8=U|@Uq_evXj!9uUb;pLWgb|T zFFyHXoBd>lL@gq2T%jGP%ly6|45@0WJC6!q$SGdTDU6<%&#|4zesD7Y%gFvI0SMvP zkZF>uU4H$bO-ETu9OP~N{Gji|&$k!|_a!J0KZ6E?C;-t{ReiSn*XdAzxerfo1+JNE z8OX5A{*3dF;9~wb+?i37re7RM&5Gz=5&J0b?*j987L9ut&ADBFgH2rO)=V(K3i@DA zi`}Wz#1laB>YASM@m(&`8TWQ_%*Ra=1F$d0$DM`m2A9k&1g*E}sG%bUo};_AyKt?- zu8q-6Msc@uvgxw$Z}!;a8r@NmsbXJ9@DyFdQQ*`xH^F-Y65WUX|T z8{D9-2%eNZmi0rN{-qIvQ(3uR8ZL#HtI{=BFwSkh-j`IJ=Ke-l3x(03g;B1U$JY=s z$9o3E>aHnQnMYZRi#xl!v*0)F5gYEV^Qq*moDEy6ef`gJrY>z3nO44ksvFb!#~iWg z3Euh*p)Z2@zXd41hN{K<;Nn8oLo|S=W%seQe-&WX%B9B`LByK(G9E;-e`upq0 zBf~`N_yYhpoEwRGIIVVU+OV>fhCPrZn>HT5EU~Ci*A?)4P-c4fdf~xH$g`BVzl6S8u}8q!KGJe^ zL)V5o?f!3Dn=Lpc9iSrdegX~hR;q|e&%n8qlk3#=#*VKz?hYsIEG#J;%mMOiYTt4} zuRm!~rL>|xLmcyz<8dbLhvK{a*C6s>XVphHvEbt)s;rY>XjoWthfGjCLM6S=G)knb zsG_`-M@KVZ9WbrBCO&6>4RaMrNFi|24Peu+XvyU1=7L?@-2{Z=k~H2ibor#m>n=4? zYU&Xr4X4!LUTeyfI3HsK&KgPA;k~>Gog|so9032kkLrN+1>zl*i+e&zDL`>RvLrm% zQBk}RBp^mr^gl7X1eNfV=K>^YSeMbgs|J#ysoDbh$zst1T~AGy-;~?^Juzm}459$< z=7XAzD~mxUmuWvR*B$5}sP=<7G~6lp;|SC}r#EG?N?-p^AfTIys1|NMfvU3cm|-yD zfv(cy4td2<^>0O^S&Zb>lxisU01info6H{Pz}x8Dr;08|Q?lMaWyW2971w-&flRuh zN@q)AoQS~&juL8u?m4`I6RmnZ?P)sVDK|_EpVB09L{}!vDdE6OWxvi$D>@bA11wXS zX}Z+_$%g}6OE>mY4^lE}dQGk1{o>YK?3wAvsG`_>P_7Ed+`#g3wOyx7x1Uc<=tUX; zYMov_IvaBv^|Zd(>)Xis~ zW)3LHMyvTFos0@mC7;GkyHDAq^{C_2E&+MfvBz=~`?~V7vg5k^M(rtkzZ_xjXKsHe z7u8Ah#^~ys| zz(xJdv7;=xMqa6^$I$2|piqE&d@iCe21MG^5OiZ+&|$VHN-~>cb)*Zr(c|}UcrbK(?4^t%HpZi-34hkH zL~+T>cY&s_%Dhz=o~F<6^H+bYza*)cn3LM<8IKH|d>y5`sVHPOayrTiMBu5xYFM$K?(3vI~d(hzJLPGGURMfel zWzx~jjn?75=En+T2uiE5eh96+!NzS$xhD|9uKYf?;j-SWsHNMHJdc6sCt%kZB7bJx z9vHdmWw|T4{wuzFE~H@+@B$XFZB)7R(z*PUadNmbVsHpmzp0g;Kgp4mgvlz2sLt#k zxTVL^`9S=0KT z;lD@!kAUp|Dn4g0bBT@O{zJ(H%$%C3=b5V{Uw;4b{qxKE6TYR!xuO5<%Jm-vcopDT zT+gc*HifI!WYn$|^{QQF;9YSRoU!W8FnJmdj|q}#D^S(`d|7ToK;iKW&i}CSYtG|$ z{Mk>JH@f3bw}@+V8}v$rg|^3lytIbT1I2C`u6)thKv`-a~f8#Sf}a zRFZ#Rn4uC&G1_Yii@s286sq8*!n%7puLf(=ow=PHZcQ=Hb`POmBe}*OfL6u$2d3fo zg}W*vZ-JAkGQ~}d-yTY6LlK^wu&P9>x9trjGHlEx?MYze8ckLd%|?fis$f48_oOU` zoim+oMoDud4IZ8np02b=Rr`y+ug;j9ppJSyLVME zLZ74oj+kdP!s$QO6_71aJE7|jejd7S)s&_DOt0o~u;~_joe2K0fEYy6S=Z`=(n42; z9yjulVI*o3Y2ilf;GWLH_&>^c<~rq#Rfy!&AA!iM;9x}O(yw1G+5CT#z^11E2xbuX zM%FcePw1GgsY3Op`I9e`cnVjwhScg6VOC$#^*u*ttlJPR_kR2^K2`vH=XB?qQaKOV z9i>i=CTAAH*#mMTsyV&=0%gEyK);4j_F5|a7E;jw+q+bQ zVcJXU_JcB3aLu2?CBF?jqH+|Ds+DZ(tc?`+X&8})%vj8Rq+nu+>I>K6Jf}RVwekb5 zb*-;ViExFW0m?%^FK^m+GSC)aZd{qM9&ZLP1egh~{R~kd=7BQcv6;_S4>nK_e?9r! zbk(_gqVHq6h`z4nRZo2}C-=J48zbug@_D_1^6^mdT7v@o-{U!&JpRWq_RTSGuYR&g z+lf}6MBD*~J4}uJ22&yslKFm)dK?V5*!PMKqxGs4oK(o^2t z!SkNv9_w*7lgNzqeN-BU2pV~!a7iR0QlT;PV&+0hN-IORrtp@>10{dlq_)Pr`k{Nb zKferVE(b(!r-!nHkk%>tfi8;dF(b>8mY$y^p>GEgSEmr)GZ_4F!J=JFPU1XQla%^!amay-JV+zzqN}twA2^0)0sCOX_1zTJ7ocTZu-22c;*ff zhD|7|G*`OOf)`Ci2^_y`pJq+)GF@wfuY~dm@LP%^`M@*<2|#(xX?16vD+q!ECAtAB zBXC;U^=MBUlaT5s1>v^DH3|m)g#+rhzPp&Z1_eMcdmv08(dD{ynE+FONY2H-X!KxKH^L(-UIaXES2sJcm5pG`cztq ztm=18%dE8&ADhs=`GU0T^}flPBLMvSf?^H%Ijj#;O9=T$>OtL~IhUTau$lVVLwi6L z(y-2mP?fXwXlig{16IS}ROuHYo1z;yLwj&WY0voB?+aZ0#>+kzE>jF?(-`OAn$7AS zqEv!N3n&$PP&9)#t`P$Czh0~9wpoTLA=JozlTq-lr`K1;Ps89MpWhdP+_wKNz*}dF z@KP*lEJ?>LU#6t%4%v8D|I_=Oe21Qsv}qIA42Ax}#mvy3h3O4vmsS$IS5ZQ*p|Q?p zNBe8#p&G$o7P#oje)B=wGI8MaK&x2Ge4V+|ApwcYFY<6pm9msh;M67uDpFnt$88~^ zsF%1kW^D_#XFDuEQyj7a@8z1@`mu07arosaJL$WlwBK#xX4k;AlWvWFGoppJ`AYKK z%C|ghXVX$+ronKT&>8Y3DFy|~B=!em;6cG9bT2&O?A-CYsQt`0KUcbaR=G3@P+tBE;o&0?{if6H)dO;Q4A(&ydLtw&I?Z-mtk8?)? zXg{B6{qW!!j4s3$5r!6O3cWtICoz`kIXEGg*WJJ&H|l(O`#dNMqY%hIE^be4X`fn-mVFKF z=&t8D7hYc7(Hl9~81&K~0W9@7twPFaR=LTF8D*Y=D#9rjlY~Wl*e+b#%2e97cZ1pY zjF6vi^r%(MTiC4T6MP53gt68$G>E7;%14WD1HhV zIJ*(aXD|!|FOQk(%7&P$2ksJ*xJH)D`yr(yCXiMWU4M@;X-7{;-<7|n5(i#ERX_C_ zVvyfg>KDxt>Ty|(ntk|IG3RAoZ-<11Fo@3)HOs!`>)4)9R+x>1t`>I) zgrA`0<#b~McBn$Or8U1&PQtDcAsSO%XjE*rN=;e$KIg(fQtubfBcr{Jam&Z=!rqG1 zwg?}{nK&QFysNF;FA#4SVm0t>W(4|z^YVs(jhDwIdw8IYsX2`oi~%*A_6&}^glUc~ zWZXnY9)K1#RRQP11vZV+^_ZJM@qZrr!#a)eCHD{WPce!0V4-u$8@ztE~O4&v;$ zssD$VzFZP>`Am*A0Jgcb?)H76o8-Jr=yZO7TRt@$g64lEifW5`zgG8!j33*b&mXn4 zJm_T>AIyvm5N+&!a&yUTPLmhH{g`8u)_C@`TphqvrGT!rLv1uw1uis6bp#w2tfzMu z2AU4u&=zlmfW5do7|HVs=})t}(h95Thimmi|Fy9!<>%)WMC#+&4-`50%0_2ktC10=1BuJ=@!Tk zegSUJuEo)O*hn%9X%Y`=3roYK6^p_@fG3UfDzn`U*Q9|4qK}L|K(obfjUbxvOiRSx z@MP-EP%^UD$=jY&g+1O%Nk&M7@nGIic_%s=&N?ynWA97$*hAJD2A6_@s$ZK5Ir+rU z^!{Sime8@8*0#WTZiTh{icYAW9W?U(`-&PHCr%sx08#DyvJb<*KIR9Ems?g=7Ip4w zEm+<{<$iY*3}>=B9LI3Kru4hP*jMnRqSt$g`qBMk{0}Ua%SG;q9&Uy36a~ccrWp*; zHv#X?5wdX!et|o)8CGL)#AFj(LSV~KZ6zgY1rUQnVO1h%-n&hWrju`NczD89 zEcqr@ewJBfkdFWQI_V>FJH$C5HNLDkrsvyjR9sQZfqO=T`E!4vtB_6g!m_}Sbzsyu zVLcW4_b9zLC*5|Mn>wN9x)5}U|3{x~J=NL~QuX^nv#3yUS$us`d2MzHkn^GcCrLw< zMciQ}_4cz_TegTi-a$n=b2(`ot*A-dwSTWRX+*O3b}3g>@D98Yy1O;p`OAHQASf88 zFwEh+R~cf;72~u7xE|CX2O1}+#V_Uay-it7{hqHB0YJxxmjPaH9#4S=S(eq$hqYS* zfwYL%{tM<-(?;Nd9DED+(#+W2ydvahbLd)mYJM_{9MzsJv#=olJz&1pzeTx{E@-7~ zFB=p|XS+nJRMBZ*8V7v`WozMvD4(m!w)b!q)LFC3yyY8-Z$Vw)^>;w4D+ z(t$SZ`DH%I_cLQPwR#>`yM7yq<&{T=(^=j2H@IluSLRuH1y48L>>$!}GR*+cTAhV3l7Dt|$-Pcr``Lu)ch-`4!BW?={-D#geYz9x(2ZsDgT~E% z13cwv2Z7hJjaMyUYq%0GJ?^5Si&A7*?2b*-Vyh468en!0bd+aP*OE5dU-3RL8x??< z-m=uIr(w|9>sS7ZEOUicfTsp|7@frbz7TA1Jl6`vY)VGwFU&~3BX5G$f*ek!U$}}2 zuVrO3OUNags!|Dlt9OxVzWn})CgP@bo*T4e(30>%Dam}5U`^|yP_Tg&ipiV($Xmw~ zYS$)H{Z-b}Bu7hc2ww6HSp2o{#@&iv1q$(>*$G{=N}|Z+SKG8p|L711m;+2Pexh^$ z2xuf)t&pVEPBJ~*T{w+)&nZXWe(#}p1$<&4$|*2&UfQsFd%TZFi`ndKj=wxwc}@%2 z1I`o{L;g&E!d>_uO^_C#_I3{;q5^+ks9vS8?caDwj3ac2Zj~u$%C?>$+R(k&p zzn>hYJo~Z&o5bzRz5$%eM4*m_c6UO$S{rPw4@a7v7iJf*hK2}4RRHJ(G=31DnyRww z>FH}2UexFEs=0$7VlSa;GNK^w*MDrPi1d07^q4L57cM~~E`Id=%3s7R1bf>aQ!>@0 zT1CcobXxvAySDE3OToOz8bzBozZfSMCOF08{|IKKAb(yF6MLT4EJSPf30;Swhi-fpGzlZP5pYi{-Q0siA2uozbey#Xj&`rp7A=p#KZ=* zS$*Z}rpq1hq!=Y=<{TE%Q->IK{AuoL`q+fDv1V_40@2rcyb-hy`Bkq}=26jHCTB5J z4|=y&F1+m~BbAc*`uI^yl|Q$yp+*v})132R@b}Ocw3;12$!$WLZX5UBd$y(cZVx*3 z%RY`Hoj(-de*qWUb;pMobyr9Zwdfzkd-YbF0ruX4&ZME$sSXP~~EQCm8wR3NE zMVcMCl_Yl2vKDxRZ&k;12{16Ib(v3|+cpNCa4&o=E=;(rVcl|QZ517_HwB)`x$U0S zAaA0OmfDvlv*y|X|KfV^jhs1i$DojykP*UFuVNxKBWG)5*-(crNDkbS$Vm%aE$K0X z-I*~xe!bJ~qhZeNZ1B{tSv@*QiMuE>*w?L2{Fa^U+ue+c+P6Asc#71+ZWn2#Lwk9M z1h;F%y?Bt4p}dan&2^ExO2#g?|Luo2eZ4ggXAD&h4#$H-oEJl`jdKr8`gO86B8>kucO;2%L8L_zF#LL-3EOcbO0(QfJj~AA~WHYwrGg& zyXOm7E?9}7&xDCf^Y`(eDk^2osj3+}#eS)Yo*})`)UY&`=Zy{GD*;V>271sa^j(ne2!>-n7 zfiL8W`?bzl`RbdPm?VcO@r8+khE8Dc6T8`M`*v(awZ|STM=>s5yo$Avg z(Ec`0TZ4(YJDeYyyssgbz(rmEV1(UEk%8`Sw!|v#!mAAr{OI;k|BHSTc%=%qV7YjQkDqeJAtS4lm~bXq88&S}K-om=9^s0*lryK>?SDUweA%p2 zhZfK4Xdg7DGLHY{=_GD?`R|}E02;HjMd+2>TUB9M<3E3|6mSNgM0d}n*kGG45c zr2EI^J?O<&zV|Pbu>X^78MqFASVPt`KbWOpBNkN>oc)iby1hPIW&v_Y4pf_2*gYOb z+rFRG2zDy9tSlD+b$PW>Q^odnuBEJ8c9MQ4oxG#qEBGD`43A5XutSB*W`ueC3WzjH zO{i*))bl9DRcAw$I9r^d=%=GHKB6tz6^|CjnslaFR#08FqWsm2Y+C_Jtm4`NkAH42r=}I=5y%D6R}C5*+lkB zWo21Xh1=?@?K-(<>Wf)7`7^ApKlQFGKM9hxX*m#CYLMhX#K}L=ciGz?c0x%1Hq0DQj5cKCv!n8!^_rlz@=4U>MBTo z24Y_sa1!1Y7Ey%v_J`Ez>pqp>Cx77z{y5(RzC(-qH})ISrEee+X}NN}K%^jIF5ksN zjP7>cA%&}1qaFmUax>uR$9s$s+f#e`h#30*7{rzfU)3OoZg5>)9h%~gt}}KLaochi zVZN*L%UI{?ej5I{UDV@D@=yH3 zZ@|!qHSrm0DJ8)(clVHD4OdRc0QHYIoa`-a4^3w6N+##woE}<-ub9ymDC1La?=hMP znCBuB+@28p(C%n*4n)(C=jdb6gLl zd?)o|M~>J6#O^o5W}a=GZN-}LR(QbJpEN>|@Y;f;e57|1ld7~Pf1j~q?wk9`4nUUa z4N+~bR7_rcrP=T(_dg+O&j#o%d|OPI{}m_)u=4-U@81`GkNy9`=l@r%zJt~dUwYM| zqi26;x=D*=DI954zjbV8*8Ih!>t?OKE@1ilK3X&Fs^dXLw-!%&nx*q)U^8mQ#s+Lc zu61qR!v?~Y5^a89EMuVDu3HJ>gr5W4-<2YUB*pf8&>!}7t_(hQQ(7f5b_ z(Vrej+)3_5IC=$E?{J^eq5yhg>9AoooEZlDW>8JA?fBszl|3B%{?R>`?*s&&urk$q z{8-_1k;}17*`SoWb*t{qy=VvN6yNE++hV`m%hpOin21N+fgCu}nM-MnXjA8UEDxaV zqKiT~_X~N$yBuL^9KQZ$iW0XZo^&GmcH;-)^+zlYOtlS;{YYMcuX2U;o=eE~x>_+& z0G;Gj3}qL9&`MUws6|_`gMI|ZwIqquN=Q{=U$%h3pjft17F@KY!;C8Z;f;M)9o+8liL$;YkD;W$dMNgcxGR z`f0s17f)R|Paq)1aCo+d6i`_-8zNm%yLd8uJU=CVs#Qgu?5ah@l24nD#X82t5@ zZ4IpwzNF5}{t(N#Z@E{~4T)X1ej6Z!lZsbsY}j+DF5BryBuzW6E7}UM*=bgD`?n5l zGgvjq^%P3jy($xmxJUbVV-0&CzFD(&jKYYytP?S)UH#FPK-BAF!{No20hhLc5HGvP zU1CzU$XU<}n4#q{k50_RRv+u9ysh;Nh>Mo#aURldbv*4{l9WUR%EFX`f89bP5GB#G zw*J%4kL;fZC(-TCzp990SLG$yIlVhNfsJp-Y0vE3Tgk6Gt@V$}>t-L1Q_}!u%FXzY z!ibRUq`vd5F70N%_=?TGL-^X2Z-jRsX621~jM0(4GcOLFM#Xy494JgC&B_QReO_D7 zVf<#*n3!))^5pC@QCX9pFRd6l*SvE%k-MMW?uXUqX=s@os^ueA4G)Cq5WE;DtE!wX zLBcgO_n0%In?pG+0;rW1&0?S@Bbs}+u}p#fkE6_^ob!6y_1_m_(0Zx~?A8 z%V(D$ylc#OKu~~Xo?K|9O@T5c?KcSB;_C|P`!sgE z*32Vk;eUYAwYqJK7vT>TT|Ahyr|kh@@N&DgiPI|SR@T?2VEYALK9t5S)d{NgUm0_O zoCiVKKH^QsfOaLbFHb(x>NfbGus9I665$Kfr;M7zc?xs0x(lrYe`l`U@Ed%MFvjJ2 zKa4yQx2Mw;86V>sY)#(&k}T2|B=iOoid0r0sMNN<9$ziv?^_Xpe^_!CzB%$7F0r^m z!7oy!DBJpSdQ z5^hBxu3*e=1xWyP?>juI(oTHx9nD>*@M^B%I2mmbk@uLWkn%%25$(BhdlC zqzOtJv3!Aa81kyxNQ@cM(r(Smin#l1r3#6gbli=b|KYJsB2t`>K;L#3rs0Ez?R1&b z9$yix*jx?8@b?9w-EQra9YAX#N@c2e_~^PQ50P@;q}jW1GpcKRwKO5bwK<3V*3_Ss z@iM7*+8)(e4t;{z2_{hVcZOzZ3!to=P`-8IA)IodUyweNh;*l{Tvw#eU``lbf0fUa;{S%#KvOo?o`j?Hla=}yMwBmvqDs4 zAZwbfzKTkLisB045Jd;Wckt_Fiqj)ZpRodTnc#1fkmL}YNm5J=RdH3SUp$1@p5`0w z`9l4WQ!{`^jOl55w6f*hB!^Tw7h|P9qhj9;3VpY#BMj(vq=@}{^K-i<6I3(`bA3;I zPoeNn*~TDjrQ_ya2VMo=iCRkH`yBmNdN%r+NN{52YW%Zf$tt`44G0ajdA>v=h29S? zPJpQgLHuiga0$uiz5SiHEK>jY;;}0`T)fbL=9OfztO4gZ&xyjz0s?kES;ZnUY^>u> zB4*D-Z&w$3)~u&a*zcE0#bX~*FRgif9bOuN5pWXY9Wt#$<)hsGhP5yoZtv+CPr>U6 z98D;qZh!{2gqamqN-k5`(WG&uZ;JdXt=|1iG;?$LU9Q~SwtAd@Kapz{~7 zdis4~m?xaVygx$uLem3+1yQ6G?n$+ccX-N{`rNKvZTAaVYC=Qcw)bvm&D>jI(Tb80 z?c9E2^QBX6gT+Tnu~~k|nzBsNQ96~D0Z*Uy`h7vH<`XC2G1a0PNYH8^vAjDf*$ zkllEEQ}*9Km34!6cO(N!J)yS>)7{^HstH*!>X0!ud79a;wNeueuAUS#GM;Dk(UNl$ zH%RIh6;JHr62tLB+Y(2oeuiEBJN%{E_r6t9iyri;Y-aEC<%i_xyY0Ew63G>%c|Cw; zf_2y2_H(Z}xL)JtU*8k>>wC98MO%KlfN-*2u|L!TM1Bv^OkxuNoyqnH<#l5BAPLK- z&^t%XG52>4yvk7%RgDjK?ol2Y#`i*SP2G1Y>TiQ>P#O0M1^$mks2~!4VlX zEz|Yc6SGChj$CY%Xa3PAC@Bv8x_N6YtiQ`L=~ltrD1rG#i2fxJ*#`z&|Ga-c>p}56 zZeH8OuC45wl%IZ)H6}8m$Z!(Y8QQP3);oB_l@2*(;AP9ji8)s(7ps{Xciw|0ei~(C z7dA5ESH=L%W53rRZzh(V&VCFvfd>~-SODG-_de%MgQZBQt751zUyHBOJ?I0S{<1cz zld*uNwU1Ldi{1*`$GnPi-~4~Yo&K472LJY${t5;P2u`4&NV=HN$ETp6m!}}j-xrEI zkSaGoimskA0aN52f4j4J*;N&=>qxg{Vz@ViT;@$@EkG8>SFVYCI6Bf{WI9v^=7vl^ z!#70kLjvfI;yruzZT@a&)MdDlzmTyb3T`oN0@1tI@Qkb$CkXetY38a3mUv{PcL^6C zmYYZ?ut+%V4Iz1-&<^OXh+cSW`I%S%#Qo99`XA^Uzl7^+qO>;qBVk+^a%qv%6pR+0rrkiJ=ZS7B!7Ro z36aT=*;Zi|7ijQDs7_Yc-B9G-Y<|zOOwyD-*ihdvrcY2-Om+9~(w@ojug5n{;Y(K7 z{uM~@7jjT4<#3wJ#M@i*>5+}$X$j_>|iVa6X%>R2%cqT>ucWR*q`3t^&1+Qe)d&nt4A)I@l ze7Iuxo|-7^4NdQIXD7`!HD$;6O_+|H4)ABEjIjR65#i=u^0ow^hwN?Gn)9ef*JDi6 zo|Tomivp&ZOtLurYnfxL{C|8Gxh^RQS3jC0qnh04z`?-KU!8gES)El^1jeGs@emW(h6kitF2&z#-m!3AA4>@`Ta)>Ff4pOsF(SV)L47w~o?7Ef$n_G~s z*tHKfj240}EB};F1g8OArF{x*%XSU8#T}5oo4dJ>8ynIxZYK1%}DQ{bC5z0cxgc zR8etBMC7_uUG{&nR;vLu6S&?RbQ7F{BD3Eqn>RARvzB;{P3vh<j-HhpRLPhz4dOiOGuWZK>R(HSJW5?X+XX zalD^)yvUi*0Q&h*7WS&GhsPPkQYBul>GX;N!Ei36Ufjpz(}*4XAouNfMaV)bf+Q5?KkM47~O;YqNuSEQ8GTisV2Is4F9L^bt!elPU)Q;0+f z2`{&S@6@X%=8WK1!JW)&dDCaYGpKP>Tf1YNnLWncPIisk1&YyEZ&5sd^Fz+o#Z-+G zBzBejZEq+9RG0jHBEOp@a_Q#)@I+x3;atniuNvMUgR#M5!G_4 z5fN5xpXfV1Z6^$mw{|GV8q19 zUlqIUd51uHs3HrW8qOzmh|G7W&Rg=rbS#;!uI`BKGy*0EUxLBF%#CDDK8@+pGFHf6 z4PbR8k+PSwH*|B+29H*1eJ32ZGoo&A$`=+UvMM#N9+*W*xo*Q~tdfsx9=W~bDwByE zN;PJQvNvA@33rsxTCi77Q7+I89~h=bC5gn@F^MQ(#lqz5)@4nV_N_FWIWnq8mqLJc zz@OWGCM>hUbMqf$c?HDuZa~!CZl^oNu|11ejd$!E@3=+$-Ig-4>NCStwyobuLd1i@ ziOGr>x$pwuwnp~PHz0I4G|gqz$Ws|X5e0EqZwoh$KP_S&9R#fEe1TNgBRjzZ%kcuB zlz=t*LGAubekY+DbR=eWrKKql33pU2Mf}s!sS;_R3F*{O-_eBG5I`9-I*A2DXFs z+|3m8imK-Y%cJJb=BPplWIA+^e{OoAdSG4To`o){M0rm6=(u3 z$63SS6k=%kusFY12c>zXZ0*xr(3TV@%j#wuR`z$6kLZXgef4SWbzoglD=kFF`&j_^6v$E zdN*C*|IShPFPex5Q@ay4&|5n_HZgft1R$%l-b87+y5-(zI5ru7Sy=daC-|m3ds^_l zu-p=wkQx0>`iI^n#C2*c^4csB?rwL2U0hg*rHtlFCcgr55m`_cB^bnnB519jCc1cbKh-eR4Ii1mrAiiU6_?bHZT5r;R-?;|Sprk+G?rwh8cP@Y85_AGhySgyPJZh@q~QXo;8z>z*Ay*`OG zqZ9)JNy?3n(-%fRQe6oRifn$Sz`Clpq#~nPwG60W$RR#|2S1hR58=TuF2W+^Bmz;SKUqThI_pmTDs*Xd@_6kfU4HZD0;=omM)|}um&w!G`xsnEUrra7;#_6Iw2t; zs=%6iZ3lRA_0Y9*WEEB_uy3K&4#@Xp=Gfmw4L$+ik+4O@#wL));GV9mMKt$}C7Z9e zlB1ZopL$_+VZR-JadB1P>K%}is-)pdmvZHBAdC{o3yQ#dWDU-$4(c?>!88M3OSarN zJlkJvi}qg2B_;fsU9lTdMII2o@wA)pmFFA@mEAa>abH7&TZin%)7Q{rnysxmm=Jkb z+~JnW*-jQ|a-n{jde0Zp{o1=i%;}Qf>utA9p1~Ez(%ec2z|2-R)cb^=dSR1Y1<-T0 zic+#&ihOpsKlRy3vB-PqL7k+9Vk(OJ?EPN-N-wRuNBh7sXsl_iX0AK<$XNYh#b+Ik ztELx|LNA{FybRn5eW$`Cm=A`QuFqqg% zpA+(nEZQd`v2)oO_dI#&x5Dan>TQQeE$~qLvhBk*#pSz{g3`)Z>7SFY+EdncE5G<2 zLDmEX*Ybem9VWn|u_Ge1TFO`SP9QF&9RKqzRgbc=;@xFaFaWO}s3Of-ixJYwe(5TD zSre2U>K*-O|8y$LC0d$!E#;LBIMvmwfsGCCO59R6Ha+N68oU;D&06>lRgQLz{xbN= zOvl!O?EtYZ=(P2mJzpstPeLSmBBP4-b#aCsdJ`>rUaHlH%>&xA0D4f0_1t&k+nQg% zF>Csh&8(FH(+M7x6Nb~g{|1UaKw;HA@#}AojPvN)Kko|E(D|UweO_~0oY7zcGdO&& zsw2J#1F^L%UXLp98wAJA`)+LeN;$aKn%{TfD@bX?5+8myEMnC^AtrhP4W)O3o}R>a zU3;;O*?FbCPABx`9s}pcZ;?1r}i`U7sZA za%41(H0%se*&Q&{(Nyc8i?4|&XjgkFR!HJMb$i?p$aGYG{z&*@pQl^tpiG?oG_T~O zDivskbjj1L0~`{({=E>okQhPruM?4yNecMw*~aq?stSKoJm~093`}hCCY39KCABnl zYlp2<%LaZd6#6pBN}2MSHd}`sc5lQ}$fJ8VaDhmh-KvOS{Il7GzvKj9KS4p{FAMrB zL@2^LKz$0B2VY+AhOBOF@gd{bd_2pj0{>Lpe-LaFdq>V1air)E1-sL{_q3J3rYC%0 ziyxRCz+tx|Toty%RWg@}@qh)x;BR0gEpPIvnUTRNJp4M0JkbAlpXr+QqhhN?O>V8r zzTb{n0kC9lh%>Y#_=I_+dUb1&m?{>3mPMPB&fKEQ?_&v7g}Y*%-%)~ce1BG+ZFIoD z967I)k2;B1a@Lm?<)-?WKy+Gs&V(iSo|N{Rgq|;C`4dr(|9d5H*%b#y*)8E-$W6Ms z{7Wq8ch%I0hbYMrw$Zg)G^0AtH94zzTHRDWwJL19aN+J7%x`XxyV+7wSXYn$Lie8)Zbb?R{p*M(w9 z#hqm9%!d3=cALH!kpHT%AFGibB%#kiAi*l!So$-%w5o4PPZxR~C{weA{tp5>V(B4dJ?K z|0;98V^F4d?!fYTlzDbjjzqJhyl_BzQCtRNly*((vc5=FkObO21cd4e#47!iaIrf~ zUubDW;HU|w`qZvH+t|0UTOaY9q?WZs<*=z1iTzXmeL=L}KNDlS7TVSY5oLqFW8Tic z!kfN+-F0-KpL+j>0NAS5T==QMek&u;bQ)Avc! z-S_>I6(S+>Q$KTHG_ZFPg1Zvvl!AtW83};@M`CqrEd7Zr_^bCCW|D-O$rWb(H;3V!h-4Qp;-LZurZ8&=X z9&aCEIq|7z(C4o|+%SqdEil`!;I+;P#n8Z=t150$Z1J30EQafHoK_(D`PENCr7Tam z@c5NqiI-+nN6)Df29w8l2l8%7a@iT?(4I#$GlKO%vj{Gt9x-d}T_;-%S+YtE*sq%m zgKh|`i+0sj`qp-aU1?u6s9VH0f1Go8^@10Er6Qoe$H~ zJakti-P?i@1WZ?s+#gBrW%n#5>F5B%egoqxk1F%Nt<GG{%qk5qz7O9BKZl}7c2l=Wo!HC1^jozuU9bLSY+3S#} zH1OFHsYOZ7=y*-w)$z$JkTFvvVYVN5axzsk;wAg?sKpv>*8V7^9As_cFr)yPUK75p zTjV|MT`tFZ_vcPWWXB-E4Hi2LbRrP+aq)~?88U*NQ*5fr6w}Mdu05C)4$zGg zx%^|?WKc%tkkPco9+JXkRvj!Af#Cvb1aS@jN}Zgb8OW@5AIu`iG+GuE;H zgsAo8NCwXi9JCVChoaR=?MiOPwT@=NU*J>w;>5pOOrB-evdqV(S z>*=YfEk7ztl|nl6=DHQ4L(7;|woI7^E#3$jMCUGAj@F|O*X!~#a^%yty=r=#G3s}v z(1L!q4c5yJ#_Y;0p4dlqV7D29twSt;b6$znqV-o)o#AOoA-r%?a8vy;#EENZgsc!U z3dGBg7L?v=ZKwBaE|lDK=Zu#E8)}SZ&l8{lNd}6KW+qEBgv+E{C%6xi6UVx#A-fZ#6O<&N%!$B~&Xue zpUIyMObL|*-GfS4#y}&pD0nDKEMxVU_kuf03bh74RHPOjd&*hSV(04c>7Do_`U$p&namGc%hMm=WX_@ojIUv@dX{4k|4((GwS3CS?TS6HPP$@*6T3 z&6sg1&NM-RHHNRv^|;$AZ+3^K2dl$~)q6pyEp!#Qj-&rV^i&7bT+6bqjl<>GA+xtk z@J(!K*?Kt6Y43`MCz!}6m~(@`3c~i`DBE*ekY)gEz$C-}X zJZVy^JqPUCDDbbdvs{O#?p_ZB!~ zB07L_XbIq;ORTFthpG19*1Bo9(g(};nXH+|l^ITk&9a)`;Ej?+SA`*FneayXJT zQv9lCXNvB?8Au5$TXn^|_ThZb6b_v{FWy{nSiW3H*Cqt6%hWG5^wKLN16E{W$1AFbk# zo@|YN&SY5YRde6JIeRvdy<=XhjESLd?*{kJT1q##9BFZ0-P%#E)YR0|gZxwWwHbN* zz|eU0Vc*0=qbTInuZ*$-F#Wa}1L}BWk^WkhBSih!=}yrpN(-*im7BZcl2`C`qsgi* z!ULe44K~=ij#X4N4|uotI+Xe~%Cn<|iF}W5EA&)01ykiHIR!u)3A;wN+1L#Zn+mk9 zjVv5pbw5qnC1!>w=;~5G)=V}Yvry5?Z~sPDrabVt#z+-*D~^gw|6p>6AVkn_?+~Ss zWz-fLRmE;hM=WGuZ$E)k&vyE4Qjv!>znYs~8$wkww9rG(!(ggMjGwWRgWuedBV#73vT@(P?egP`spQ=)rUyUw z4p$}ww7?&8k9aDok}bxK#->WCT_}Fsn%XRB*nTbi`*y3Y{xOlnb--F8Pr20M^JKp< zfA(DZzVhQfU{0&0ZW0<;y|1FVx3$z`0Ddrj<*i6|iiL%CG@Jh6&dt=bBHECmchQbp zo1F6D{)+90-VtGz@t5f0L$2kKV+Os`Vbh5|v+rIH0*OB*y%gG2Kx?68+)D@eSKHf@ zeXWVRhcQu6ul)mlI%Sm0X~k!w3j+&F9pbWo){Oid*SRGrQnx7J@F?N_yB6r_577%7 zaq^FHP9B|~wvO$CH-uw8OEN3l{pVH`ifAjP*!Un^* zAU|pB47VbfKfRH$81qG*WGjOcc@3?crVN;$W&ddHf9j)Jgo5rm`c z)YvHg?r$Q(Ue&-=P~;SByOEG6_t9IS0>m<_8>}=*X)v6`-|1&>Dz-f+RSiS1t?lDg zrm01QU|%kQ)zPmp^s(MkeQ(F_c77}!3x^RQ-CX>szRs(}NhmZq-8vqk$E52&>A({n zc8$xe7@Cq{1)p_erPYV9z(_-XDqfd9=xzdF0kpHD))O{+D1Q%{92m4OH22##8NYT& z%;6?l=PU6JMV(@L5EWVMCPNv=>WV=b-NWX{bTO}-&J$Q;t#VRvl&1MfA~0yuleD0ipPlH|v`rnO4M?%3Wqf&W&h7zohk9gBpDWi% zL4F=E76)yo)gHc_tW_EsEPk@;c)c!l5PdackE2oMXu-74trkv z)jJQW5QWk$)*YXUtNTXl>#dy=L%Ivkbt@nFt{gR6-Eh*{3fWG{H4d&cIbI^74&uF| zZ3S}k_N&(-Xk}>Pu=T?Zh3Sc6cs1OSG?WKWK}-zKCO$s}!df1kJH41b1Iu znd5+cIBcWGSwXCSfIyk1Q7^aAbK3ro__OmN8{Mcg6YeAXv&ht*6-cMCz(R&vH4FY1j#B2 zd+FyFjH+(7?Mt#Gw*_T{Mn62(rap0}>FIElu%VXNajdVmCDuA-Q2c%YPu3Q=#m{ka zkeC5O6EiJC|;bCpBu^xmdW@hcHGkdXGQUCDRTavO5a z1>-8-GoFJy1(-!kYoy8}q_=)WM!SPLvcsoCKAeNgVl2(1{En>ju5Kv$#a#8;#bf3m z5rAFSW_vMx|KAJmlzH?L5q!0+j$ZGYB2+8-u2C}Lm@ns*mO{6%VcrV8+E?@wC-eiNE-f!9yG z(VIGuHGBd{UI}^obJTWub;#7rK``jZl^im()6N9(a)KX0GH&P^PEHPrq?EqO=`Xju z)Qq8tfaRY>(RDZp&&)0j8@3?aS5BDO9=@N@v1M>J^wJXe)NaJdpUQ2_I<_L`1Nt1e zI#o@-B{>INRWD@w*8ZN&Ty-T0$M*PlJe!@_q~6u_xk+oY`Fqwy=Jh@q_5p56NBeF; zb3g5hO1>EgXWr%AGT;B7q?3l&t!6*c5Nc&=uk)xyX&Hh;@gP1+}zl0bG<2}bBN4J)l`A6^cGU{FHx?u9dJ15 z{>j~AAk@fr7V>p3Hf#KquDCn1@n^giP%KXLUlQHDJbJgLSKc97Hx@9kA5_GrbvW0n zrg*y|ZHMpCBwxZ?E(HO3R7+JoSL4?6g+o1|_Ul3H?j9Vf?h z1!i_}oLeyH66Pqa-AZb5mGUa2aTlq7)c@Tc{lo07KnA-&9LozQ=f5v#VQ{Uu|BViT zy>hQFe$A&Wf#?07G(YLODx< z<``k7*jzXQH1zpJE?Zo49(lbjIon!Mkkj0+XU4Bl!PtuaXHJ#=*Zy?cK-MU&tL;s_ zj6{0Z>kw7ws$x(^e)H-#%85#cvHKHuk)VD(9Q9?hKN1Wcsu z(;DEYBRv4S9iJCeSAI=rk>gICtR^g=Y!5lvhVXE}#7#xDxdB~#TEk;zPtk;&gpl$X`fFuDIr@UKL!Dg)YAE-Rd~Nr%1COhFK4i^weh4fqEVr% zK#tpXceo^B_`5yjtw+)G4UzKt7K>JkK-SeTPPLc3xtT&~GxakZpU%1u%f;sJF@I{Y zjaTMK+}}krS<6oCKZr0Qvyc2Bnot=n%7o7zbEX@|dl1;cOi&MLF=aOf?MT8qbB7!9 zh^vV6Rj@kEYuFgq|EDYxmxBPuN+cuB#e#i56o>Rod*G_GOS`s2?L*>y9OYbT4Yr=m{?|m zE89*&0%f2q$*fN*_U7j^Q9J<|8CA&%Zf>m#rD~5px3>HJ?a1?w$_bh(0Z1P>ECbMD z_(wM0diMqvPqnW-)$9VVcZ|0MwHC-VoRVO>9VKRjtkVWeQ*Y^@+A|#nC#c6v$5_J& z;dyYD=-IYz@eAPT41o~vgK>!@?*3G2;XYIP1o7Ngt?@)(n{)JVr|oFQ3btB8!f~9B zI?Grhg4Jj|LWdkx=}jj#ZMSS8k&nj+4z41+FSl%jgm|Q(SrW8;T%tay8_oc9t@eeh zKmN{nm)Ir?M$+C4cyaZebz@Jgp6J?W+{BZy%~i#fc+c&k-pQ3WU%Zh_q8UbRL2t{p z&_$ojnS1un%(BfKyhP1hXq9xEV39>`{+bRQIm0np!_Lr&K;?N9#@WEJjyMqJxGoF? z5X^({*As>y23SqbxY03#i9TWXty;>}D`eQKg%ke7XD45_W13nY6pxgTo~f*!rxGTNE#` zwKQ!2jpFyX42{0|td>mr5(J#03+txa2J2uQ=1gXT2AH~4bXY%yGBv*MS`OzW`uZ@3<_D&N6y}Lsk13R`&gnRr~t*%7GJ2 zdxG)$`OYfIXHPU_oeH8HVQMp@-~}4M z6vA0#PX7vtfq)PJMMo<~Qt_0;Cv`yhO+W0eH_NBhrbS`-;%+j{@|n^d!unci-!m)u z(FCH%M)B@0_eMD-BVJXFR*Xx5@b3?z#BI{&ncoxrkZ9`t2F&%{nE1Z=Dqg<{<38u= zd70*g%9_I#*Xzb>H*92bD@q1>te@u$dp9FiP^iBp-dpakhb)zjAmgarv~TC5>$q12 ztL_wO!Y*?>2qB>uo$C1p4VTrx@NhA0!}%`>V9@AnH#bpb8>!^g$f-x4(ow3z$?16{ z)&9ZPZ^~T}41C-adAbeJ%o9Qmub4N<#QLW#27a;JmAw2gsIPpZ7BA{sKalX^*0VS4 zpHRPd1Fdbc>jnrv=Kf;GYus{eP6pC)HmPTo6K$B< z)0>-pSbN?ENy+0y1yJ6=gy%`$Gi_ICqg`$HVu+}c_+AgR{GWb}sL&KMh>`4do! z6)o;FcgQ)tlu3NIlO*k^$?7IZqNJ6%zX0Yz}V^q4vGTf7mGJORK}Mn z^=yeHAA~n0k7sX~vgcPgxXH2AMbN&fKqqMPiPr#WdKa)pP@q*`%Xh>&-w(V&Y%~XH zy9Gy%Wm;AUN{=}s*zPxC7RM6UnfHnujx+W4s2(tvUpdJ9b5U;p;B(ZFbd_IDf5D2)G2L*q118{qiW`jaeo9NEVp?lJ zRK~21?{~;iXlZNEVf~xs(t4wk!?~aj{a$+g&6KlNvjICPVz6uN_nyn3y1k1B0szdO z)(~Qj5|&u|oxK+y$Dyt$;YahG32yvCuKd!n`wlA?zP_tmmD!bDkMgR_F^?N`a(%5E zRTuz;taD2Cy0-sWcEuSP2b?MO)#_${+uK+V!+&ZBS(Veaf5R@1zb!MU&7*?==!jI{ zo3teJ7m8A5?;u$g=(N8}Z=TWhfZVD0)qgK&wHj+P--VYGVtaa%8QO-XU1moaY>kxh48lhL8j^^QT_ff}8)h=x~TH^c)LPNAk= z#r59dId8~^Tc=n3C8u*9p8VDq7Dj5n_RH!At0g4dU+iBiLZ=VS{{6X- z*IU1i3a2%rvL$**u_O>er4Coj@#7RZOnKHc`sRd-*O@$$v+q?-q-I!`JX_!_%nyH8 zldx)yGaH+|~I^5{-SEaS$a==GMGwTKG zxFgve@EQ>4h=yPG*-#Or-J1iQV}t9>RY<;*qylcf>23 zj4IG{(Y4aE1>jEBr0XkR1na15ZehPtXzH0Yt_A%t+z>--Y~~UR|2OKZAi%8qk$T z0qpr2tpk`HHf0mW@%_5vJv<9(Zt7jeK0BT_vAm9h5j$g3Il{|I$Vyf>s4l+cBK>L~ z=O<@%jkKJR$dpscPdZ>*=--i}WnndsHy~pt`s;fm6?91NrK9fD!2Kp8O?{7y5vToD2Paca|F;gZPK=BxGK&`w&~@b{ur6A#Pb=!@g# zU)5EZZqoY2Lyiq^15fPM8DXJ~8@by~z9&dYW1WAZZB4C#^Z$rsPqc$+44tP|bURIH z97M4v{a$euvdPbW>6B>93^1;q(a2|L+IR>sm7s`?-kNvr%l>%QKbZ-V>lF`{E-{Ub zXg*3(%!r4-Sxzo5=r#Mk7v67o%CC~0=S#3F3z~Du&x_Jta7JZFxX{p>w%Hw8wC{ox zj?vPPr^uC^2l0bh=*Cl&K6#*>;U!sRIBzguYIe&FIYmZdMUW;dO;9QOBi32v%IJib z!mh$)wGD*i1mkfsVDR}fV8?S-tx+|t#K+7N6JfU(+&E0{r5`Xqv#`<@Ly1*BY=z*%YRWrFYXgOa|kHM=%E)AR4Pkeb8D@7fK8pPnD|G+BV@Dm)I~(C$?x2-x>psNF260dhzXXgrU1~} z55UH*pikN!?xqpW1*9jkw5BFyo}iC@rzdX>C~RBZ1+&nDj}eWyF6O(UAB8JjL>J z*6rKsXLb?M^@^IJQYK0F+X;^WC^B7T%$r@WvJtj$$=G=O%2&^K>A3~)eEjdl=T&bh z&`8pbCP~N{j9->8Mrv(ql~FQLRg$yvl=Es47Re1b7MLY8hhIHdpe8c^ zVKFzmD*60grLw{^9oD>1KHGE@O=x-=#uu0`ncs9*QyJ{dBWZJxTH=>dWfgbv}XKMYMp zz}WiMHa+%@po>=%gFOJOL606rQBET5>$2NcT`k$lW>)tD*HLMTXaLmp=J)Knny3Nw z^4=9U5uwf5`eM~3U_0fe8+dW2G~jO2*^$ds^9K6k@q;I?lzd$>3WnC1N`OODQ`pakBh?A-7i9Cmc z^XS$!E_P_j3kvcOx5OQ2F*JSTmt@=_Y_yDh44bkSO*SsOzbSZXGyRgMs8~`p+qNyp z=}*3B?eI*?KV$9$E7PAI>ksek<&c7{XB2roVX}jZmMDSH`Aj+3E?Ix0+o@JwTS)SF ztUq#C$v~-AQv`fbs&{VedEfhRZs1Cu>6v!VTl!Pak*YIo)tm`my@0Tq5dCG0&0{gq z6{DiJ3R!OV6Y|yEB$WbeWq%ueJu?C52A!^yT8d_ z#-=Od`B`PGFAf`!jC7BjAw{Yc-YVyA8v<0@dQ@MW`qnWBAGGUX1CY`7Eg4NbFpJH@ zm^RmXbUKSbpb$Wv2Mw^G~eeMh4U|nV@HoyW$aiI-qCqycdfs-^}&n zmLzR&(z#KRwrh$lO{as^(H+tYK2Ql5oDHvZcVn^blDpo_%rGXSbEz$~NiUO_7No5= z`#wQ#(a5+{R#lGW=k5(ZW4)UnUsynqvUJn7RG=n2vMZrO6i1r`gCYVAPe}dxUlxMV zF|&R!eL04f5Lu7k#O9;=m6P|g9+#${*+Lrm@;){hefie-huXb*ch6Ps@!gq%FX}{$ufkTC~G*i2IJyOZ&fM>%W^V#>WMu& zztJnzAX@8fRtGa3-SNz73_7ZA>Sb^sE5~Adh?-6wyOmM-ox7FgjZJ$hO8N}muOBzB z#$_kO#iadZ-9dhbO1xz2 zmgK=M)sJA!=!)QOLp88bZ}v6!9j-LdMppQu&W{p1FE9MrwS%KtOx4}g>o_>>8#s=m zJJ2&jNyU;muE$$!XX2jKy5*H;kpsa5-M`c=A$&4E+DH9=*|Jnnc+cg-{<+c*Kz-bE z4rTw}Om1Hh5h1}h=H+ha&84@w#djtLYSh=u(u^m%*Zgvoss-cY;saw>FU@)bFRG@th1F0b#j?7%{!pWP`|PDB)H{>3Ap zhG5zq(-Y}-hv&-O=P$A2;l(cQ*Xe&9*2B<+bi3bSr)RV9)6^AMAKq}NA=WmOXx!Q1 zf1K?80Fqz)QmWDSDd(et4;OpmnRJJowS$i^z2~#PU7Msbr%_%?Dr0j`o}?6jTHsfL zWd*K86gl6@n8(_*6qFQ}7-=Tt7r%&P5Z{(RwOUA}0qwhFZexQy4%# z3s1C%7B%m>9{%NES|;cg(mBTa7@{OxvY3ZEs18Xi+m8Kwa0J^6Yjja$sKuZ>mdi=5 z#(P8P`Ls%0lWe8aj@vFFJNF&hMTk;zA9VAsMFC4C54tGyXX9DP^=7ER{JhA(!OCCj#JzjGovr!RP1Q3 zb8V^DRTPvO`9h|LPWjoR{nbC?!7O7sF9o^swx?V&c>eSklng&T<@sOh2f!b>{oNRz zf6+j%g-1*F-wPHSq>%p&K<9t`=wHJBZ;fIen&59HqxXlHu<>|5)63-a^=xx~i!^qo z3xcsm{5K!j&GiYlUVO{-KfiK?FwCbcruRZ7nU&HBmKie`2MI$=wOAa-6Vi7I!!JI1 ze_2gtEccN+)UsyuwZ!Wy!=q=J78}3+IVE9l^*^XBtX0rr`BGkK| z^S6#qM@e`Lpjt)7GtnqmnBlkABC_^*6#VYyYdn5+6lpm_?GJkfc60?z%BA>kryuLm zTR0ZKavwx6t;9#m)A|&C74XDFh}cG*wYArr<;C`>r}urg6FyyM_Fa86Sm_>Fg_NVCgh9UqAv7ylK=y7|HwP_&}qw6jL{e~Wkecu!|TBn1CwKN~G zN@+$9PZ$s;yLAk2)Eie!?#P4~b+7PJQY!#%nb+_AkNcjL8M7Uj@8>udq&c~xC1>GY z<*4oZp#Xqa9E86ZW->PkR@c+g(resi9^XBP6JLE)K76=ZxTnQoeMT+ zeKPR{es6=jaNnjY<&Hs~H`lk@UtGJvDgq9x#A_wyW$u?G+x6>!Z%-JlY)voFkVW*T zxB=U>DMbl-f(kcuYI5;yMh3ak%wQDKFs% z@BJs`vjYp0L}DGVR;Vt%O`(lU)q{w24UHUTCUGvg8E(Oo#0|=_Vd^D6$K~Cdsd(e` z=POx-xjj9#(fts{ZazD86X(KGGN}s)fF)7bqZ<_&N}P9<*q-eM=?!G!Ljtlr#Z z8#%L+4g2E8wHWyGy_x!J&(xTtN97JHXX^xu@?*;^2N~baL=)}ltfxn(K1j#W00uXm zcXL_YS)Swd!TL_JE_>S&+d`LnxV zvEOIph2dwvhkMnuEJY_7hTC%3o-LPErt&0LSD^>j#iDan#8>Zh(nz>0l-;q74xmSU z_v+ggFCSQ|r7}lx4@y*0?o90c=^Jbba)g-5$<07Wo*y#(VSj0r755pBuRJy$)>7B9 zw7O5WN$5wZWN*!vb>nFhn?#=)FlzU`wzQEZsKhZv9b5WACLG1YMIG~mxaiK(YP zoNcJ>@REMLi;Ph}di4uk$69N&>@JW$T0@H_2cOe^(lr6k&+o(3z`d+gT6HUf5EnN! zg4QtOrx}Z;vd&KW8Bcc}_~&8523{lyT}GSXq$W-3FUK0CEiy4~rcrU_}k?ubdb~ z@I}T4(QJViT)8(6Dv-Sgrol;uCi}Wgun%a#P063*89^`YGJ@tFcwTYz<)L=YBh=;K zG=BVslQ7mQydMY!`71;mTD9EkX{EtdUhZ;Q(g{wISJ0o2@HX=j^1Wvve^a|P$iiZa zNnHL>*xs-~aiSGulr{wapz87(`w+Ii+Q06im{~6i9`u}@%hJh0FRG%(jlG!HpUr2M zaMs##x718FZ0f7=WtL|b$BR5lnADPiro}+i<;#bD-^cQxBo>}02JaYw5Dz4@!;Vw-fchn(w4FWX88+oh=C(}fp(g!}T6>867Yr6USu32yPt+`-Ba zr37`!TXYAU%DE@rG%15oJ@9WD80r!~KnUCtLPJV|tqh(cfAuI0FKF2l^ z0rqp*O5t)sS8l!_Fk1DBi;eP$BHR*Mk?JtVwVzN93F@ip$b30uWp8b-uPz%L1WxJh#gU1{FTdr?R-!Ua#kk5&{tDue`cP9camyxalyP5 zpk8XBKLKhNHy;GPJV*it!w=_d938pauh#(=AW<>wqBSDeV#M#zh)h(u3i4}5JCva`c_x9LpSM&iP4I|&poHoH`Q8wJS@m*=fZcrmjhe?5oj+}uuYWd3_(!`^T`BJYlX$K|LdCUO zX5Z}}p~Wcx!#Ybus7z=*G(X|hN{T`$UkqMM>9fw3Ba}}YbD4unnU)-6UkZ~uYN3bN z3OJIZ7DiiIc~Se|B+GMN+bQ#Tan-uA)-Z=3BCRX=QVUSCw6e~K9U40iGbt>lv1O-81v}t{=REmYdK}*hM$_hah3bJa z4`3DbxxakBv^Z21f4{W9I7p7o#KrgnAR{1=F3+hckopY*a}6lK5FW=4TnMqXz8oq2 zAnDg-=svHZP)Jb5=;O)E$KYv-2b=o#je}i!6ax$ZGX%;xWkQpcCQAeQVE$sHQKjiL zwbf$su6K7YwWYlVf7bP|ZoG}DO=cD{$&=qqa=tDg1~JgerM~63&&e$Dwmax7+k_^9 zB=fccgC4rOD)NIa*Q&C}Tne_YybJN(*aGmM$@?iJz`8&HOisEYlLWO^&+j!&TaAcS z$ZA1xk88oegiF~q(BJfHJ?{6j{WN18_A18L9xbX}^jJOH2;Y_cU{{8KczUnygr8Nb z3DhbxWgp!)E3A8|riw)6|0t-#aPTPtT4u?amBs{nw7&0ycdvM+KYZH^tNyh&(4T41 zE=UNjI<;UwC?f;maIBelHI-7K`Ljr@)iJl zy{=;*`m>@1N5*^$Y9>K{Ks;g19+)nFOe=A$f0q<6xKwxZQr3)U#HQRaobg613vJUw^u7 z8<3h>?NYO)rF-d)*)@Uoyb5|J{DHul+(8LlHArJ@RRLlWm`Gt<5mSwM+ARaG5x;94gFCfM%aR;d5Rx_F0=?P_XGLXvg#bnjSx(hvX zMo7$+6u$cwl&gJ+eNAZ}ZfLzy*$98-<+V}ouXfRI8RIRJ?!Qu}CaU(dscmMuv}EXG z3RyJj7r!ZC#EJ5Z)hBSjPu%&%ebKT9e3JWBC}my4qXa_n*XS4z&p5@q17+y_IaQ;k z>_~rSf}OXUf~oEkU5*HUIS?qRLQX0*5HuVl(J@WsFWGF*P9+;}kw>GZ3D++fDcL)P zK$R(-o}B_(mgk~r5OmczsCKR-py`X63ETxmDNei3Cz@)T+a&8&vX(qR*U{ljeA zWrR7y3f*v!&ttaZ)7Dv%>cp%tn|xIz&Ci_5STHw`u=qALODf%;8aJG>1RI&y#M(V! zN;7USJ+R{^YnQKM&Wy25t)SQ5e3Kc>9y*BFynT+HAqYB7Qw{r$$34di23%00{oN); zrXucdvNfGwx%RJYqmzv_1rptb6Su+{C;&WN`U8kU=a2rnZCqF};G<5=h|QIrCssAT zAwUxSp(4kDPFAV%j$3A-kG;eY`j3^xa3kWc4-2O(aEds8>!5ak|5-MM4{EM)9thEG z_SqXWgbiwgbdFG4l(lJ|17{z;{zjO-R_JS^n@oX|o<_kM60__ejFHW?s$BNhv}{_1a@24Y2UzxO-X? z^Vf3&j(vSyw~n{8e~Hj3{^0}6;d^$iIm59p%CdPr49ryAK2rQ_e&l2Hj8>$i4Shl? zdH3ko>q192g-lVhlI4&Q4Lj^!Dy3P)39i96(Y}fvTM@#Cd(g6L(Q-|ry-$d@Er70e z5LvmKnn29-V@=Ngh`Eb0nl-RZM3lcvVW405-Rw0zWMlfA`6vM*`_$cWh6aDD_qVG| zUIqe|W4SYE>G&YvVOGS~Vccr%k;Ul`{lQ4nZr&6i2-9@oC!r62<-#8)o*H6lsi%>Q znL;|Lo@qe}io@|a)vB8DS1s^d_uqgPZ{xWV`l^Tl^mbxQMYL6Qs=|~Ef4pGc__E)F zXJdR$7O^fFe*YE*{Ys@%Cb{_gi4NCVFFL_Ww#7)0iq>kH+Q-oTRb#D-%F78A&@u3p zFR{7qE^ED06-u_L)l>+(g%d_?tWrw)V?%FA;pz+?$GKYFiar|igxC!e zPDhJA^!eB|BNwgi>@4;yyx>Ll5S!xb5{XJ2!3!MMbVY#CB(>mnPJz(I_Re-Fbv{KFd;rF@ zI@I{%)cjp3unR*BJ#q!1XkNcGPJ`8qlhOXpGI{F~b6)twaSsJ9R{mVR%V zhu|s2#oNH=q6%v zLO(d?(-WM^?2JC4qP1{JzZoQCAL?5PvC(t`onM#t@3+BwawrD)Tn-KSV|6wyuCzQd zdt#1Udnv8yg;PoQ>Q<10t&@K}oyG9R1(cDz`PJXGFZ?2L<>Jz~B$Hz(TTPa+)4}yJ zsQz;b`2|y)@up`N4eNC^OXuPzR?&^-5}T%k9dxCX#BY&YF6ZZG<+)r1%4nr$x13m& z)HRY#etB>yn%m_?!84pYDl)w7SJcZJO3G0`cdd6X!Xy4C1bl1=%&xMbt4u|hINg_a z9Rj~vzC^sfpWV`$=;&D2G2X)?L%6{PsPaJ-B)cjgtsVcNqN@DVP(Yz8d@5vnB0Uk#*nGNIdKbhStAA@4vXusVd z4qN1mQbsX|uh|#*-2%ojX%Hz9S-z9$qs$LtsP6WDMgnl1&~_M;32((~j@YbP_HVQ1 zB`oU2$CS59`jYMszLL8;0;*Rps%yDULQ0+`n%WLKfTOHOW`YEAGyd2IKdU#;Q^Ygf z;9Zs)!a!PQ;u?)KDL_jLL zRZyAVt3SQdbe%~MHfCN%Ayv^X*Cw{Srtn&kl#Yr~X*Nl*xrjbI*OT}$V9RK~`}ku& z6=GeZl}T_2dcx2_c=4;o3Um}7^jXV3R%LuTrk_5V4UfFgTF-(kO()#zI z!21W!DOQc6HVkXvJfQAj?jlArbLrdIVS&GvvCa`QkXBN+Jwu>7RNbGY+82XHD(0 z%)-L4SNpg(<*7es7RFoFK0>kV4IX>_&jaFr_xPWg|6>IAzuh1I@qhsLj3oFnILE&k zs(XN;i#FF)PxXJI^2O{EY8suo4(d~RJh&!a@E4%7%8j@s1{8Q~r0?EP{FL`Roy}fR zHFq!eS*<8N8y1Tc1TjQ3-`V_-_*R4CR!GWTy<*cLH7+my*5yW#LzMFyatDaJw!A0; zP}}`}c82PZIxKO4MeiCBjA~j^P|EGl-6fv<;`&3rFyHjm{=Te;t{%!`hgaU-WyEYy{qZQ<+!Bvk zI0bTzWzCXD*5rNV&5B=<@s&Gdrxj9&h~@c7|h04e4VmwD{m z`m^l1@9Y^|5Q*xYJTlRT70-RftCkzz^7Se`Q{iJbfsW*MDZB{DmDB}UnAS*SL0HZP z4Kn-n3=;br?Us5Be|k5I3RrV?6Hyw_gS=3spywN*E=J6-oVJggE2N%FV7 zcn=%Q>-rGgipI6N_oL0qzrJ-SlK(dCtl|K+q(l)vpHDR=batBdoqkZbD>LfZpzMai z7|#t-#hv;|XdP%)I&1oX@pYi-)6Z&NOsFKS-n<8MYDycg?YPFM*aR9jboP|#o99Pb zEt~tPaWy22@#Xw1+dC(7%^?iGHglWED^|%}mZLv^B)zND6JLCk_K1^$+s9&CHr90i z3}hczEFA9tvXihm$KzAom=lT+G4x*P7xr-Sdr%DqMC>48j&Q};+9#&Bs|V1VLzl6z zKgArE&DA6d9$o}V+%z=^e|9P3?P)W!W3y^~oFVE#cV=uY@)gai=T1->)LI)%(SCNz zxYi9AIfoFpfbaq`&Pf{I5*kZ9)@YaWuSqoLipRj4e1%dtQ@6RR3({ccsD7tf_e@BE z!Epv8qqr0z?I(YpL#rZXiHKdsA)hX{CdU?Lx~tbdwnjhfTG{n`KJ#?W$^ z4-dNa;3Zt~hfxC~_n!+03)di039b2bt-cXSpkoiNxP4Er47>PfhNmmm6RCjeP+kf8 z1fpIZ_h~`14(hh4bZihROiiu*xFnj&z9I-8hkT-dgG~;bt`~;GdBm zWe@By^`X?1Aj$}4Y!`v!3F4Ux5{zR2jKZHx7hl`spbs|H3kPi#0Ir`Ar7o93=r z&J-*zUD9A-BzMIFwE{wR6d*b4_+;FzF17W5s}{_)fMEQ!O^k01$`Mfzcyf7ju=oct zq$SqMLZa+@htbQ}Eb~~tdVov6BfC*@Fm(FU{@JpIMyns}G85=IZOJWD1$TsXd#!jV zsHYsv7dW=@gvrg?87bLax}$j|wy59H@9ueL@)@~Y9$azLa&lHz-JUzEv*QU2vBSQ$ zpJ1|@3CJnQo3_ef8!_G{h=ptRq|F+kQT0!NF?Py3z;wRw)sq zW-1l%wsgJ9ihZw~%}Zb|d$$O?tg0>=ciO)1aixVB3RkOIzxX&iSF_n)<~*LjSFwTyPW0tRaQeG8;jd!~bIrZPWxKcth+kElQg>2n} z!bS72Ncy{Dja{NzZt$`4+SoJ{=i_kf;B!^})V^@1jbGSfE9f#EU06`X^@Vwh%j#IC zK;hWx5+P&DU)}@9==tHX0Y7+giuxDw+Gmgmneg~AnqO*I*#N7L=u zi*s#{t+-j%q56iSQ0`9xt#0F$AeT%YK?B)QRK5Kz>pPiKNUg_S}iG+|~oB60uh1a!pmv?DTRU z3J$k4EKC0j%GNjJT&*??fvM6tQs>ViuiI#-DPR<8jx5a{yOMgG#WRK8l?f162fxfh0ysm+8qLt6`Mch1I|*gCu1@-%Df!Gbo;v-c|H9GcFlf!dZsth zdE&|w?m*I`#gzb};C&ZdRG65V!kE(V(MlmRx5--`XY3`=bcpbsAEnGocr)dde=fMf zYl{{S8v=2JbpPpV=$vL-55rG-rZU!AkB{mnCw4)t(1^*UGhKf&>p|lio_@z&yXGuB zq1%-jr+zuegIZD2w2xR;VZ$#x!Of93$z2!|ymWa%c7SsF*D2@hBXsdy=^hRa3vD{BQDeAfO z>m91C0kE9m6OoZ|GO(kE6}TQ9Yt&Wv5=gE-o;}lwVU_#ZNtJm+E=CqI-q_u32k8?o ziMFI4p#kg>Kq`mKmmMl|Gk!yRj?ZXb%`Y<0vtm|T_j4Z$Qt%!p;ubh&tkb|j%Pe|- z+7xWoZOZbtAWbMx;<5m+&V)L_yYQtL5aYrrD!IVJGu9^~z})8JfTMX^Kl6Li?w$STs^M|{O*tr~39fs5HIAUl%Jbv~J%Ok@B z_4GQy`0`-ffUoxntLeuD8#hf>Z4NJ~EL{Y$_)2_*tC{+iCZk((`EI+?T!_k&z_1&p z;PgBhrC)&9q@Ip=Q|QzA!M^6^FA}K^&Y&lsiA}*sNa#>o);jo2{glOG>ZbeFWb*c{ zD~v47Wixwhk^-0BlXLo(*3L#<71ov*Qd1!=gvVesr&C{_vWF z)HoX<^9Hgc)puTB1^L#4rs%>Ug@B*6=^PFm!C6e0zzBP_@(i3F8{{2SSsmub_2Bc_ z$r|Utc2$~PUd(*M17&dcY^#ljw|Xzv#~nUP&j?-L#EX#+?S>#|`9h4Vutp zDI>B&OY536zp@s|M#>!!XH?e-6hG%arM46ZCUHDgvsuaESMhZVQZ^HFstEOjHJ4XB zHq}3p2WgvXnz>b1FnYT~$*qqwKiuDq zCS<2ee;ON)D|UTy91s>#d)4HUY4pcj%GQpu;igYgyD%-LOgRP$3n8E963)RLHAybV ziwF2eALQrev)nVkXQj2=ZqQ~8lz!?rvFe-ptsAUuaeKPq%@fmKCVmHc!}Tn2k_Hw= zX48Svx-aSvdKJuYg&qo%(;o&We%I9zn|aA)RIb}}>y?=3xYG%5b;0U>@RyD|vC!C? zhlh{mTO<~~_!@!-@~z|!qoy@dNM_p80{+_`;$l6QE74h>&8NF{4Iyr^KbeA`2Xq=- z`5f=}X!(YB{~mkue-t6_5xOjXMv5PAn+obl)yfz8_I*OFCeQyZb^)PR&ELhnP~km6 z;45jvL~r#u%iZX1Qi1-ge~;zX%VP~5j|%4(gOcLb?{SwxLw`(&B9yf=N*3fyG_lAM z#{ExxIpeTNdP%#`-)j)L`tXYwr%`WP*UkAbxciDic0sLW9oO5+ox3mfw|~UNnt`XC zJoev7uo#+Km>=n_4w6!X;7o5N0Urh$unA~I#Sg<+={{_kPXvujv^G$gfAenJ1%XaI z92S!`_dX;P!N!N#v%|^q4SW5w0{qINW{o!$<5prOb@pV>leWH(u>fsjq!_Fc%qqAB{l$u)e1iYO00&f~` z)*n{9;|h(XJG*Fl@GkzO^hd&-i6HvVa5XBDY1*zrgS$;L-cU@EvGq=2?|B^qSF_OT^hdGgTOZmLuinb1$*2d@T1EbsxW)PMO zb7i1h!2NK8MrGQm^;e_Ceso|j;fay;^5In?RI<)#Nn_~GSke7muGUtCpm8hYfL;u` z&$)43b5JuT9ECD}uVtkzxG1525 zfOTzzx_Qn{dM@+@RX@1PlD10B?c#i7$EU9B9TV^}tvJkV>Z3KXR0)tRb>&7}gp13r z%{H|t6fy5m*!p*NQ+bfL$r`P3)$d@0tE9!wki#wDki zG14kzZcgX&Gp`o5zp;+zq3fCGVc^U6&$88wltnodHCf@A$3gc}hRmsG7H6ek>fe+P zQ^Hvlb1a#gd9l}C|1!Bu^t*b~KK}iR;I0YKJA~}oV%uUuIL>TploL2vC&KDToFlw4 zqSpSQU{ur~c?EOG^DAx}5e8dS-2#J*n#h-5Fdv#oO{C`RFF{NX(|bE2=N@rEjk!Oef5@Vg zmCUn^6EHu`Hf=UW^!0mTj7s8%28E48#2<6}*5l;sQS7mgc$=hm*8t)9wd6&$e!qV% zJT~6!xxw!tI-BiAY8=A{!}$L+O!!kZG@-6%_lx&$@vR1l1HD$#P)N{=tdKIzK%v+o z%V+l^cWjtP%(MOfSqDeHPG_h2kd}HnFhkIjL2uFy6|obiKT_wN(6x}kxFj9uspvOb z%2CJN2}hy!xH1Dm-qn47*d zEJ?3A0XrY2Ipt|^uQKmbDcJogjM`%-Rf6nN0CB5GA;<{1-D^9siQk>3A(H@)^78-kDqoX z?uZ1Vw<7vbwIO6kY@c=1jm%dMRI&&8j^JI3VX1F`l1E?_f@`Ne!H4hTiCqV}`n6L} zi5%7L$C}Zh`_|FocaLwKE;9vGl7!+Q;)WU$twGE`Pwk54u)~Dq1QNQE%#uLuV)$(V zWzEuAeJ*eOK&D>?Je*J;sbii{N^x!#bi?XIhBIPn z%H@qBV%phV!cXaiVho>>1$U=9+K#LDcJ=7Nm+kVgsm@)PlKn}=;$5%mqZ?0B7uo~v z7~XP|6Eu*;?xdIH-M$k z>zYeXy#L;5F+v%&L>L5Geg~>pm9%H)4trK;HW(W9RYMOpWX@f*kgEK+Sl%(7R zRacjBU`NIxof+>C4G zt!68lst^jP>#)KC@r1)N0|lVKv2gYKR(Qu_6w^u&FM;bgGp*EOMHD5Q3_w?TI)!D!Wb-M zwke|K!wB*3szYVs2OxR?)rd0Rni4$RtgL?7VRfmGl8NyRGimWlJyi`Vts2~I9D2^S z^!Ic4jj~cy_j-_*u)XfR&-sLUyfyoY8&D9aLZU7d&trKsLIa{l2m}LC;uu?p?Fhld z@g*%-I(afz;>TE4t{V)!C8aDNnKEon+Sl^y*4UoEJ!LMWbuqo5u)xyLvsW2N(|P|E zuKcVex86?xkkhp&m`;+}D5F&Mll@lzjk=>HpT@A1Q!gnzGB>}did9!nt9eUYQvTuh z)ZfHMpSnA~Lt`EmMI`bcE(6oq@9?g75y0Xi<&exLyC1V6bG@i=m};Egf8yZ0i|p=n zJy~@8m6j8qGH(;+^y<9@ckh+o}wgNyk-u1h>xdMirXE9;*N_hmN%>#$#B z4ZT+vY*Ykr4q`v#6Wz5Id9GYH1uGAXb+-tXmGnS$Eez7KLMLJ>OZ#y}Ix@APvE_!v z5|G%I9$=8K{BieY;bnbMX1jDxrH>^k3-5C4rbZ*Y}JF%~ICiVMCU<@L@l_Oc#jd;>nUzhFPn ze{aQx!OR!za`$5D%Fv>TJoU>H_$7Xbytx%RYoYjc;PHAXbMJZaE7t^Wi(ca@g{B5?QV@A7;F(fG`-XJEk=ORM|HXqb{EE;{E_t!g@qUdXk z#1>%LXG{PnXKky(C5)Pu9}v3Bx{q@Zqy^jO)1?X7*)_h&UaZGw7=NFwy@%2p$f9n8 zl+bb&J6GDzVg(R&>@$1FHTNT--L@)gDtzsLQL|@f5j#f+g6S?fJj`1P-rCt$Jlzf- zb+DPvFe*qUkK)=(3~elCoWx4YY6H1+-y!Y_n>iKqTvOG5b*0XcGB(E4#%1*4XOzHA6+M@5soUw@wB^b?jtWzn|?r6O_sk+as?o5 z?N2YuKMBblNT(;ls0$99KQ7YU;Zn?8R53Zc2CLGyMK`4ZaOZKn>%pc#ygIn;q$4cY z%w?|?bjiU)50qAx&n~T`vszvP8OjouQ@p#KD7r6U_m>}^<|KAd@?{`UU4&iRP`

      GQ%l(@ZlYy3}GCqU4o!Yw)lwbvc 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�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⪻UcqA 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

      o#Rt?`yJ*P)X$Xs&0< zdRDdriS$8b3(t9*>@uspcsrnB!2S|TRG=~T?4O66!hlSxl>DLlO%V6P21|ZfLGT`a zD1*jrY1Xc=+XR{k@+YoIdryW8GC$L~s>USE^NK&Z$W!f!E-E>odj&BZUW>^=A<}F| ztPe*yoI}Ru&cJb;3H*1BT*^+`O{eCj&ep&C=ew-dr^P+cwyXy(+75nW1{GmJecl7Q zI?1{`568@CjOg)P-3~-%Jc*!r7~FC$SOAhDwGU}Td|H2l@zm+C^I{jPclk^s&fM_v zon7F6QKu(M547Bpd)>UBOuiO6WEMowSc<1@xym2Wp1}h8G!rN$(TfI0bKM~ePt@9i z*wM~rCs&P~5MrGEP&4uf#_H$7_@Ih3$C@7#lfl27sKGn<6yVwE zrm5!EVh(*PV~P0UNfQ171rd=59tS!K28khbI~mUe+0hm;DCP%gyWME?P!I3eolM zjZG$3{^fVpLh~9WSQG|2>^r1m8{y-I{EvTMQt>)7-kAOhuxLr>0ctv!?V85J`nvoF zhrtfK<~BitVx}GI>iswqKjXKxlk8e_5l$l4_?QwGtc19Eyh2YKHpv-)!{R#S2HYlQ z)8^=Gpalcy)v`dgvNN3C2^l_9$9NV{%qo{V8%Q~1%CodoQfmMArNBZwW;(^Q(=tIT zMPQ*66`fbk@d{!$CqnU|3Xw>g%nO|E)LB~S&x2g<1OC>xO}_?Ku9Hc#J|ruD{6a2x z!Q2`*jYHQ3Hx+%(N8x!!tM-ZSo%BhpW+u%#FYimDNmj2ei ztna!FpQkdqF>2nr$tPUeBJlOnb-sMJK-qW}#R!(Chz}zsN#S=4)P-v0*i!@Q$IOP} z0~c9G-f69(bQ1J=UtY-1`Mnu3=#cJg;kXyk>xhy$RtKeyY3rsGpsJD`bY)(>l2G9cqiSk8iYx0-g;y8O$$8fe!=i4Jv=gh0By2e*rAJxr$DmG%$HA;5 z=<-cLd(xn~9b|iXp_b?|nw8$+b^*wob2VAg(HCB;2$%-ev*bdt6^J1-iP5FGwLY0V}2PCh*y5Cdj8%Boo>bP6L%3p+y zS%+U@V)+eZwBqQm$D(>k6&%Tc3pf8v`~FwZ+HSU2V#hNe#Z`c(1oOY!HI+b0y{dtD;~UE?X=d>8wU{c?sV+Y`R_ zm^$n;4>)KRV67r9mdA6W1Z|-e>T6{QmnRg!)v_8|x1jZ^VFlGMth9+Jy7YglMU_Mq z1Ae`l|5(mmY!_CtUQe7-PK)Z-BcIt}G1_bNgFg)XZM*40Z#(-X9R7KB@k(ZKwTQ6v zk#nfE$CmU=QYoZ4vnW-_1PF93Ss1#HbC8s)kO4Jm6c4vw4Zth}Bi5A3+LV#We9=&L z&x<~We@=#F*~kn(N&Y4i&h*`ss|Oiy4b|2t(-SqOw?^6~7BR(@ML6q-#=1M&w%Qi# zCIMxIs2?+$=qva_?rx>!DenCu@Os1~2dO6XHhf`TA>MWaec<C*VK$e794VA^D2O~SW!D{4=3d-(HSLZVXj zj1u<*&J+#zFYzELwa;828fuSOe+kFZcJXr04p=<`dp87-1w|i`X*$P^Q%@$0esBe4 z6@J4v*Y7Xg8PB5zAN>hAPsSMn3#_5W=E?f38RXO;@r5(?zZ}-KXMV} zdy~Z%&GXLjO>IMx2-{r^?BnPz*2RNbzj}!{HM-ExMb}syI<0JacK&$KevWfrt|9B2 ztrT@R*xpa2@lwI;M^cq4Nwg;<0ZY4StX>;vME%xS(mU{=I=c{O$@0h_X;{b0%CzYNZTn1#b z1IdO2H28eHC`*XJT+vV*s_F<}O1aw0RCZ%G6z@Z&7mQ}}a& z+Cp9rnyvcyK<87Gct`C~Uznwi<#zg$2qeIdzhAtx7C0@qTH;Lvjm%=SC1=lJ(lw## zCwj6Bz@&lbeAGT0@@p{gp{%|fBWw~BTAluR6@(Mi-c z#IjR6^SqoVc1Jm1i9crx+L!cs7Ngg(=Kp=cmt(1{o})iV{WBhet7+1;=HlhAe=b1KAc3nB#|qu8IR#w~B;4szZ=s{#nx{Y>5HCM{F+3JB zEGrrp_;=Rja$cslKogqtm8;_a=f&{9Es{3r+xnxe;{$Zk*TL~xAGnar$9L3z#=p#J zZe~e*J8~A>+ZtW=2DPqUY$}GDKE7(tl*lleD1LFhX>jBA&6fQ+B}~hk*~(5Ja7CJR zaQL=dLeYR--PwHapmYE=PokYKtl*}8YO;_)+PjDExVFTBY;FNT%0JW=ok-J^)m8Yb zrN)l$2C_XrKBaLR7#SJbeMx>)JLV@c?kICh&8Sj0L`T6;e!HRUeA2ykW+?W|n@m}=?q=8*Mfnkcb-CPOCz#CuiqV!YQyq8)}oB6~C+PaOxB z2!7CIYIOtB^k0@u#gr*8n<)=!FUxF{oE=bcAVwFV(=@U9uop|d3fCnM4%_*Pk|bDr z&y_`{EQcR8VRxeV(~2GFW*LY?(H|xPSw$x1-2=+kI+oS*`_Y@$ruccBDz-<{AGusr z(y8yq-PccNtM8?g2jkBNYK>Q9Xy#^XCsj6R@u!-Pyqk4LEG>|(4(T^G{XdH9a`LGq z%b01H{O^f?{}q29dZLB}XiHI26u>d(m)7NKkkMsNEc^6sD}R{H%}w~+vKy= zeoGv|&L$ezCbL(k|8{$b?Zqg+8&U(B^0Yau6MLriI0r-vHJz(Rj0y8>FI}Aq4szN7 znT8}dPx)Ce!}_U?S2eI?IQ&O?ynv07<%FnQSw9D7`LW1powf%}X^^GMQVLI8z==yH zR(6hvGJ9k#)h}3jK6~UNGC!KkH1h#w4cJiSHOleP5F zSwi^559L4A%-+pC&FuVuc47+VeT&+(`yl|y=T-Yyj*93qep8V+plGP7cw&8Oz#xDe zuj%DU{s#edbZ7o&X;)%lenzI9c;#P}`&Sq*Q~(^x^Z)jg?S@lI5Ko56s0~=o6R&>7 zlg04hf%+jCd?)lJT1f_9)(_moRQVLR|2WNeNK2NjsDc>qBIr|9w45uGbo3=U26tD* z_2B>UkUay8mXBb9e$FBjP#!yo0%J?Hj;#8!URIWFv7W5XqdcrY`2YplSNA!ZvD!1N5X_$t zm1(8h=nbh@S-~Jug_D&6%gr*#X362$>Q!l*<$+GRJm>D!a=M4bw-|;uY4_&PaE#>}Ahlt00gtAB#acO*Y%gZxa(omy0%3F&5>o*eUC{}| zo!3@GW#NWyWL~nt(d{k5mkB_h!A)`W!v3W2H@nqybh;2K2gos--0p;5gCHYs_hLQWn(fD!(Q<6y}Uq?gOP_CtS?)sd`8OgW9RgD0OkP%*&>K&_3`+5#SOHY!`S8l8^1vRj$|zHpcg#$%vH zzY2Lp*FQO<0kUZwdScDEuR*Z^)|73O?SOJ?P!p!@Bx8{CU`Ck{cCB+R$1p>*88d>x ztmM{RR4?Fi*g&qPt2JjWU&@DPoVR@r2hgtDwWF&gceLXSDn<^0i`{!90aWna7i>U% zK(Vcp#MjS;o1(1HgOW|@k%|&$Xr6GF7a&S{AE9f>`7WZw?tD}Dx;-nZ_PH5o5 z!`XE_tKy=(n;TBr?Alb%B+uuhUH-zj1%G%Q1FlN;|Pc){v98kawzV1XoG?cn5|e6 z{@!qB{f`|Bh?yKloN45Dl2Y2gv|=n*e_smrBz^{Zn_RpT$-lyHFpV7J9#3UMDW)E7f9!3j2l(njZz# z7oS9#jF?3R0gm{Oo$D}9mq1Y^VKA$3Fg zRBfr_d&ZM&j?O;(8B(KC@@l;j!lIy7Cq=+`}jRO4#Y0mC{Dut$?azX)> z?)QI{Q#HEshC7d4T&X{24M{?O7nKj%2USB!@uy#ZmijdM1D&m8BkaZnr+Tw>_&c5xJuVnOj zCFDzE$Q{)(-D8BxtXWRRsET>cFLQJ3{hl+08BkI?rcUP6YE?3AbZmU%X>q(kih ze-loG4+*rj(7_<@5oVJhMB3k?eMisglLG*v~ft4 zeY$IUYe)v|DOKd`%YLV`z0CEl;eU7IvvFtbjgfO5ehvFlzb`GeH7($P6U)!?Q%0@ zfz-==^e5IvfZC1lI`MxS3W!W;fc(kz?9FmtOR|&^kCbsXhOd42Q@ePHcbKKi;pF3j$Tmn(xJ_Yst$8wazbuP8Szf zRt3pliDyl>NLT-8Dh*P)nx-jSh-kltSXSqi1i-V1TL^wQ$S{XNqj3|j%#?E9Najtk z)l?QkHhI)#V`yxQDLpz<_0ByN;}n}0Vt>S&bDD($XjK)ODwObs|N2*;f)ot#Tr5s_ z~dNs_F{XET@Ghm3)~*+I-**^vjT1nxW;D zF}nH39c!_Z@7w|COBdXctxg-F8f=l)dB<|zq7WZ8XfD+CC4~WHa*B{G=5u*edF$=f ze8}393yT{6#kuc~?`S40Qc9oVB3mC19{UVfqe07dk)P~ei2$Gb`&_ufS86p5F)VpQ z_SGKYqMddNe82$lq>IPrcoXoLD2j`A%fkf@j5OfLxcahdhCs*s&uXzBq%?M?PQC2m z-@h-BTY)$82Q`DH9(=hSbp5pF$MhaWhW=tmvjLbj%B`jH8vec{=b`juuAUzxkKAjx zA#mQ8O2i$Y(j`esotuu?aCFKj`!49n4{D8R+_x^&D^EgZ@T(X-kQ0vW29Vvf;?yqD=Pv~Eff*-iJ0!kb zO&%q7q>>JKVa`Dl(9nV-3CwgwxxQJJ!E(|0@%w}1mmd42G2iXu)K!!!Ii=l`qi&va7bqyZ0-P}YMqeM` z$(wY71#dSuAZ9q-!xs2YUo?x>Po3N6xa>80qz8{E9vi1J)%2X!E9ecf%?(K26$_36jMAq( z38YdQ0U6|1e4ZJ)HmRZ2IR5r$)?6x2;YbkzEVl{>Dz!MP|+ap*lNU5suR2O5D$ z50pSbv6DS1Lt}iIaV6HP2fB9wRf*ALU)AX#_{?nc@DXXJw3H@K7%n<oWR7&gJ(qhjVPexfh4OZr~sLFmuBN2*9!yvl@@DLgtF(e3o zjZt{xiykIYe_zrYpY$YD&jL}dOV)^kO-YT+`6<>p7(E_O(bBnZY4_A5GYe97w;&xE zAL&{W6;WZkmp4*_OOEWB_!j0U}C%>9>hres4lumeHAx(chr+* zk+ZPf`-lU^sn#TEsP6!~Y6A6O^0Is!kPQ!;o)qcCg1?CaXVI_2sGUK6v*9K@LNItq zGNz}m-TU0E0oK&DeJ3{~nvF(`svI5&cPGl9u_0AN{e6lWx^KJ^X8#*<_w8E%(-G?E zV}L>9rW)QTbE8MBh;$2VHx6*;cJK(&t&6!@P7&HqU9=E}TcP>nd<4w2KT+qr-?*dC z+wf{GH-cg98^EM0A%qBrWMHbuq61Dq$BXCH*Tag3P82QkT~`i5BrV?kAaWMh*9GW^ zS}AY@IM>QK4o>#9d7pKa6-QQC`6>GT2{{wdfgKKi1x#dVC4-;3&6w*CGjr1p&f8(S zo(#d$n+MHpvAg368J@4_6CR=+=^BqHbP@c{@@(DF(H{y*EZl_oBdhXPNW^2>AMi)j zl&j5;LRn8(*7BE0eV(MPL2h^Iy`>h`?2b~9krQlM8pBgw7Ca){NVy>_1RG{MNe6{x zvtm1$&eY<|Z;Lrs^Xg{FBKmg{?BU<%FA4$5?I+DXJ17GIrSPre^ z$1h?>AW1x4Ax5ydi|GmB)9_XFvIRB1ikJU?D3nl z7f!$K^&vQzF)uy+%Ohm=)44#=t`7xOuX&Qq!|gXvAI;m``4yg+q)B^i8&(}1^1YRo zY}1P|%oFwP$^b8@3u^K{&M#B_V)&d+6oI()9}v}XKqd8q#B>6}Kh-kjXo zw(>aEl92Mu-&#q4b^6MAGEzS1|w%l#5t6!?)P23=>6%nRE zOzFxcF8>|JAXy)Ht6(~z0n0{1+@NMd&B<)6PVS^jaMbOyN^^OuUf9NARx>muJyblfW!#I z$C*=vcll$Hv;7O z7Gl;iTqciTPDHLKl4x6rlsD#Wz)x}x(4@0-lQ#UKtT)C+GB{@ z;Tfwid8~)Ex?jyqu|*XFRsksfk+pNOxI1WP4#-PU+$$|Z@qX$3i8%}!qk7?`*u<9` z=H#AMD<|3;~>0V^<`Wu~r@IXk$|KHaa1;O&xffk^xqgnw`)g>Ie#LRM84%^NXLkCs5Bjd!XMVfZW%uL3O zBNH7w^{Hth+7LI;gsdvYT}a|F-;*xAq{xt}(7S-uPCU|f(8$h7;odSSm;hSpg7!U~ zJRiCIB1^ZuC z4mC}9vC!!BAod3rEWCb3CMs9qe@z*8UG;cW-(A;R#uMefO*y4{Bag*&S zmRJy{KT%mQF*(Pcv^cr90K(w{7TW$=-H1V*pH`La!iMRexvAL?X=Li`r4jTQ>zJPW zFf{x&*D*w*F{Mzm$;~=mhdgH!GeG4*C}?!e&6_!QmwqmaI||=yR*C9b@jmlP;^ch5 zs@sv?+W--Iy~LqJp8D&%K+1!0tz+?8*mv-mfeZNyB|oy8pP?@#;o!VsXaC%%!`lzs zOYM@?H`8}HZ1AlaYC6GEmbmTEFwPg!<4S+3Mf7PKiM();s_o>{JIzqF)J!fY>K!$n zFddy4pO_vqH5g$w;kV{D{wD~M{R-tX*7x8;|2EH4Q^Om4e}(k_2i;Q5XGeUx-#e0+ zKiCO_KkP$@D5qMhjInwhB{zdJtaG(no6@;Z;gzK&$)d}jX3uln^$k^`7fd=F;yU?R z*2>GxP(^vJbbgXZ5D6wg{6^3cW*?l=FOst=mbuNKEs(k6)3Wn(BmB-kb!nz~Z#h1s z*3fBOPeP>19*)x>7Z~a`7IOUiQnz@*Go4}$tv%4JlE}esl|S}8M>djjeL*1wu3ICs z^rr91km?6W>}YvO4>U_Kww?dZiwa1}S%i*e2Z=m$1|7@q)1bf!p6SJX`kM+iu0pai zi1mFF%FspX-cs^;xC}w#f+7wav<3`TR<$Lq+#pJW^5mfuFR#c4BfXvS4;U zH@^<1cTY5TJ|~=`Md(d)eQK{54@!7dFHdN}NaqNtcJFa&BNJKurt3!Z%TG5MQ{h1A z@iNp_NO0(+x)Tw3246JIjCJk_q$jDaz z@cc%|MK=Kzfsaaq&>LolERk4wMdTE5cp!zb8J%GX581PLTT>-0npA(#EF`N(wMcoY zodOAu%Fel$+?a_RA)?(>{?s_Nxs@)&M0Rn~NIgq9I=3OsuaLV7G~0uaTG|Z6*@}5v zpnzNaJ4D}1_35b=+;Z_*tS5}KUY68XMZ(ptU|;$>D+g%CY0))`iQfj>Mpt~%$ua8E zzL*)*S1Eib`r@eeP#JrWKPb3%z0Y{m5*)#?rDg@v!n^!oUbJ!#|O|J+>wSvr%F%7~&E8rKEL$ z0mUjF=l>XapU)I(x*=a*;VRi|juv<9nZSU3h;XD~v5y?r#KO`yNj8Jbp>_AJqkF`iDg|_F=pE9aKp*}HBPrN? zP`?p*mLf6}S1Zv{0R?HXw=EJE8<}ieDn3lkESGX+8d+!zdYx%%439+OhQrr1$XOKT zHU%%CMjuK=`W!CN>y~l?w{YupWZlz`1<;)N%vhwz?Pg)+C}YR_A{JmHG=yE59Ha*0xDXeM{ z*F4mb`GwnGjZbdpp#YdSv8XBZMsb|utay>~vQX01EW*ti$~@x&WvU&KB=k44atkb*#X6d|N3f7-yGj{ z@vW~)_Lb9pj!<l2pspXi!HcFMX{`8A$H%F5RM9guq6d{;>R1RevNS)jmXKAca=wyB$Mu{j~#DQO@x z1wd!!+Zh{VhHa}*J~6q;F$(ne-}sAx@7QMbtX}0RbFY~+99y?5i}whD8frYq8_(we zwucu8Ad)1D1>Ej`{cFOy3eK0J8`VHlT|HUyBvmdqN@DI`oy)QHcpk*?$J|gmO8(Sy zncSk&ZY}Q#t!E)sSN9mA@dg4mHz0X`*#-kSk+z!y)WO!nAcE}%lseu7GdLcxCwl1Z zl$~8nT!g632Af^BsN3D8AL?=3=g{A)@SWYdi_}#6(*;FaaqI!i+tZG-+C_f8V1;t6BGlRWoz&uh-#ly-gkY0g}9L7C4C%3j1VI-i)V z&zfB%)Es1}afcS>?v&XXF8kDxX6IrBi~1)lZ*50RZf?i;*!MW~QtaJ)Z5r3mp0H+A@Gl^?lMCFD%ChcS zXuse`p-PJ5Y(ulsv!q22hecvhpT5;f%3Zn>OejNX3oACwGPn38j!!C<#XcWNBygCU zSQH`o`EFf{S2sL%H1v^a2EFGI@GV!c$>#28k{M*4!zCxO)Zc#FJIE*x zZjAo5z*JJv|GeI^2Bqz5E|R*ITrfRpD9{Q)EWJ1i@cc;)&X$6W3>WhNbEvFZW$K`|Z91^%U?LTk&D~F=3!? zni8rYIg|cXOk$?cG4Isg#=(>iX^+h4PMFvp2j36;KpT(xsM<=0SW-I=dv~Hr5mY0~ z`+=tC%oL*wyLotmG`*%F_P_E6yd?Iq19MI8dkhxQm1I5HW2w8fbTmw2BUS?WNrk2m9ES%@&skBP5w-&Y41o50NpcTW|Gnzn{g_@<+ zg5o|~-^QYDu;!<{Qj7WU=RXa9z7t3i`3=$uO3!gUgg|K=ui97tlyf;eRMD^w9z3~C zKMt{!>F(2=m1s(W7xl3uYHic+Zv)@5n|eb|L_~kyO1Jq|y4Hij;gf$Zk!+KbL~f0F zxO(o)H;3@FWR;k4= zR4I*!c8cz}Nd(&LoC&>{nDnA>Qdz)08Qr} zJKd27z)+_y#G<@24}tG1PGMCcdj6)^%$|Dhw^PGN%`7cgcTh8v6vW_O#&Mc{j`yUM@-e{ZY6vmzU$Omxc}&(*8>Fgy9% z(k>=%qW1dnwPRkx8R-?|D|PXOk*^jRVxUu_<}n$!!wTk3lT)||M(OkLW9D*DtreuX zog$z_ULU8BX4wtGRfc1ReuaRc4E^7RK${)ovieL7id}YC=JS*g@Qb<_olnDhW~pZ? zT~Tr0LjB7vcI-mdblFSsWGHdN9$4E;d{*TRv4=+t7;m6QrYj;OYfo4e6K?)JvW5Ze zhDUhqwiV;*+0|YRTUuo#l{ofkAJV#-4O=~BK1vwgU;9bGphy^sARD=Vqa;Y8?ro;* zSF*#LRmxmr`a(a*CU|mpI1c@IGNX%gB8WEq5q2~&g&r;G{hGkz3sz(HGZx_;h~avx zb9}HVUWBt^9B;K6%a9WMHfLo}1uL@NZMdJ4+cVzvVeX09+jlc1)$h8I>B;<&EVsen zTZGtMobUc3&1(2|v%SDm@ui zu~Kv2np33H*Z8m|LY!6B9{*DRC|ELp?nHpCZgt3hi~akkp{9X2h=-RM86f1vtNtf! zQ5+)}_v@E#*0n^Fjz8M+<5>(4)n6*jD|jzj@Y}oIs8!j& zF!J?n{V|^F?Mp1dhT%OPt!segMaOgaywNcqyGjg}AoM0@+wVl{Ytv!ob;3qqBZv8A zLmSS*#gK<6_8r#6l5lfAGvWE?30k$&y@*vTr5yA?Gb$w)#Bg+v?d`AHJDzl`R6M87 zg)}UjQC9R?X6fVLqWSm@3=8e1<1GXA#BgT1&L=-4PuLMEjO$`Llv+eejHiEy1g|EW zkC$mw5lGRlAiQ8Lqn>n4%lmG5Q5qbx9U6=p=A3 zx|BxdUzF5o2(hQ^vwNNZJ%{rIm6kM4?=V ztEG$aO}d|Y45!-A_z^bp56TwR0Wox0b*-6_gTXqePcw6P{2Wg-zTj;*z?u3`GIrpQ zYZeK$7&jQ4#W**`_J5dJM)y{4S zUd8LtE$*pCWap@#L_d9YJ1PIbPM#~s(s@^pFm>VG=wr^kp!wPNc~a_Xj|1WD3QATF z6)qZqd*$CFacZ8VFe8iOA%CK{lrFGv4Ci`(X>JpH!=OTR%q``|)YdiU*~1Ed=STO0 zwdlEsCdVRyrV9}Ywbkel7C7iZ>Hw> zgqOQrQDF6CwgkJH7fC<;xof#pDu50xOe-X^9$B;#?fbf9^HIas-r?=`QwXpMo{y?K zXxNRIZXeNEV!#K-#eFLn+Kq8$g*5G<4;e7+Wg+y*5{Ix~ zy0Qn<_}KWS0cGCb%M}PRR>lGV*7c`AcnAWkvn_dO6K<*ZazwHCs>Q zOrgVkl^^@xz?hpO*f2g~F$M1qu(F}3N<_=I+yr-n)93Zfh7>xoL);w=M->(bQa)(7mGWTcvkXXv3XuKt8=VPEPO+K922KKC#}Z9A4WzU{ANgWMuGWl=Z8H z7xWm3gPzL5X9M5=9KZ4R)FbEvu~M!2RtkTc`aT!t4JB+JxQk?U{Pc+4Bk$>tE-IAQ ze;FCyi-s)@dV1_yrT6{%EicQfY|bw9D06eU^7E`P3k|CK35di19OFjE6<0)voa>C< zQ{X*|)=@xAc>x`n3F8oTLw^fHCj@QCi|hFH65|VL_3{cs9rC;ud<{r|WyE&{b*A<| ze)JG8U-C*658;2&ssC+m{I7riO#DYy2NL@Sz(=TbLiKWB-ZBGzP{*K&YI9N!GWEC9 z5MhhUDcjV4kIKPw6uS~x7+k$4^w5b!%UZPUB%HirnYewPCokL1`Y6k!(c z+D@TF|8pt6Vw|dHr81vktv{K+uaDm!-mdo-|L4-q8GUBfb`x!IQT0h9*4?@$cMf_U zqoS#7rdwzLUF99=VxfoltUZ@~>ztE+d(q&3$??Y@_dWIgR?T4i*8YFU^S=>lA)?|Q zTPxdG3V6XEK1~H92D``+^(=@%L{uhXHVDIUaw}$M$akY@8*{&)`^WUl<;V3Z?*Hnx}RCzUInR`sXndU}cZp%uy6E%4f#Id^)kF86&5Z~yPl z-v8^P_`m-fd~3$_66dY=o_|o@zhL|4lJ(*_Y5#Q*oZ|IcHPMo_vaC)0h-5{w{$Am{ zmf>=uKh?e0qG;R&IR98CfGpJNd0^^VxIkp#<@lVJhFH+i4!IKeVUvto-4!-3cJ!seN4L7w-pu8|v2Et6xj(*?qANo#Sqk#xg!pfOOWt=qn%<9J?SdLy;`1+3++^ut_-gBl0ga@XcL$BR$MO2c1*R2vtpbgE!wG<0`&fMVRN=>y zh3z_}TWg*Z*)W)V--|8z@Oizw$qdW49Wm6EV+I6Q-I1@;VbWV=%kV%8Jc%g^B%d3f z_tt1?1r_n{k}sLm2CkIbIDED*g9 zq6DCM0VtMp?NHa2fJo%Wnv9RO#WJU9oL>0kpmATg+UDXQnP=MO-g(sewIKSBQP(H& zENzl+{KJ>}?9M@=^$fGRA6dk)-P0yhnwm-2md0{IYEQj`GBYq(tLTI9B zfYq3Op7pEMP0gzqV%j49ZrejIvB(DQ;}(>n|0>DGUh&-CeK#g6hKS+JEAU*G`smD7M zURaWS?(AIw%b(kKhbd`WNYZH@L4L)Qc;mNwj}xHZ%l>m|eRD>LC{ITE8#i7MQU^8B zL5$w3Qj_cq-8*UqvBP?5-1o!_JvPmm4>!BtU><2ya*A43&*?>n*xF~19T5^g)w>|VN@-IKzk;e(FxlKLsp+3 zwo5hItaZ96aAhf?*Q4v}E;kMhJL#B}r8h{<=~xv@MCD;0y%?5Tn|}<3OgsADayn$P z&2>>Tj<8`3(S^@ajq$R9m1JKMt8XbfTYG{MwMkn(o?0=JA@SP7J-K6nGc@ID`cC8d zLuKx31`-kFoyHSgX|E>gb(3T~{%{ugT|jDYcLL(_m0y6whT83@fww%*1Wms-oy|ji?bids?~46B(SH97T=V!edfDRwA3l3gJB2IaBC4l< zck&LeQQUFxduX<~O$ZRSbswbHQ;yt1%FqC!6Br>96NVqeoWa2FHATw&98rYY$Zu>xiCDLJ$BVX9SBq= zU84d&@xczyr(h2Pmh1=@?KsBk=k0UjGRaqF7obsdvfL>x=Z^I?!?V>+R0DhOCrvZx zMJc$(!*CVMhtj;XYSA63T+=V*`&TJIICe=5&nGN$$RiDWske?3Imma~+8zDEmEN7m zjr*QFVav}ShPKHuRL%ACF}+y=Bqg9&gN`r?F%XSk^SPrf0l40I^T>feHJW2qEBz>m zWRBTJBBD)~hun0S zx>L748S=RlLhei$E2Ce3ip#~`MLiVGeJE)B+^_%wd3TB7(miqzP^#I!q=|S86Tqsj zMG^O}fa5!d+m-T4eOl>jPyU`imPtBKx5f{xoZMRa}sV*v$$Yf(%DOFpQ*4dwRQm-^?;Z%HjXX|&mnYuNCbJl9V5u@L`pl~%`N&|=w z)UDyhe>N+`g6q6rR76d zzn;(%c$b=e6B`{g-MQ>&>B2wBj)8a2?lx_?KbxSHNoLE5H`Jj+-Pa@?ljmed6L9QY(Xm*X$(bg_ zl9k9D)DjERGrrSYx4+i%A=hSKNNn*183z53CsR%KUYSw^_sNf8`odv8yP^&z z>!+EuZ!aPYk@;{2m`ClQztF`-?+VW)bw>?FZRG`pzOh#8G$D~+@1HWq8`qr!kGDyU z>b_t(-+o_-Z6!trO2NI`d3S>GC&}#>m&_ZV2L%DS#I4|VEqV1lBeZ)3}LtD-M|s()8e`*A(qTZ`}EmmH*U z8tEA@(YNp1tiEyq@ZUKS;)}x3_?dJtKMI&;PkpcFtS3b%M4Gn6^DzZlH(5O&hjk(2ssAQ)sw9}qhRNl_Q^$NQ$ z3V6UG0^}A`=$im6>g|TjO`qF8$G{MS@Q!iF_mazKjl#rtMC51_e@xp zFiC@{j0XY#x`R@URnqkKaJT*y6Vx?^D=I-RT(|KE`ChX4&RtGFvM~K!t-_6}m|NxS zeFIigg@uJTVA_KYY;2mAhTQxnCN(MC=J63Xqh!3&`mxX7_cO?)*O=X2b+rEi9f&GV zlorp~k-`Ue)t(*Nt~RSx)tZ6Iw_8g!V+Xv%j^}XU36*jqUAg(a6HG~q3Us(GE&4w* zy8cKGEQqcv{@wj4YP$!3I-&4uN@JZ${Yi6^nvL>y73OJ+8;^_XiIORr@>cSq`eH|a9$ zRu)A(uv8BZ^Yr4zHr5;&m#ZkgoE}v%VL)&sYpaJp3oTk0gaan`4~Ymt>*#^kwYYZs zlZ}2|pfK=r#>*&+$Qn zWBE|<-E3S%e(3i8;?W2-s7}Ek&1UpE8JM@lcJVZ_c})as$X zGoLisEb`9wQR$~oZUC5ez4~pdcA1MoLS-=$P+bRgBTA_(hQ#T{wnu=JyRe)oU|^*d zPv)Er@?W_|UTioE1TKC&CNS~Ad4(?|@LdRaqRb#YuPd{x-r}Jvw7+Fy()hSv!wdsF z2_4mxVS(px#bG#pC!}CIGjG90g5ePeJ>qdHvh6R#z)?%Y_9KKT9OZd^r~ zc-alUy90ZwtNoNJrE1ItZ0A&-0F~#lC%gKCwtJ3ZY?d(U`mT`F!3IH+97ALRzow;f zf=p7qjBISb5q3~S_AjmRbzaI)$RmW5>KgqtZj|gr(*IRad1{FR(g+FditPa&eYO!5 zLCvt^ksmQ`{mqhh9pW(@<`ikMk1;;GEeZ0G=rA5W2?#yEY_R%WU~Vc=tn{Mz|E9*D zI#4#LLhh}#3o6nRoSOL{L10AB4omv!advm1Bn|69$N16+X6^6R8E*txQc{%i>YDNZ1aF8H2JC9dI zuDgxROhSZg*}CdUV~DhEw#S5_0|9zEAgrw`=Mz~=bL0)#jX)NN0k>d)tk`_c|D%1x za=8fdV+RolWY4d=j$!QRw9W&+lpe}oO`m^~HQnv?rBzE!M=ff==^AZCeSRLQj4Ejp zGwkS)nN>RonruIa3{*60Pv+_Sn*OslDM?jXi2<40dfOrwmBK?fEEg6MoWCz_a;+ru zN3evn%W8JeOG?z7QXaw&eLkzCD#m(Cy2<;hO@x7pihTRg0G>= zoX%qHu2t8ioy+o_j1tNH8^l@NJ=dlpp|oLpH=kMeB>Qp|M@@nF64!yVt;Xz>WEZQ| zxETJ=AW}8(Ee4Glj{~Jk>$fw0f>l^FI9DExI~TW6i+0_7Gy$M3hi_eyW2?4xTLbw`RjhN^d1HBhe1i81wtg^dcvZ${*iiYg8jX~(f#?{YVHb_IwfUO=ePKbGTBg^a zqQ(FE4TInQ3KcIhiXH6W$q(h+Vmu#h^+IGwP#$?LWgjXok{2o8JzFOUZfl@rB+tI8 zi~CnAxucoZ7>J%8vgajm3YGY>7z2xP&8L4&)Fn00_1<&23*ZE2d07Cq5~<%N20v;* zWy%0&&#JiKR3bi~W~A(B?|uL7Z(y`*LBCJH`R+weDXM(@GH~*UDR5aTk<;JT)b4(6 zLCVdq&-mh*&uOD0jnvH;v%A_)Ew1;|$Go3w7FZuuTRQKkCO_FEU56d**<79Cj*7>{ z?FxCTLX)@M(c;Y>rt6l1Z~9wi`VWks3%oZJW+Qrb9fXlv5fU-$jMtR72??v=ql@Rq zXm?BO+v8;Y1c_=TPIse6P8{_$x7X#Y4LyQZ5vr9aSv7;^p45=#U_ z#-<(swZV5&C+Fne@o7DK(W316GUG9&WHltX@4prR|66taKR;B#QsJ!fXJMO;;Ewjm z7of9a3FUTGgBMkepzNaKD+xzD)7vxhZ1*cqIc+X}We1;=YF3Cr%Zxq1wL%4IJ_$J4 zM9Q1T?To>WuXB6%cQNXTeY=5WC-+`mQ&X+-e|Ce|?E6?qAX(9U{W3b{f$odIz|?$* zH_B;X|MZ1~^=nemSujAi6*&hl8NfW|$`*a?J-A0(b?n20_}iOvh|!oVhuc>|_D*$(*D@bwDN@=-7cAAh9IER%I36FF zcjGwwT6I>0g@sD#L0+7eU`-?79hh*FMK{6;RIxvjv^S)}5L9xmY45e;sC{G087Rv* zqusQRG$uX>|6}`f8A|2Y0z%*rr5Wf?ks= z(b6$ILqR94Z03CQ=9kLQY}t=}qO!&WEgP2SGih$FrN8Z53Q(c-DFDQdh8#p`Y!h&z z3RCJ9alCNbGkh~?YUOm>u*x?rdW45*Dr1yhGkM66ub!P^sYcTr^Pqt!X=HDg?5pBo z{P%+|55xlfCS;7g`Ir;^>Z-6R>bh^o!96ruU{jG%)0c7Gml#C`dHa4@rOm|aa1M|T z0U_pN#kx0^96@=5d+D+8S;y+>!9INx(K3oVynpOO9PSBPUuY24wI+_n3ke?_-3$|Z zUl=;cJU+3O4kJt$eN;va`qmNJANyvr0GT3xe=q4e;NYbQjFJP7Of8pxRg{)U!P-Z} znh82LCA^&ooJQL^$Tlhni{asV*f%bO{*Sc7-R*v5^Po(Um_mc zY;fSY2b-I5m2%-fzk37@?Z1VG0sN{_kHytFl@8N3W$xKVZzwLl&9MGqr=fFBhYE{| zm3yexu+Fn2C{cvUWJwvKUOl?E-3PazdYCIT6}cJim7T25uw;-hArE$M{vHW86LgWI zZq7fsDHZTWv{F=oqj)opCBLNHShUNUzLChZX5i2)2W>C%36TAXT6_NlJs zVIDaE?IwS{kD;rZqPNvHj&HW}ISK!{r$TD1S;%SUW%e7JB;8ZXle=sXIk@)(8@Fyn z$m(1dk-8;wT&r=|>YnoR2e6HbwOKTW|u^Gkudf z?Lh3^eej3cfzz@$t*(`Qqi)I|ZCUF*mxuIEW4?7luLZ{NN0ns19GA6Eqt$w?cim`Q z8!8?pH>b*ilK*1RtBR~S5%c5F@ACY-bq(LB_`>8aSMpIxMRo07;Mr{^Wk-%u3nw+^ ziuc?PMiO>{{&S86sv2shcB0Vy0^*Y%U-ml9VhXi_a!>_t{Y zCV~x3uvKVXPFr8Cn4Ei;7PU3wq78Yg;#gu)8bfj9h{Mrk`R|^!{V?pHIAQietN!=H zZ-K8up1BUKljm#f5C8n-T!OZ--2LR{OD;N3`(Z$l=9C(LRtuSwVNN_|>M6_a!=IY- z^l+IkaGEYL_VUEq2w|okVVuNW18bU@=R1;K}2p? zvZUXb?>^0ethwOn(|McGTJku&f5xw!Kd4DH6md6DzD175=R`nb*P|$LlYX>01qG~i z2kuqkf(=v4IJdpu5bM3w_#O0*Msz*^EhUlN?nxT=BIsF{REKqK+n1^ksVh!`t~F-w z+HSy`iu-@OoEo=t+xlJ7bmgtF@za?8DX>Y8-b;ukl#oo{n^^HMOP+(Ic?MN_ zN-T(Pa_nav3ob&Q$SIqKI#1Vb@R5yJ4cWmOP8bbp@#UB(3I|#QJP4NHA`BrT0SBi3 z>CE{^3c1ry8IbmgvcUc&k7^(60$Jn`n^h09n0F-Qk>%A5XPG?dLICBWC-Ord|LfvN zWZLf!=}Bz6yZgIu(875YxCY@Fp`84J(r!Cf=gpGUaI>7etS|fpUo5`f_!wJUryd*3 zlI9-VKVtXKrKlxe_B@3G4rOZ~H2oB1JRZEuJ08$wIBf9vXDMVkOMYeip=>JrRCP(+ zQsT;#>L@Q+75)wA#*KDTUps8)fiYO1+TSj_Ze^)PAs+Jx%w{CKMa%D*(CI9Z#Dx`I zBb(BMq2Q?w835kkj8|iM8Cfj*CyJTX5%xemyvRpObs}Dia=x~64d3b@x3N>kKq{=4 z1AFXn6FziIsihR>mbvcyDll{T^>(E0XCpBZW5E%2_3zC39$Jacd;ezrTm>SNY1xai zTyk>@TvH13kXpA2#FVD`WqO}<+z2bnB2wThVoli5wVB8Z_>YpJhx%O7~yn#Mlb zI_5i|*eOr)6rNCj&Ri|AmyWw{E{?Up$7`r&U#AG_yg>+ln6na@YV~@$*e($y@3o#SNf8&%DgueGj1 z^)z^RAwse@N=>z*DvJgXZ1SXfV5z~CJz(d#14mDeID$;PhKCy-78gd;94>TWsx9-J z48Tw6Jgn*@E!Et9zEz?*%Mu}_H;9(g z)6lN*$U|eWJ#g!@g(uNU3Qs7WD~qSx|6IbjNI?YA=A|yMV_Jn{F+N1mVw26V4wb3= zaFA>}>4d@U&Qf*ZroEjad1U?xOaJ>Way@?}V_pT1PIbD@SBxcR(aMyR_O&E6(6IRV z5jGqv;w@en@DKt0{i7N@@>I+;^mOIWc%LzWhf74>a}<}Tv=Z#)ezed?NO6}76xH7! zuoFQM0?M+sx|XZU`6ik*P%rpDjBDxvl@Ae=w6?seVV38H1~k>9G=%&g!?()f^q3>> zDcHu{q$|52{Cx(g*|bdcwv6GaZ%_Q^(&5AY)omeX7gXDAcp5Hl*ksNY9!}WeLjUYG z%$aC!HZ--OSH4W?`z`8aXOMl6*j71b*-pM^djh+_&FC#vpC{C}X{^X+4KJk9*Qd@k zR{|67hOMVZdq$6@etWkdk(t@|=cHzuYlOWR8|BKMe_=TBst>4)|6CFUeZ!^c&y7Gw zyOAHa;jDIEJ1Vu9BHX#!A4xGh(HwM-rna-{XLF<)BgsOWt}X1*CW_{=nj-y7-^{Ti zLF55Iu*nF~Mo4bC|8wa%9E$Zch*V{Cn}+@J2hDz)XRbY&?)%E1BPMoDxVF*%(OubB z`f_kCzaJ2HIKPKFTYAw0Rvn=RA*AY#)-=&*wtdjXo%9#kfHD&DY#Pe=>d52}Xc@?p zlN(HKe&@M2x!0PJUWAXc!?asK_juoWevRE zJV_KqveQF(o8orbMFiJ1DpMwn3iW`nxqF+NZ3?RP<0u`^JG%+id{ zOGW)$5<}dBfL884S)dNms%Z$W<%`GC+%cS#k<#MU(Fgn+AF2?tHtFZv0J@IFJ>Zv_b(yUordn^;%4>m~b(T#00pd}72_wK>n4PwfK4?aJr z2(5`s9#J{RU+%K#Ra;2Pr{PN%;}m>QV3>c$%v`Ja*36%G=P%9|KeRsyG5pn3L}e8? z*emsidU&XS@FltP7LOH#zDID*0{13YNH#xacUPp2SEUd`ZSX~%A zvTGvOCLxz7Nq^aV;bFjS?o?sbCFKiD^6vgB;UFCI*1a5Cbr^KL#5-6E{CVC?x$V!j zYL&P8_*u*8YqOtLT!&x?uR;M?9V-J=)>Gq7HhZ0O#o$>=cL*!ohy36t&+VYvRfdu6 zf4fBaqpuVy4vH8w2>V#(orXJ{OkG;_nL!Chd$Ra)JSpmV&7)-gywT#f)4yxlV*8K! zqDfi8TiQ%IX3G^>HQtt7cYnLgeOWTpGgZvM@^u>h2yHUcyf4ZwXj^^gyM%ZSKR;cW zLEH^_wzK6iUgJMSePf3IPl1H~0>_Vq*-H~jRK^ZH+2EYDe( zYYYq|x+7K*20Da|RaN*A4KACrwea`i#_qkWlz^oYr<`uj&A?H(se7Y5NI0@2>xE0{ z@a@u2A?~z0>L#{NL0w`P3=xNU`wLcaIbCsRZ++PIgvyGv7!ZX+%-x6m-pzZ+B$0(4 z6#i}Pa$9$jiO-p#2>Q~@%O9M&PlsGw$R08+KcN7p()|$W-STDv50!b7(ty#EH{Ivb z^gGs6`)TZPu|kxEF{qt}%nqQP++H8bGAoG68h#K+R@SpWI%vaj7ctl?idHe`={@u^3PjR6@t$`%bi<~W4U~X_fnXQ~}PLFI>(X*;5J(x(-l{NW| zlT8ys95hq7P7 zFwb@cNk}gT|1!&uZNH5>n%*kR(vgiJ{T-5u-(7C=zYfqa>fKke__R(J98Fr8v}i&Z?++FVK5hwxOx4 zg8aRwQCi8@Q_jC~vo?vNMSIQnWsjJ;U$cWHuTE6l6|h6;Z(Z=%n2zjosIl0e6@{k? z1`lIp_$x+qJP%w^gnohT|14>vFccwGvNpz3Ve75b@#Kv=Ez%lE@4+4%Lf35@>?&iDS_*lH$C8q<<0mMzc*QE^O|EftH+=6W$>=> zjM@KNn)xp#?SD+7|4jWqLMqVL#v0R7>Wdg=DwK=dC@SyT$^IDZ$BQGZJu1Pz#xShz zUK}S5LC_)tKaHkMev0!=^zmvvDhiLX&PU#nr6nB$0i_QgD3(J<0>omKqGt4u&ONWB z3w*0m#BK9fT-VJ;*QUywpL&n8ZH!DOMtXr7crgu5*{m)dVRg>&wdkYY2NPm?8sq;G zF}B*t#Rz#kkT+HDB?FN_rAEl%sUK5oHf9{@`b|ZM!UJC@TA^O~5PM(f)TowHlIC55VaDm>R5u9M%@u@0QWMe@j>c;P7=!fnEDoZQn z2VhhabPWka2)naRm+rhENp8+hq%y{~-+7x>1RQB@F{4^)>hpC${g}JUs4ZLF)ShXh z)0TrS-e;@I$+r*OsH~a?QEjAa0=DqBEtSKAL0>27N%j`c^Dq@Q=#eq|;Zt32mh)!V zvgAx~M|g2rMSNjgE_K`FrIAJ)1hQruX-*w?AF11=7VQPMYc?x})dv1^sqUm#jU#;6 z(DPpNqEb-663Ux1^1Z`j4nZGFSZ*%URS!wv4YWNj8d4|Pa(+Abzi1N|BudOI;N)5h zpfrZ)?CJHlHshAA_cHY3n>*Fyws&5U^xUC4nOGpjiq1g|!yYLj=@xW<{A3PKcX69t zB7j?Tif@>!N2mT5E3>l<70Igj9YSzQ%JzKagIXAGAe@JQ`g`|-E4iKd_njjPN#5jbGUDwB&fU z_>7x2T#lw$g=TM%=DouL;9V|@)IjLv0%X2)2bkG+92kG^V7aRG=st@Y^$6dzjaT61 zld6dwcv?+!yMvVS{|5nbhNBAYmtKh9XuR)@C?LzVwq<9N+5u4;z0=0S)F5fj#Tvwh z9A1(-f;^c=^GK-c#rHT!9>=i@|CV}kHu=se+}Kg;VG@VA7TpcdaRC{Cj^0$JLb65W z#leW9Ss+Kf=+Sa6brZ2o3%909Ae-ECOYzDoeVG3|` zsUO;w0(aXC3Q(DkDc>GLVh-5WvVaT#HZ!yFu~5WjWMDLCWgdFY$^9W5bKgLNbS!2C zV&iJ>{MOMQwSBQ7_p0Ez1k(5pWbj4R=`bM42rje+v=D<;DH9=#d)CGHRJvn*X}>*& zu+BZP%Cy;CWjFq70#FKc-#vy9sO6Hh9AA^~iA`JfIasL|R+qygZX;4vJKM9AY{h;O zL{_sQB<%{;gtv}z6}b7j#EX<#0(3Hdr9=RK0ZBdg=zmWfrK|3gW^eKm&op#Yxoe?^rIekI*MqK+=X$#UqZy*1TGwPhG;-V~1JKdw))xZbsJ;#O@zzy~T3?;BEXs z?W(%?HLl9O^75aSQ3_mcC)__{3uke#c+75oNpWF0esWDx#=Ez=iiJZv^2tKvL+KY= z;YXWWLR*`^O7pYsqAZGSiwjGNk{%Tm7p2j}j?(;$uR!KY)x_VpALbR%NHZnB)piEJ1g5*woaGbtn?#;G@jTxyJym6EVJz!C% zfAUUuOno)pbs#hSZTnS+-HPGUK?<^o(WBpzQVg_M>$V2ZAcim)hf#>FG!SQe2|$e# z+F&s~Em^$0IuNzYZ-b6CmR`YL7TV7IxuMB*c0e)d>cPyJ>#{#(usu6j-X5@ubAKdk z@eipIjSI5@?JvcDXop_HV5g+|qHz`{;RWg;3w+*oucy}})hIJ`b$WdXj~xzAVXgNG zJ~td&cV3Luw~=EbMx55-%ocC}*-Qa3DqRmVb=;6+J=)PWh^lKPCC{{eZX`@Ve+xgZ zk99xW{oE=yB5JCR@ox8Sft9~8JgJ?Sns|boZF>UD&WLn*VjDgVj-W#EtU>bCXecNs za5r6iG0J5b<`3oWsuz?zZq%kPX>wjK*dFQXT`_6D&9%W|s-m6zIk%Gv zTrpJ))`|}CEA1q&<^2JV(kkj-d#zy@H}GvDf6%R*+|4w34PTC0%*~-yjP8@*xCWBk zY|maa<-o2wW4$n~s4xo^-q1s%@Pd)8H|F$ZJMjzd)FZD(>B?%>z-I7%=Jsw$rn~qr zpp6u#9r^cDOT*ZN0Fs2I`05<9f(`DLxX6RXsU`(8jXuccoc1@e0q`wWnynA8&__`% zZ5ZYE{a~y?Duy*Do(KCK=3ja?ljj zCwzzTsGE`;FJ{kUP#_Wa-88+Hg8Czm@ zSq^L5UVd--_#zW*&?JXf-}}1akr}RZUA~Eb zznoMrzxb=LcM4)SvG*nK!`x$&Cc_EMlTPFdp^mu8U4aHk)q`AMf(pj9z#KVOuVe9R zL4?kb?~-;7zz9|uLWvKvHTe+$%ZI-8IJ5*C#+Kevw*qT2s9ieRHG4Bvl^=UYj{*#F zv4|ePSu=Et9KHucr?}II3G4FM?6Bbdsdm;gOt8Pd!^iTy%GgZbdWN8N{hvEC&$Sxz zyLU5jU_I7*v=))tiYTs3DoJ!X@b9Y1?_Ba&@iMA2ekf>#v1=q{Gb?I~@k={b+tuAY z+}s6ZLg`Ay+7LW!2aa)t)yw&dG|5dVrU+y`pW}EX21LPbm!rZX&DO#*T~=M=9u?z9 znQGir6EKJ5(=_5>xl&C1xnoww6);h}IY&ueJ{#ZcU(mGQ=x?&V6@uTch%H`_-Bpk5 z@_gkNlvy>S{XmcP&NuAR_=`HjE?b@_2%!5!?T|#Kg>Nlp^GF6g@K(2oj1c9(XP2R-BjBmdz_%Z$-mANP4!fx&+D$kk|d^ z(xb}Iqw~+3=ylfGJQ1Zg&LX96lb+10TzA|nD{?X3b6vpd(CPeNjJtBbJ=UMl4U)S$q_dS}mNp*(UAL!jE-*EI-!V@ShzF2C$R7)5er@54PHKe7*34Re_ zQcAXeF+8g3@Qe3Xfem}E$0vJ)cs*a&puVU~%1j4Qs>p zc)#>tQwA%7OTB}Wf&(W>k4-+9Z(CC4;dvM{06Gw#$i<{2YAXkP0p#!Oo>p`@`Av1q z$cI*Yc+h>A3e59~jjWYG>t~|rOylMQBS6U!BAi_Ad^!m;%ceq3U*mb>k)HIV0r*pe zk%K^w7&O*5NEsizK>%FS$FS~g>e5_r{O7qzMeT$_6N!l?cG1zkCB)+Jy2KOb!wd@} zC5%{jNja5S8nmFw35k-VU_@bJJ`{Ew;D1!&a^H!Z4|EbEkiU`biczz4J?CSy@>%kY zGPyd{^kI0G`JxxW?E)yN+mZ8{2}K$)Dler0#0&xW#XaZh1TD;sllZ{WVh1r)i%}`< z>mHxm#K8H4@+e+cnNNyl{Q`;V%GWPNnB_d1_($-Ds)Km-$Zaa)$O0R4^WO{EoSSoG zzLZaDJ))mBl_D0NHX#Wa?2ao7dIk>U2m!o2BsI zdsFb;Zq1E7SI#G@TIi8OD%)l}m8p@^2!!ZNm7WCQLL1msUswclooeQ<^cXw7?j@eF zZXPeFLf+Y5zgutcR>RK4g4O>rWXBpXg;AMV&{bQ$tKiG7aRu;M7`mBY&7kDKC zQ^mpS;}HYvG7Y%dcZuMimkgH64=-%W>9-72B2g&Jq8=QOTf-J1?GJoFR!4hg9&ra% zS*VAIKiR zY5GC&3p_2TCF>wJL8XE{9c*5eZ3mQYwnb)D{d?yey8I+}7=f=xo0W2N3Tq4AD7Q(^ zSSo(E{_a`AMRMeC@tVc`Ct5EJe42)V>SeQ#!_0B>PzwTEQ+^P(jjQC~T(!_bbsBS6@g+%!drhhEp!Dl4pbyhT2{U;QVGTQJ zo|NR-w(&;U={JTBhvVz$9uJRf)g3$#;O6gMKa^ON-4>s+FOI6=DXl1zcr_qV`>LaZ zb5OREYc=+8n)K1g{x=a$Nct8S!4jUwtDK7emVLE%lLGU&qo$rzHCLb0I4j)*t>JXj zpVpsJ3v+9#PRyYYzzTx9XnzX8^8cv5R{P{UzgfwX@-qTD1;5un&=wHCEIJ-7uctH@ z1Xg=r`ok~JzT)54D^oRz0q24A540Y}mj6ze(=)y`o_7Jz{yYw*s1`~V>&iaTOe;GY zEidz{_N+E4cJ@fmT*Xh0aC z?^BNn^?&c91kztHT}TJ**Kn~w&?7l{D-7}SC zTG|PcC4lLIQaG=@DaJCsERxpN7x-Rm)*nuNoyS2{4N9iMA}K%5z{}?}2ThxsS_ker z>%@*Z#V~Z?upgf+d?z69DDCqf9aI=(M-DO6mRR9t1^qqrBi+qN$Jtdt2tu98V$?jc z3eCM?@==PQc3ETi$CED)w^Egbt6rR&=4505_CCIqJpVTG8NBsB+7bx^|IyRH??4Hr z+H+lPf4i-K=kl0z7tQKgmfPZgh56|xO5d#X=;R1{hVokZHVa|yZe#Z$4?y6zZ%eCZ zng#qUFNtFKII)K%c&zNHP!@<=Q$S8h*URQ~#L!P>FhVxIG_{a90a>VlurHn`1J+r< zNghXA!dId$ZaU~wF{G=X_*`UU>MJ~F9y1uW#MM(BXZiO6rGP+WR~;sPQgUURx28Xd zFx{a{^(}DFX2DtF=h7)2CN`dgi5QuY2xjnwuUJOnT&9NLtYZ;qrT^&6!_Q+l7m$8F z(ZR)aS7yWdf()v#ZM%Vpyb2}f#j=tf?b^oEo+f!DW4q^!Vm|t+wgh9-_N}2(e6vE@ z!jiLK`ASt;X=xe{6N}nJgGr+5hgn+*Widb@c)Y_V1!V}Qe`^>eDbn*h_u6S(d-0<` zu7WEm_v2C%3yRm@Ih4QBzou0wH2z(=m*Hme3zqixp}~}U@@N!N9@Qf6*IauqCg`m$LKpufj3RGbkoSbbK4PL+PU4|1IZN z_4XV&Q#t=mc=)E%8UgJmsd+*+%M2cEk_Fna{HB*Z4gKlRv4s`$PY`Y?>LsZS;W>Pv zRWcCM#?PcIoIHIg*$G(e{fNh;h3(?T>t);*e^OMbUKn~HlSYq{2bkTX?l-JTtIBq7 z731mk?O{OP>$|WpX7^;i}P-tBl_D^^<7IaElle*eCTQ>|tVYo*(yauY95m z9HuLcV#$86H^EC$3z~x{Ha)-H&LfxX3Qys%?~!Sbxfe-5c}o=`_aCXNVDvThwTs3_m;D!fd8C?Ot`r_T#&PO*q+Jc&*|xq&OGi3hr#hL zo|zU=#7rQk|58A)Nhw!gI5K`fxBQZj>acz6a=cx2hW82q!zQom`381k?Cq{t*s$ei zh5!0}@sx9UWNhW_TWN#lw&n&{8|RBsfs~%H^C5Tsdq?_R#^#KQ3Qfejp zd6+;V5&k2`%iyWUdd6WDlIu%p-*{KbA7Qr?sd|P<@gB=h#Oj)FB!xhAjoktbjdb3k zObOo}9=Aln-im^_2KQg{ZDHt>svooInc38RJUNM`PNw8WY`$6R)arcmlW#M^+Cl~r zsyyY0%tx=$h)tXF_syp#^Hie+mP7b001?|c5^Ij63&)bj5do5pHsnk6jdM|2h9JAu zN5d=IE-ss%M-vId<4!_e^h5eK2iiidwS7`mhp$3_HP*{dSJFQjUPj%$aN%a6`spE- zYWZXvNNGh=8Lr>~ks;97L9JPdjq(T^sBsIqbv|kMJoSghBf+hc)9#$MrpMCzzl;ZT zGy3#BZvCn#NKJ$UxP9bjv^dn)N-%JP1KhojOMtgOo9pm)KGoi&5;GS$XMjsIxsQtzUZ((uhwJYLnn-C2R zDd#^ME*pALlDtyl-(yC9B#)+=yuV!GS>$@oXsCAKw!f8BipHzRhg=A`2r#v!k#j?L zU5&zzC0Pb-Vgs8S#kzAo0F>P(yLpnZU*HiFuE*DkSb!&Ix_C(kVqwd$xA|S}{pCXT z(uQz6M6M^jF4HkeT>)9j2h33)fBwCoz_r?iHyQpzHN*9V?ONmtN!}8Jz-Tm2!kL$= z0W2ge_mRY8-&}Cu{8J6O7GhN9rpX5K@#Ed>b7>3Ln}*|c+QM(6(Ko3Ko|IEiYVLUt z)MIUcAnSGFVZK7<_IgvxR$Ujs(f)L=wcz&yEg9?Eqn4mt)~&y{+Ll|;Y)e-+Dm4s#I)v!iG^9>$QjUW*4D}3E z)aS^`(wRS(Qd3zS{1J_Ii{-erC{`B;zsJ2nz9Nib{~KTqf}9+T`+p#QlcEctRfE`~+@GMD%HfA#IN1#7~}-wW}}c_Al}3zpDn z%RJ@JohR1T+wDJp{`TWz4tPVruDiaGb(f`Sg>@O@p>5+)1@r$J@XF|f+B)V}e_(QE zXNr62O8-I;ST9|-rfT9U+X@!$P(0^xHhMAvNckMBr6x8ftX@WEiQLMm$uQ#&t2A_Q zkP-YK=vrQz0EX6g|0(Yo?XGxa)gFg7!z6L-N5NBOaFgg-%)NFiA7Ev$9bk#(b?ogr zjlFufl{*x$9pcCiTiA+8vBDP3KXLh8i0vy7`61}64{3T>axW;Dr@JKiE3|rzmqNxY z$}8gKxK}9V)r|oI4^*3SGHTnE*H7a;eWvzj%xT0Tb5WiY%iTvppR7dX+;}vfHO&Z` zrQNw|&~69qOfvJ-u66p!MKr-25OxuC#o;8O8noJy4p1au|E6=7rvKr|so}C?uhVL` zZEZUB=j@S`>2(*T7_hO!TJKJbbD=*^_(e;0E^of^U~OER;sTpabg)mgvm58sEI>^3 zh4L+L?O;Q3Gm!!)2KB@)4cBkr5!R?of9_m5E$Qz5E#Eh<^8Y`x{eKDS|JR4@CQ3T- z1Ho_2=x1JK2-ru=__S&yAShdkK{29^;NKcIS2nQB+c~w?ptO>}%>TashW~0DZTsEY z)CM}UuVt~{ZA#%o)kL)+$LVcaF@Ws!h%4zSCY~d#;c$W+e{%}gh>;x@6-i1hOoX&p zENBOPnlY%)Dx_Bwje1vBR(q5(-dfGGms6_pDmc8UdR+vFzsXV!Uay^$9zLR4H;<&8 zyVN~4bz3<Caq)X^Qm;pm;#60=ff?Fnlhp1C%sW~80TY7i7Cn7+Fz2&1|n%^om3 znU}Fe^2GFykE-0>CAVtbTEhk+In>gyY+=`%2&Swm)jccwrW))GN-dw(0-U#;w;pl6 zTK!ZM_0m)?D%I#h;_b$f|6VFS7b%^9TSZYz~ z#z1WcucBxNykCsUP`-ai%2)x?U78+IU;=!#GURCxhh-ZoS=yW)CE>Hf&iMAI$7bYY zz}onMZ;s5#i_9$E!d?OJ14a=QeXOi!koFvrI=su34d;;StgthDpFFT~=EXM?kv z(X2^Tt_00wdGZ^mRsUQTr0G{XpW>))9HMXaQH`C{gq}3JblWyWc16tWXxz; zoPALdcA_ZKvrhK~=wS%!8zre8N+M`crp;TM3+k?ON@8qO0#sayKMg0kic>JO%Fp+ttr<@ zImQGwH(xHZ$-QRSZTDEh7cBWcl%2yMC$_bmsG18CRC%)n;0NfB~2AR!msGG;}z4|4$*8Yw*u`(9c4?zJZ$VbUY|q{ zV6*61ngpreZ8sk|KQ@$juJ?LBT_I(~_|$Ux$j}G^o^Rsp>3I`>%9@&~lRfZetoDW( zdkT|Sh`pHQl8yH9)h#7bdb=&Ke zLuhChQoQNtq?thcIaR45;v;l&YOhqD3bn^k4Nc?o6w&pym?2qG;~TP5JJLA`bh~r6 zi6lK8j5>&CQEz)Y8HnVa_93|s4;ku1X@><(Fj0=M-LYBo+}y!u9lIkz$FqlXLGrpkzNX{qF=uE=j)aC|D0U7=w(9a5uVRG z9%=s{7w3%SyyfCJ7bi?c4Tauo2v{g?S-&H~tE3qWD>=qy(YWYMP7l|mZwjEu1tqD; z1_pzDbW;CV^NdOKu%fYxN{a`uu0 z4g%|Hn$FIsBP8azKyw>SOyJ)Ot%nBK0ogC*<@146r7VV~bWF-`2b23Jnr60`;!^K^ z$|ErXO!TUDVt#A86Qv~TgA#yvk%;@zM;ReqN-i(SWvxuBYs~pKEB{P2OrXgss7n+( zi`D*}bf;0`7{z{hmOV~}>5j_C5QbDA^d1Kv+*>Uj%v)pp#3H!+DXjVIaM`KH#2eko z`zcksMmY-6kzOGe~29GqHTEc&*$?T3$ z9dPmO5{5alU#MXD5?@ zLsPx9MpS5@FU8KJJ-WzudV25=6+9c)F%VQqwagOYxHS+~NdOI%E*dQjX=J#_?jym+^Yq?AA_EHw?dqXMW-ZFbf*2 zX3luuQq(yjJ=29VhyK)pQR1c`CoM!VWXoek>@bpQjA1Fj8*oe>dMt;C`D{P+BJAok zV9!$;$ZWMWv)UL&9W|neuI0A6gz39lE#Q!ZM49m>>+E;h2!D$pb;F;6)|Lq#Ykni} z2wWdw!R4eKybS*-Tr#iG7SUQW!FClDG!3OO^W@t$$IKRsZxxx_{Nlabp*3x= zkn<7u^Bk-+M`2qlMkcjL zctJTnHwULRGhNTRFMjV{YB>5DJIL~J{de}X9H?#$a%)|I{b%|*wJ-=g^*aKV?!zgw4ahKZD@V?l==n$Sds(L zji=6H?v#iP>ljPl(H`zLg5~T?aHHC6Mytm9enx-usgl@E$^1Qp1(Xk4f_#O1gSb1P z8zXM-d*v&sxt5&uZil0uGAt*E!E8MCoeVUuX@9w`)n;uBteQ1~0JHveET+pl7@0*L z6g?Y+X3|O-n6xMj0tuwdi!zE{@5Uvj#;r!Wftm#%X@9+u1o{RV16jglMS8})jYt1p zph0fIvjb5@a$&*8yPLT;g6iPiFdWoqC?3S%v$}ytLmf8>u96IlW14KLVevijtH4Zq zeU@sCEfm3lgn^xat_r0+;=1zfQ?A?NV{Su>S1$=!x9CUF&Tir}dSQ;%ya4p>iCuWf ztGUb^L4@m6CX4fi>&B!xiptPOsi)Fy?`+h9Qk=d6alP%R8- zGYe89gr4QS;tB|a|Cn;!u%nt-bJnM(BT&)an4IWqdy zO0Jvmb>=)u=82R%h@=k}Q=qfDvOnoUxLu5_eX?1fpE8H>a=XD^rQhaI!{aZVkSEkV&-;lv%ElY6Xr%?MUAqi&-G$MYtv}LH#QMLUNQE!!6%;6J7)ngniD*~ zz=r*VS4@+^z+@Ym_fYBO(R3GQ9xmtj;8L(n3%0yL{tdm)o-k!8k2w-L((HT^e#gnX zPjW?Q!)4Uqu4&hY)ADejm+Hc&e}LB$7#wHdf5J%oK@Yb*Ck#h-&uf}OJpHUKCXMVK zr}(OzG_ZwivnzTAm&V`I_|g2ih8$HWZk-krob{%RH`Mb!+XUBshWkEAU{*XRhWa*- zJJ5AC&4(1Q?q2e}YuBP%2*Q#70a;#L(Xo4LqAZCxm|p%eL=fVp!t{^xN@humdd#s( z_ax>HCe@=;?9&Wwi0oIYqOX(5M3uZNcohPxn(U$-c0{@?cZps_`g&BvfZb`&4KMYX z0JUs*of5A}L;acq1Fd0C=d$xS!R<`4*rhxW)V-eQQCJp++85T2%G z%57pxP`@u6^z5!X5VQ`9$4fsgdstdgQvSnH;)w=E;Qex!C8K(2tP8&yo!~?d>lC>4 zOS$+fsl*}+wf`i&tT2Np5+ulz>YT@ZXi0NGbCKInpHEqGGB`fMrxK-C@u;b~}?kriq@!~fn6@8DLEm1YkUD8{4r`&*3$Mg}BZIuwJMwg%DPb1TEShI)uo*f!#I&A2Ii6V-GY}!s#zj9B;HOV>$XJ zSm^Sbk$EiEVDtEB`DBiiMv3MJpTpy#G@RFw&cj9!1fp&Jq570Yc;c&-IQ7YmN$ymc z@Qd2)Q1*(0Col=2CVP`<%`3l5&D5U5c7mF!%C`rT@AYd<9Ldx8O)5w9YQ~0#2}#L^ z)5lsV*pzH5&rx z7gIN>MqGogmE-}xQ|2xlu3?F4Zb@Jn`W=wzUZ*{xram*7_1=A1-8?71Xs&L8><3X+ zKmB5BaRrh?gnHZGeN2_4m+Y< zEbLeL-aXLb6?~o-8mDD1CZP_#qWW@>r~2Lf*1a{4umy9>Z@>Pwgh`X3o#dU^kzZ|P zZRWSSh8<5r0I0@w6aqzNA83CJ)*FeAteu!p=lQlGz~>`cGx1CzscWBjdIT5|K3cR!-rar?_?N~%_ z&^F225*$~qZdcYbVMuvDFFB;5;4LWEs?`lJPripbgE@^=t?9Qe!+ zWXv(0RTS@|Z5)BM3woWsrk~33g|L>Z5nwIc+Z16oW^luZo;ZSO)#iEIrF{cjQGWIP zC=cHAA@|uNiy+2R3jagq(zBzPi#FT~Z7G;+pJv3~C(z?Z4fy@u4+13}e0K4JZd!_#&c}UM=wNWqA#Hk3w#ngv!OA@!`2Q;m zK}l|^LW;2!o$KQ1lwoe}OJI9(Q;48joJ>o`-wT$C4s^Dy`uUW?a+AY18g}0jY zoKg%O$^P!+XudRJvkN&>NXCvVbN;EEnSro4yFAE`3u7EN%}<_rw<%!vA!LnKb+*8t zqbRGt{;oogMHdk`tZpa*@4=D2fqmjo^n(SN9;mi@Voe6(hcTFynYKEi3pL-0y`eWi^3_s|rwE?a{_zw$E z3TEgLLYC?0mLJh{E}1CU3ghw#GUdYsWeQ? zycYCbqNZ2Vv<+$j;iY=LgHqa<>bKh1q0={GS<@VFe40W>o5y-8ZK&%GYyq_tVIJJZ zEdgvzkCU5X2k7kYojpOwh{_du8BRFx7DjL=C%-d7NbmLnpBc`8hNS!1vacEDG|UyQ z=q;n9dCZpG;-dD6jfi`JE>;*rB^xi$E3nBxECP(-hY0f z)g8FAK4rs&6c^h8Lk-L~ZGc)bN77TL)3x8b)v%SlhQs>m(k4!8H@(dPsv-SRYHU27 zoibu<<_a;t>xExc;7_=T(TAN&dqcYgoA~8FN@H06L68TOlDjyE)fC9a&QSYGBd%DA zih0?g0w3P!+uT49QXcow1wDK6@x7*tgYDb@d~W^!`iEzYdMEfhHx+RW02SLL=9Xj@ zNM+vm%%{x$OPBUNvanpR|9j~5%jS4=-XJ%X{tl(4-E;8~QijWPsr@c24vT%dtu%$V zW{{D!JO&E2?I9cdN$v(Gr8;)vUu-3h>KG4iWDF~5shT0~oH$9+3ekiaZ|1FL0Y4Wp z>WiGu5qWp|cy1r!daMp}H!9GuHJ*e_$%miP~8iO9q1igJ1I(zG+Q{dcEEQ|aK&PJZ@} z71_1#Pq!_NG_OC$vI%N@-a4uG1v2XT9cr0TS2Im4zeKl!Nwyj2@@5C2(E%R-Q)&rG3GY$eS&WlBbLpHT_ z`j;IcXF*wNTKB)^WKi&+9A*lC*gNQ66lUmB2_nSy=<>v|a(SR1Z~G}NX}(H$N=;f@ z)+8vmcNv-VR)5JzBQ+1QOVK4DUIyH|i=>ZY(Cc%-40w>j;iA=R5tQ+-q{8NQlieHD z3F({(^Bku8=A@8QW>b(UymmRv=M{M*7<&Hqg0X2DkVXC0m`I{yRK<;qt`uUujK)?T z32yu;c%mFZNmIIWdewD>W>9RM?hsj78|-&zx5j0=$Jw^Ei>~Z9Qqdl>zH`Dr3{LuX zl4Eq`F*z6_L1LI|n#q$*=uy;mizG8ZYcSQZSh9uU+r5Q5`K~Jbo=L7rk1??(6 z%w|3#KZk!{t$DMch8}P58uE^gqP z(!*uRbV2`y6Csvll|D_~$o!sZ7ND;y6LtUCV5_;2!pB8|!v!mxl%cwr!Q5Bv+LT^u zU!CaOqx*XS_v<)!xz`8eV(?m+WhiS(-utF5PnH~J=&@rN0&u#HO@9Ak52-nZ@Xagb z=H7U9zqmM*6=LRWS6Wh18v7tL_u6YU+JBYr-S`~!52EbCg$qolhu;P!-Q$x$`}MIk zbz~$x`mL0r`P@@CKg*r-j$7F12OqJ)xA*tNux+|cZ)@xz5&;no?eyqO}NBJr!ZC<@hHBCJPE1;wnVyI{2|WnbbF%)*2c%H&eopRE&oqOr_T=iCC?sML0c+W%*#vYudBo<-uhODjx-0Z5?2zLrgLv~F^@=^GBgc{PXfv$6RVfw zRkwHYKL-u}PIF#cEBNWB6cEp%YV;;Lw3n_iR_Y&(j?}?rt4x|#2%a!N%33!a|9gQQ zlRAAsB(78PjbYiqkF~Ney(4HG9~gFVdV+X_J5OmT?e)!Fh90-xPx&qAyycy~%`5%5 zEX5P+!Bl_zc`ZXosW7*9{qXT_RT1%+f@yyP!}#yAS50rx@;^V}y#3bxgftX8py*ER z=$pt@phzb++*yR z|Cw$&i&VFPnyTP}Y#eg1|JQP1B?6Hc-KP<##0>u-dO~Bi-caorL?AJL-W!B*U$Qnt zhsgMHF5o$WzipjL)$sU~pjIxeJ@R7=VU49dRKIcy`@_iK)wcv=S{GqPMrO(YD9z*N zUNqMTGwC%&Xd^fQ#k-j^-9V+W4YZ>-)XW}PlDGEV8%ciJ*4*kPk0BnBbeF*H3bwDo zI(NY@Z4)1f(26(dmdL(1qS2u^Zy`7<|Fl z^dhiC=QpLHPa!^pN>8V9(2u-S+3g6L3PAEV^%&jk~a^wbR)B8f0^WDFd>RR=rpIs#cW1KZCl$9v61YjMryfYN;Jjg5n(0<`q-p=BVXnEmH zKHuW#e!|#`>&%1ypWuLVj-H2`7?HgQ_BGeHXr{fsuu$-43#EXMoG+#^Y$w z{xV&B%Rg zSR8ag(W5TVugni77VJAr?i_c5HLU+UAM}}J57x&8)%g-S!I}!tcbm?ezoQDS8y4g~ zT2*~UOPM3Zk6T})N}j;`txgBpC2f35*bOsTWs08;O*gr&GMIZfLZcvk4>V%pG%5>A zOQL&4ytZF(Kjr~uZ!h^$QY&1fy>1)6CQqMG;p`7#p)H1W?n=_o}Q?rbNWRuExO*Cvr)$VCynSRl-`0RWv z5L_ULNX=?geDvaS2J$=_nEkJy(2NQ{3rfi>F&}LY$f+Fpb8zu??v=CncgE1OnU(yW zDL#Iuu5Yd-1?u{0rHDIuJP4|~1M9>Nnv#WC)UalSXW%tyex0W@axM1^w#JzHiz`|j z`?3sun5!dx=Iztfn;=h>+d5W8Me!jjI#%^rKaV=3W}0oj6Lbo5NW6IY-)j$laz3Zo zymo4V4Rm{RmbB7(?$AJ(oIT5cecu_B;R}1G96N7vj!eLt3JMT3AcBSE6{V%sWQbcZ zqffNfgx*w9iU^~EPoK?^q32W=�!hlqT3vi#5}}%pf%iR){Rke6?F(QX2QF_?b?) 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�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

      U%$1&Q0b_0s$N9DL? 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

      Q<5@R7W(sk*={? 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 DK+|d} literal 0 HcmV?d00001 diff --git a/docs/img/ostrich.png b/docs/img/ostrich.png new file mode 100644 index 0000000000000000000000000000000000000000..1dbe890a44f4537055c5932a61b55caad282384f GIT binary patch literal 106406 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 literal 0 HcmV?d00001 diff --git a/docs/img/pixel_editor.png b/docs/img/pixel_editor.png new file mode 100644 index 0000000000000000000000000000000000000000..e1c80901ff8edb54f4a89b32c4e91835477f23cf GIT binary patch literal 9252 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 literal 0 HcmV?d00001 diff --git a/docs/img/pizza-squirrel.svg b/docs/img/pizza-squirrel.svg new file mode 100644 index 000000000..4cc93317c --- /dev/null +++ b/docs/img/pizza-squirrel.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + +No squirrel, no pizza76Squirrel, no pizza4No squirrel, pizza9Squirrel, pizza1 \ No newline at end of file diff --git a/docs/img/player.png b/docs/img/player.png new file mode 100644 index 0000000000000000000000000000000000000000..1244769acb1b9e8a611ab7fc6e92820a82c62a74 GIT binary patch literal 2376 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 G=l=lLZiEg1 literal 0 HcmV?d00001 diff --git a/docs/img/prompt.png b/docs/img/prompt.png new file mode 100644 index 0000000000000000000000000000000000000000..d485a0ef9edb75977e5ee63a9e8ba3febf5649da GIT binary patch literal 6145 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 literal 0 HcmV?d00001 diff --git a/docs/img/rabbits.svg b/docs/img/rabbits.svg new file mode 100644 index 000000000..5ee2dac16 --- /dev/null +++ b/docs/img/rabbits.svg @@ -0,0 +1,13 @@ + + +toString: <function>...teeth: "small"speak: <function>killerRabbitteeth: "long, sharp, ..."type: "killer"RabbitprototypeObjectcreate: <function>prototype... diff --git a/docs/img/re_number.svg b/docs/img/re_number.svg new file mode 100644 index 000000000..a2fe450e5 --- /dev/null +++ b/docs/img/re_number.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + Created with Snapword boundarygroup #1One of:01bOne of:digit-afhdigitword boundary diff --git a/docs/img/re_pigchickens.svg b/docs/img/re_pigchickens.svg new file mode 100644 index 000000000..4bec21c4d --- /dev/null +++ b/docs/img/re_pigchickens.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + Created with Raphaël 2.1.0 + + + + " " + + + + + + + boundary + + + + boundary + + + + Group #1 + + + + "chicken" + + + + "cow" + + + + "pig" + + + + digit + + + + "s" + + + + + + diff --git a/docs/img/re_slow.svg b/docs/img/re_slow.svg new file mode 100644 index 000000000..493c9bc99 --- /dev/null +++ b/docs/img/re_slow.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + Created with Raphaël 2.1.0 + + + + "b" + + + + Group #1 + + + + One of: + + + + "1" + + + + "0" + + + + + + diff --git a/docs/img/robot_idle.png b/docs/img/robot_idle.png new file mode 100644 index 0000000000000000000000000000000000000000..605293ef170a21bd17c7fbfaadba00e3d31fcbc2 GIT binary patch literal 433 zcmV;i0Z#sjP)BW*GS`~3RGcD^d^0tA5d@d6X;1qN|Mghydg^0e*a zVYw^X_KW?77-x7`?h0!8U|tGp`2bL^>jw=270jA>08o0h9s|`%uEGF7CIW26HK+K^Q)OE%yY0SH!O9Mp)&p|M@qYfm`gp<1(}U)d zkbzo0&Tr2JwSF3~MuvX@9M0n_0KmCkrz7<2Qo*jCH+oY8suHV0=pc?<^4$-4Vx6w|0M%vy(H4V0H0|2rt!_BkY_2U>=XNLc= b3GQsaT>Yc7FemN500000NkvXXu0mjfrlYgR literal 0 HcmV?d00001 diff --git a/docs/img/robot_idle2x.png b/docs/img/robot_idle2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d1b36b96088e006096b560d5809158c38fd14ced GIT binary patch literal 460 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NV3?%C=ER6$FEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBC?OZ%6XFV_f#B2EUkM97 zC@3fxI7|p=_)t+%;o;%&|Nno_b#ER5r5Q_t{DK)Ap4~_Tagw~1vc;Vkfo zEM{Qf76xHPhFNnYfP(BLp1!W^54pwoMa`#M#eW70b$YruhG?8$d-0-Rivb5qK-3Mf zoP%Q9SfduOMsIk-eD=lNJF99u(ms8hG{NDF+W{AiBQ1L8V&B~J6G~xz`HUO_QmvAUQh^kMk%6JP zuA!l>kx_`Dsg;qDm4UgofuWUwLA*4tJBo(f{FKbJN^}i|R;H#94N`o9TY(xFJYD@< J);T3K0RSI)mreix literal 0 HcmV?d00001 diff --git a/docs/img/robot_moving.gif b/docs/img/robot_moving.gif new file mode 100644 index 0000000000000000000000000000000000000000..d73feb3b312ba8651bf5aa8c249fc1eb85946dd1 GIT binary patch literal 1670 zcmZ?wbhEHb6k!ly_|Cvky=O851H-4UzY-RFSeC4PUIv_uTvIzr6H3K7ujD-Qi!DbF_ zE`u2x6dM?rC9Hfj3LhO{l3-5QqOqZ&k(rIDK_}tjT>g+pj*<1|Idc`lxp9Zt5&Gq1Rk`T2=&lYBf^?aRv#jx#Z_srati zxcDM7YsRHNl^--tt}yeJ3tezvc^tQFpO^l4#990VsBSt)c|o+3&c^1 z$Pso~`NV8y`+YLMHaVPNb}+mpV$h&@xnFW}l8e`5HTTv4&v{d_46+zmxfYatnX%x_ h)xP+{eX*yfB{&QU7jc;BgwNp=nj?F9Te1U#H2|kiHnRW# literal 0 HcmV?d00001 diff --git a/docs/img/robot_moving2x.gif b/docs/img/robot_moving2x.gif new file mode 100644 index 0000000000000000000000000000000000000000..3e145bbae85a7db5115a7d343c03b3d7b1d6d734 GIT binary patch literal 1557 zcmZ?wbhEHb)L_tH_{_lo1PTfY9v&VQ6%_^!69O7OBrN#w>FclRJ(EFl7(nqS3#R}B z2ZQ2&Za>$MU}whwS0gu?#Ga1WtOsW;yWctogfy3dZ9fJ6E($O@E~{ zBXNtq)cMH2m#R*EpVd&%A<5R@n7qKkC8&kPX`#STpT$nt&c|6;n57Zc%QR z=e)&rZWRmtCucFL8m(npKRb4XXpGbP9qdM%g14;PyK2dNFAb*yhn9Nn+q&vlN9t+W zZH(tH6i+&Lxbylf=i4_|M_p!q!t53$^!VxX7j8`NKU{2c{_ye6i)hBLpCsxeYbbpRI&$@A%gWb%FnG98{a{?beu%4rJ z`TWZGdZYY``dqb^*6eoWIEAc2g_8Up@xK0yeul}6Ra0H&HY{4)Fr`!`Zpo^gg<5laa)-iTv9FlfDaxDFX)b4J#i_Hs9C11L3>si8dm&r5M z?asXi-k!`aUY_f4d--a4U=ZWI`yW1j`tthw?>~S4sS7d;PY>UFvizCbi@WD^U70^I zzrXVN%d5a&AP?BVQ(4GRu7b3Ap$8rh&s4Nl$F^hn)8`8}eLFVHO*&!!;+;9uGwJ=r!QW%E{Aa? z>*^_sfk}J^+paj@QmMU+yAHFP9`ux1v3X-x`iYJ+r_P?^tUsr!H0W)7JrSUEe=B b|8^epPv)yH7=QhK@Qzc)V}rs34hCxgnkcg8 literal 0 HcmV?d00001 diff --git a/docs/img/skillsharing.png b/docs/img/skillsharing.png new file mode 100644 index 0000000000000000000000000000000000000000..48781637ae037dd07ced791da47284fa3507122b GIT binary patch literal 22850 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 Y&!TE5Pc4=~5e4>yx}yWb-Lii2Ukl!|`Tzg` literal 0 HcmV?d00001 diff --git a/docs/img/sprites.png b/docs/img/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..2a77ab99166c3c5921cc5884ba729bf4bd9619f7 GIT binary patch literal 1063 zcmV+?1laqDP)C|=`_$cDC36Lm%(Q1!GF+z@G=X)Qos@Mc-623 zkla`@bS9-r3mb(rHg`@f0I-{mgA{Mlr%q0sp1M-G zYTH~n4Xgl@$Tjf$WEHlvB13XxNnpXj{6xmx+&Q%pH1X4)u1u09t?$M8P;CS6d61}| zlmH^HQ?(tgj);H80>FOKCNLD#iv=%-gh;A*e5oWUK8tnj{3^6(0vs|@0hZUX#wEJJnHWW z`BR%uV;L&|VW<#gLva8zb?Z;*p?i~FQm&p(-C`ys^yb&s0wi6V$f07#n^ zXXqQ27xBQ~{plp5eXS%Hr>zQKW{1g;3(YQty3|!K5Z0s$}?lk$u zld>6QL#iau--DDtOQMp}Po9BCgD?%`4)%XYfec6;482yS>lZ}|w+7^1H(b%!`JrA# z6*Nx{6eZAf9F-_cO5)u}yjF`L2{u~~tBRSrb+;=QK?>4$S0y1-G?h07X`>s=`ADocF7%WZ;iHKkm0v19*NM~=NLQ4Mx(N1iFomvV8Y^?ne z5P~TJnj(Uj1PgOTh^G*_;E&)hT#Mb=WWT$!Gw&{uUGt{O?#-LK+q_R^=KChW(oY<4TUifYc%bYU+kLQA-w|Q}Icj5CL9Q#qY zzKgt-+Jf-YeDoVWa5(+S=lAJrd_A_crPt9;dH02gh=$LdNv~Jd)p~kTei)Als*g1D zzkxyw@B9pT5`m8K=|(HaBWB=ax9Jx!`(~Ypi0jvU&TyVH>XwB-+9qaWh+)^*m`tyPv-wYm073c;9rByxWDTqPo+ zx#!o&Qsr?j@&uKgAl~uI5(#|m7+8v%@l#%(x)tY;A#zg`tmH`R6vFs(b>Ep!KTv@j zzR%=v|D%!Q2`W25N#`#p2$L_PojQ1If7GuWJ(coH`WR3IGV$X3lh0=I;|cx^jT|c( zT!r;hx*F5g3z{4g{ZVCW^KKo-FO+#xu=*?Fi z&=x_FKoy&sCWQe%4-%y1946;V((m{jRA~!ZZ>PheeuS$r&KXt(K{D*KH2>PfdADyQ z|u&B?`W(Gt>Ku{(mrGXU-oM~uW94+pL98EoO75vaYQ4}1fdYBY8B*yna z|C^Ga%?wDSh{^ zQLgXT;+(+!%7U@lLh?GzVgLG^$rDr&g0vm4P2osN0Q+l-gErIPoCA#i&!ff_W)L!3 dpeJZ&+h3IbtJ38xrsx0w002ovPDHLkV1hYeBESFu literal 0 HcmV?d00001 diff --git a/docs/img/svg-demo.png b/docs/img/svg-demo.png new file mode 100644 index 0000000000000000000000000000000000000000..1efcff8235572bdf43974a78132e66934acc2fd1 GIT binary patch literal 5234 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 + +dodefinex10if>x5print"large"print"small" \ No newline at end of file diff --git a/docs/img/tamil.png b/docs/img/tamil.png new file mode 100644 index 0000000000000000000000000000000000000000..dc7591f742ec3ffcd9927ceba6e182c4b028c1a7 GIT binary patch literal 23533 zcmaHTWmp_d6YbzG!69gHcemi~ECdJ;+}#~QaCc_|1b26LcL`2#cel&?-RJ(jXJ=(* zXL{@DuBooBI<;ZSic%aETbR+w}ps_Mub7EwIl=pq=1aLh`RgoNtW4H zOqtJv)a|ifNh#)(Md6BPphd)Kxu8X=Bib`PT_@LX%gmUUT8lz+i?AR>JQLQ%m?0no zVih-EN!_qic+muX?EJu_{2!=&elg2V^T1h{pNAp+AKpNl%Gh832TN#(|DpI3^ndsx ziT*#t5{Uf&zJ8A<2;=|7$T+XuF81GfH?Vi*MywnfND^OI3+olMVN2yx0xbz4Kk&~e zRxI1dOp2Ni0S>@21F?l4J6>7Jh0{HF`>Ls7`fqX6nQyO2Mcq$_lo0>#f{d6bTU}(? zP`Q+(JdAu=M@M61Sb8kPh;aJ&gjvLErxPjdinXe~X_y%b_z#5I&UTu$4?mzEMF zCaSLuG%q)s-^-z23HUxQ1m;U{KBJvzwRb~VLjCV_GL5{Y(8v{Tk^}sMZ#rlstRv82 zqTY1772}Kd#Jp8bvn_%ln$Ds8tt~Ce=FoG*|Mf>K0SZJ%HvQV#+8%QTM`~Zn1l-w@ z{lU>~MuK;gwiYBl8;&K~f2~VLy?vTTK1;IV-u>fHyYDrs@(0di0dixFjQLAXkC^F?WLD(gZEehn z541M9$(sryBP?=H)mPU~&8ob5qdH>!j84{&(UUQ?j4Q8Youd;Ibya%uHdXpc-`$^P zf1_g&Y&ZUB$nydR6a?ixfd&Jg8)(kfyr2Wg7*z_A~d{*QC4Ji%pG#LnF=mvt_Tno#CG+Km8h6Msdr>#>WB)c6F51)b7CT z_kMhK>fM{kSeiQD@IK=mz`zKiAh)DwrZPwk#M4rwhqpB4K|zF+79Z-idg=y?<8hAs zW7o>3OvJ&#Zf%JIsVDs;9_buCl@po#DLSZ>dcZn+7ZH{(UB#9BU@~MSM#SzyM5pd0 z%r+S)%9Pnm`LCDrfWXpnPQ%qo-8hm%L=U8UxIw3CEd91}fCXh^RMYdmRhk}`oq>@u zu??T3{vM>cy)#`KcFq=YUAk?Q;8g4Q=>ipoGXH%j?@ehxif{8P#ix45l`LJ36ow2H z9ZgC($@Emqv~)ZuKv|k1hFM*`Cz8-2>rtq(q$Z)@Zg?6mNj3<=zaPcbI*}kFr|eGz z(BvQDw&KC4)<%7pVfDt1qM_=n=j>ee+~lBW6oPh@Sj|Lkr`P$+#I@$o_R09tPoBqm zJv={Sn&za + +image/svg+xml \ No newline at end of file diff --git a/docs/img/village.png b/docs/img/village.png new file mode 100644 index 0000000000000000000000000000000000000000..886c8fce18b2e6ea46a6524edca470c0cf5fdd55 GIT binary patch literal 12574 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 A{{R30 literal 0 HcmV?d00001 diff --git a/docs/img/village2x.png b/docs/img/village2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8b1d7c7656859bc6b3102fa7dc86f07059197a1d GIT binary patch literal 20935 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 ^rQ=BiGBz7iaRX@xuM~4_q24 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 VYRMNxA&g>H*X*vA{BirQ{{g8@%#Q#7 literal 0 HcmV?d00001 diff --git a/docs/img/weresquirrel.png b/docs/img/weresquirrel.png new file mode 100644 index 0000000000000000000000000000000000000000..c24d684c5bfb5caa27f281e9dca98a42a1934ce7 GIT binary patch literal 59456 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 literal 0 HcmV?d00001 diff --git a/docs/img/weresquirrel.svg b/docs/img/weresquirrel.svg new file mode 100644 index 000000000..687c3675a --- /dev/null +++ b/docs/img/weresquirrel.svg @@ -0,0 +1,6994 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index c6f5421e1..c7702175a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1 +1,146 @@ -Hello eloquent JS Español + + + + + Eloquent JavaScript + + + + +

        +
        + +

        + + Cover image + +

        + +

        Eloquent JavaScript
        3rd edition

        + +

        This is a book about JavaScript, programming, and the wonders of + the digital. You can read it online here, or get your own + paperback + copy.

        + +

        Written by Marijn Haverbeke.

        + +
        +

        Licensed under + a Creative + Commons attribution-noncommercial license. All code in this book + may also be considered licensed under + an MIT license.

        + +

        Illustrations by various artists: Cover and chapter illustrations + by Madalina Tantareanu. + Pixel art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular + expression diagrams in Chapter 9 generated + with regexper.com by Jeff + Avallone. Village photograph in Chapter 11 by Fabrice Creuzot. Game + concept for Chapter 15 by Thomas + Palef.

        + +

        The third edition was made possible + by 325 financial backers, most + notably and . The second edition + was supported by 454 backers, with + significant contributions + from , , + and .

        +
        + +
        + + + + +
        diff --git a/docs/js/.tern-project b/docs/js/.tern-project new file mode 100644 index 000000000..3a218cbc1 --- /dev/null +++ b/docs/js/.tern-project @@ -0,0 +1,3 @@ +{ + "libs": ["browser"] +} \ No newline at end of file diff --git a/docs/js/acorn_codemirror.js b/docs/js/acorn_codemirror.js new file mode 100644 index 000000000..729457568 --- /dev/null +++ b/docs/js/acorn_codemirror.js @@ -0,0 +1,11 @@ +(function(e,t){typeof exports==="object"&&typeof module!=="undefined"?module.exports=t():typeof define==="function"&&define.amd?define(t):e.CodeMirror=t()})(this,function(){"use strict";var e=navigator.userAgent;var t=navigator.platform;var r=/gecko\/\d/i.test(e);var i=/MSIE \d/.test(e);var n=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e);var a=/Edge\/(\d+)/.exec(e);var s=i||n||a;var o=s&&(i?document.documentMode||6:+(a||n)[1]);var l=!a&&/WebKit\//.test(e);var u=l&&/Qt\/\d+\.\d+/.test(e);var c=!a&&/Chrome\//.test(e);var f=/Opera\//.test(e);var h=/Apple Computer/.test(navigator.vendor);var p=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e);var d=/PhantomJS/.test(e);var m=!a&&/AppleWebKit/.test(e)&&/Mobile\/\w+/.test(e);var v=/Android/.test(e);var g=m||v||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(e);var y=m||/Mac/.test(t);var x=/\bCrOS\b/.test(e);var b=/win/i.test(t);var w=f&&e.match(/Version\/(\d*\.\d*)/);if(w){w=Number(w[1])}if(w&&w>=15){f=false;l=true}var k=y&&(u||f&&(w==null||w<12.11));var C=r||s&&o>=9;function S(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}var L=function(e,t){var r=e.className;var i=S(t).exec(r);if(i){var n=r.slice(i.index+i[0].length);e.className=r.slice(0,i.index)+(n?i[1]+n:"")}};function T(e){for(var t=e.childNodes.length;t>0;--t){e.removeChild(e.firstChild)}return e}function A(e,t){return T(e).appendChild(t)}function E(e,t,r,i){var n=document.createElement(e);if(r){n.className=r}if(i){n.style.cssText=i}if(typeof t=="string"){n.appendChild(document.createTextNode(t))}else if(t){for(var a=0;a=t){return s+(t-a)}s+=o-a;s+=r-s%r;a=o+1}}var W=function(){this.id=null;this.f=null;this.time=0;this.handler=R(this.onTimeout,this)};W.prototype.onTimeout=function(e){e.id=0;if(e.time<=+new Date){e.f()}else{setTimeout(e.handler,e.time-+new Date)}};W.prototype.set=function(e,t){this.f=t;var r=+new Date+e;if(!this.id||r=t){return i+Math.min(s,t-n)}n+=a-i;n+=r-n%r;i=a+1;if(n>=t){return i}}}var K=[""];function $(e){while(K.length<=e){K.push(X(K)+" ")}return K[e]}function X(e){return e[e.length-1]}function Y(e,t){var r=[];for(var i=0;i"€"&&(e.toUpperCase()!=e.toLowerCase()||ee.test(e))}function re(e,t){if(!t){return te(e)}if(t.source.indexOf("\\w")>-1&&te(e)){return true}return t.test(e)}function ie(e){for(var t in e){if(e.hasOwnProperty(t)&&e[t]){return false}}return true}var ne=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;function ae(e){return e.charCodeAt(0)>=768&&ne.test(e)}function se(e,t,r){while((r<0?t>0:tr?-1:1;for(;;){if(t==r){return t}var n=(t+r)/2,a=i<0?Math.ceil(n):Math.floor(n);if(a==t){return e(a)?t:r}if(e(a)){r=a}else{t=a+i}}}function le(e,t,r,i){if(!e){return i(t,r,"ltr",0)}var n=false;for(var a=0;at||t==r&&s.to==t){i(Math.max(s.from,t),Math.min(s.to,r),s.level==1?"rtl":"ltr",a);n=true}}if(!n){i(t,r,"ltr")}}var ue=null;function ce(e,t,r){var i;ue=null;for(var n=0;nt){return n}if(a.to==t){if(a.from!=a.to&&r=="before"){i=n}else{ue=n}}if(a.from==t){if(a.from!=a.to&&r!="before"){i=n}else{ue=n}}}return i!=null?i:ue}var fe=function(){var e="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";var t="nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111";function r(r){if(r<=247){return e.charAt(r)}else if(1424<=r&&r<=1524){return"R"}else if(1536<=r&&r<=1785){return t.charAt(r-1536)}else if(1774<=r&&r<=2220){return"r"}else if(8192<=r&&r<=8203){return"w"}else if(r==8204){return"b"}else{return"L"}}var i=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;var n=/[stwN]/,a=/[LRr]/,s=/[Lb1n]/,o=/[1n]/;function l(e,t,r){this.level=e;this.from=t;this.to=r}return function(e,t){var u=t=="ltr"?"L":"R";if(e.length==0||t=="ltr"&&!i.test(e)){return false}var c=e.length,f=[];for(var h=0;h-1){i[t]=n.slice(0,a).concat(n.slice(a+1))}}}}function ge(e,t){var r=me(e,t);if(!r.length){return}var i=Array.prototype.slice.call(arguments,2);for(var n=0;n0}function we(e){e.prototype.on=function(e,t){de(this,e,t)};e.prototype.off=function(e,t){ve(this,e,t)}}function ke(e){if(e.preventDefault){e.preventDefault()}else{e.returnValue=false}}function Ce(e){if(e.stopPropagation){e.stopPropagation()}else{e.cancelBubble=true}}function Se(e){return e.defaultPrevented!=null?e.defaultPrevented:e.returnValue==false}function Le(e){ke(e);Ce(e)}function Te(e){return e.target||e.srcElement}function Ae(e){var t=e.which;if(t==null){if(e.button&1){t=1}else if(e.button&2){t=3}else if(e.button&4){t=2}}if(y&&e.ctrlKey&&t==1){t=3}return t}var Ee=function(){if(s&&o<9){return false}var e=E("div");return"draggable"in e||"dragDrop"in e}();var Me;function Ne(e){if(Me==null){var t=E("span","​");A(e,E("span",[t,document.createTextNode("x")]));if(e.firstChild.offsetHeight!=0){Me=t.offsetWidth<=1&&t.offsetHeight>2&&!(s&&o<8)}}var r=Me?E("span","​"):E("span"," ",null,"display: inline-block; width: 1px; margin-right: -1px");r.setAttribute("cm-text","");return r}var _e;function Pe(e){if(_e!=null){return _e}var t=A(e,document.createTextNode("AخA"));var r=N(t,0,1).getBoundingClientRect();var i=N(t,1,2).getBoundingClientRect();T(e);if(!r||r.left==r.right){return false}return _e=i.right-r.right<3}var Ie="\n\nb".split(/\n/).length!=3?function(e){var t=0,r=[],i=e.length;while(t<=i){var n=e.indexOf("\n",t);if(n==-1){n=e.length}var a=e.slice(t,e.charAt(n-1)=="\r"?n-1:n);var s=a.indexOf("\r");if(s!=-1){r.push(a.slice(0,s));t+=s+1}else{r.push(a);t=n+1}}return r}:function(e){return e.split(/\r\n?|\n/)};var Oe=window.getSelection?function(e){try{return e.selectionStart!=e.selectionEnd}catch(e){return false}}:function(e){var t;try{t=e.ownerDocument.selection.createRange()}catch(e){}if(!t||t.parentElement()!=e){return false}return t.compareEndPoints("StartToEnd",t)!=0};var De=function(){var e=E("div");if("oncopy"in e){return true}e.setAttribute("oncopy","return;");return typeof e.oncopy=="function"}();var Re=null;function Ve(e){if(Re!=null){return Re}var t=A(e,E("span","x"));var r=t.getBoundingClientRect();var i=N(t,0,1).getBoundingClientRect();return Re=Math.abs(r.left-i.left)>1}var Fe={},We={};function ze(e,t){if(arguments.length>2){t.dependencies=Array.prototype.slice.call(arguments,2)}Fe[e]=t}function Be(e,t){We[e]=t}function He(e){if(typeof e=="string"&&We.hasOwnProperty(e)){e=We[e]}else if(e&&typeof e.name=="string"&&We.hasOwnProperty(e.name)){var t=We[e.name];if(typeof t=="string"){t={name:t}}e=J(t,e);e.name=t.name}else if(typeof e=="string"&&/^[\w\-]+\/[\w\-]+\+xml$/.test(e)){return He("application/xml")}else if(typeof e=="string"&&/^[\w\-]+\/[\w\-]+\+json$/.test(e)){return He("application/json")}if(typeof e=="string"){return{name:e}}else{return e||{name:"null"}}}function je(e,t){t=He(t);var r=Fe[t.name];if(!r){return je(e,"text/plain")}var i=r(e,t);if(Ue.hasOwnProperty(t.name)){var n=Ue[t.name];for(var a in n){if(!n.hasOwnProperty(a)){continue}if(i.hasOwnProperty(a)){i["_"+a]=i[a]}i[a]=n[a]}}i.name=t.name;if(t.helperType){i.helperType=t.helperType}if(t.modeProps){for(var s in t.modeProps){i[s]=t.modeProps[s]}}return i}var Ue={};function Ge(e,t){var r=Ue.hasOwnProperty(e)?Ue[e]:Ue[e]={};V(t,r)}function qe(e,t){if(t===true){return t}if(e.copyState){return e.copyState(t)}var r={};for(var i in t){var n=t[i];if(n instanceof Array){n=n.concat([])}r[i]=n}return r}function Ke(e,t){var r;while(e.innerMode){r=e.innerMode(t);if(!r||r.mode==e){break}t=r.state;e=r.mode}return r||{mode:e,state:t}}function $e(e,t,r){return e.startState?e.startState(t,r):true}var Xe=function(e,t,r){this.pos=this.start=0;this.string=e;this.tabSize=t||8;this.lastColumnPos=this.lastColumnValue=0;this.lineStart=0;this.lineOracle=r};Xe.prototype.eol=function(){return this.pos>=this.string.length};Xe.prototype.sol=function(){return this.pos==this.lineStart};Xe.prototype.peek=function(){return this.string.charAt(this.pos)||undefined};Xe.prototype.next=function(){if(this.post};Xe.prototype.eatSpace=function(){var e=this;var t=this.pos;while(/[\s\u00a0]/.test(this.string.charAt(this.pos))){++e.pos}return this.pos>t};Xe.prototype.skipToEnd=function(){this.pos=this.string.length};Xe.prototype.skipTo=function(e){var t=this.string.indexOf(e,this.pos);if(t>-1){this.pos=t;return true}};Xe.prototype.backUp=function(e){this.pos-=e};Xe.prototype.column=function(){if(this.lastColumnPos0){return null}if(a&&t!==false){this.pos+=a[0].length}return a}};Xe.prototype.current=function(){return this.string.slice(this.start,this.pos)};Xe.prototype.hideFirstChars=function(e,t){this.lineStart+=e;try{return t()}finally{this.lineStart-=e}};Xe.prototype.lookAhead=function(e){var t=this.lineOracle;return t&&t.lookAhead(e)};Xe.prototype.baseToken=function(){var e=this.lineOracle;return e&&e.baseToken(this.pos)};function Ye(e,t){t-=e.first;if(t<0||t>=e.size){throw new Error("There is no line "+(t+e.first)+" in the document.")}var r=e;while(!r.lines){for(var i=0;;++i){var n=r.children[i],a=n.chunkSize();if(t=e.first&&tr){return nt(r,Ye(e,r).text.length)}return ht(t,Ye(e,t.line).text.length)}function ht(e,t){var r=e.ch;if(r==null||r>t){return nt(e.line,t)}else if(r<0){return nt(e.line,0)}else{return e}}function pt(e,t){var r=[];for(var i=0;ithis.maxLookAhead){this.maxLookAhead=e}return t};mt.prototype.baseToken=function(e){var t=this;if(!this.baseTokens){return null}while(this.baseTokens[this.baseTokenPos]<=e){t.baseTokenPos+=2}var r=this.baseTokens[this.baseTokenPos+1];return{type:r&&r.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}};mt.prototype.nextLine=function(){this.line++;if(this.maxLookAhead>0){this.maxLookAhead--}};mt.fromSaved=function(e,t,r){if(t instanceof dt){return new mt(e,qe(e.mode,t.state),r,t.lookAhead)}else{return new mt(e,qe(e.mode,t),r)}};mt.prototype.save=function(e){var t=e!==false?qe(this.doc.mode,this.state):this.state;return this.maxLookAhead>0?new dt(t,this.maxLookAhead):t};function vt(e,t,r,i){var n=[e.state.modeGen],a={};Lt(e,t.text,e.doc.mode,r,function(e,t){return n.push(e,t)},a,i);var s=r.state;var o=function(i){r.baseTokens=n;var o=e.state.overlays[i],l=1,u=0;r.state=true;Lt(e,t.text,o.mode,r,function(e,t){var r=l;while(ue){n.splice(l,1,e,n[l+1],i)}l+=2;u=Math.min(e,i)}if(!t){return}if(o.opaque){n.splice(r,l-r,e,"overlay "+t);l=r+2}else{for(;re.options.maxHighlightLength&&qe(e.doc.mode,i.state);var a=vt(e,t,i);if(n){i.state=n}t.stateAfter=i.save(!n);t.styles=a.styles;if(a.classes){t.styleClasses=a.classes}else if(t.styleClasses){t.styleClasses=null}if(r===e.doc.highlightFrontier){e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier)}}return t.styles}function yt(e,t,r){var i=e.doc,n=e.display;if(!i.mode.startState){return new mt(i,true,t)}var a=Tt(e,t,r);var s=a>i.first&&Ye(i,a-1).stateAfter;var o=s?mt.fromSaved(i,s,a):new mt(i,$e(i.mode),a);i.iter(a,t,function(r){xt(e,r.text,o);var i=o.line;r.stateAfter=i==t-1||i%5==0||i>=n.viewFrom&&it.start){return a}}throw new Error("Mode "+e.name+" failed to advance stream.")}var kt=function(e,t,r){this.start=e.start;this.end=e.pos;this.string=e.current();this.type=t||null;this.state=r};function Ct(e,t,r,i){var n=e.doc,a=n.mode,s;t=ft(n,t);var o=Ye(n,t.line),l=yt(e,t.line,r);var u=new Xe(o.text,e.options.tabSize,l),c;if(i){c=[]}while((i||u.pose.options.maxHighlightLength){o=false;if(s){xt(e,t,i,c.pos)}c.pos=t.length;f=null}else{f=St(wt(r,c,i.state,h),a)}if(h){var p=h[0].name;if(p){f="m-"+(f?p+" "+f:p)}}if(!o||u!=f){while(ls;--o){if(o<=a.first){return a.first}var l=Ye(a,o-1),u=l.stateAfter;if(u&&(!r||o+(u instanceof dt?u.lookAhead:0)<=a.modeFrontier)){return o}var c=F(l.text,null,e.options.tabSize);if(n==null||i>c){n=o-1;i=c}}return n}function At(e,t){e.modeFrontier=Math.min(e.modeFrontier,t);if(e.highlightFrontierr;i--){var n=Ye(e,i).stateAfter;if(n&&(!(n instanceof dt)||i+n.lookAhead=t:a.to>t);(i||(i=[])).push(new Pt(s,a.from,l?null:a.to))}}}return i}function Vt(e,t,r){var i;if(e){for(var n=0;n=t:a.to>t);if(o||a.from==t&&s.type=="bookmark"&&(!r||a.marker.insertLeft)){var l=a.from==null||(s.inclusiveLeft?a.from<=t:a.from0&&o){for(var b=0;b0){continue}var c=[l,1],f=at(u.from,o.from),h=at(u.to,o.to);if(f<0||!s.inclusiveLeft&&!f){c.push({from:u.from,to:o.from})}if(h>0||!s.inclusiveRight&&!h){c.push({from:o.to,to:u.to})}n.splice.apply(n,c);l+=c.length-3}}return n}function Bt(e){var t=e.markedSpans;if(!t){return}for(var r=0;rt)&&(!i||Gt(i,a.marker)<0)){i=a.marker}}}return i}function Yt(e,t,r,i,n){var a=Ye(e,t);var s=Mt&&a.markedSpans;if(s){for(var o=0;o=0&&f<=0||c<=0&&f>=0){continue}if(c<=0&&(l.marker.inclusiveRight&&n.inclusiveLeft?at(u.to,r)>=0:at(u.to,r)>0)||c>=0&&(l.marker.inclusiveRight&&n.inclusiveLeft?at(u.from,i)<=0:at(u.from,i)<0)){return true}}}}function Qt(e){var t;while(t=Kt(e)){e=t.find(-1,true).line}return e}function Zt(e){var t;while(t=$t(e)){e=t.find(1,true).line}return e}function Jt(e){var t,r;while(t=$t(e)){e=t.find(1,true).line;(r||(r=[])).push(e)}return r}function er(e,t){var r=Ye(e,t),i=Qt(r);if(r==i){return t}return et(i)}function tr(e,t){if(t>e.lastLine()){return t}var r=Ye(e,t),i;if(!rr(e,r)){return t}while(i=$t(r)){r=i.find(1,true).line}return et(r)+1}function rr(e,t){var r=Mt&&t.markedSpans;if(r){for(var i=void 0,n=0;nt.maxLineLength){t.maxLineLength=r;t.maxLine=e}})}var or=function(e,t,r){this.text=e;Ht(this,t);this.height=r?r(this):1};or.prototype.lineNo=function(){return et(this)};we(or);function lr(e,t,r,i){e.text=t;if(e.stateAfter){e.stateAfter=null}if(e.styles){e.styles=null}if(e.order!=null){e.order=null}Bt(e);Ht(e,r);var n=i?i(e):1;if(n!=e.height){Je(e,n)}}function ur(e){e.parent=null;Bt(e)}var cr={},fr={};function hr(e,t){if(!e||/^\s*$/.test(e)){return null}var r=t.addModeClass?fr:cr;return r[e]||(r[e]=e.replace(/\S+/g,"cm-$&"))}function pr(e,t){var r=M("span",null,null,l?"padding-right: .1px":null);var i={pre:M("pre",[r],"CodeMirror-line"),content:r,col:0,pos:0,cm:e,trailingSpace:false,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var n=0;n<=(t.rest?t.rest.length:0);n++){var a=n?t.rest[n-1]:t.line,s=void 0;i.pos=0;i.addToken=mr;if(Pe(e.display.measure)&&(s=he(a,e.doc.direction))){i.addToken=gr(i.addToken,s)}i.map=[];var o=t!=e.display.externalMeasured&&et(a);xr(a,i,gt(e,a,o));if(a.styleClasses){if(a.styleClasses.bgClass){i.bgClass=O(a.styleClasses.bgClass,i.bgClass||"")}if(a.styleClasses.textClass){i.textClass=O(a.styleClasses.textClass,i.textClass||"")}}if(i.map.length==0){i.map.push(0,0,i.content.appendChild(Ne(e.display.measure)))}if(n==0){t.measure.map=i.map;t.measure.cache={}}else{(t.measure.maps||(t.measure.maps=[])).push(i.map);(t.measure.caches||(t.measure.caches=[])).push({})}}if(l){var u=i.content.lastChild;if(/\bcm-tab\b/.test(u.className)||u.querySelector&&u.querySelector(".cm-tab")){i.content.className="cm-tab-wrap-hack"}}ge(e,"renderLine",e,t.line,i.pre);if(i.pre.className){i.textClass=O(i.pre.className,i.textClass||"")}return i}function dr(e){var t=E("span","•","cm-invalidchar");t.title="\\u"+e.charCodeAt(0).toString(16);t.setAttribute("aria-label",t.title);return t}function mr(e,t,r,i,n,a,l){if(!t){return}var u=e.splitSpaces?vr(t,e.trailingSpace):t;var c=e.cm.state.specialChars,f=false;var h;if(!c.test(t)){e.col+=t.length;h=document.createTextNode(u);e.map.push(e.pos,e.pos+t.length,h);if(s&&o<9){f=true}e.pos+=t.length}else{h=document.createDocumentFragment();var p=0;while(true){c.lastIndex=p;var d=c.exec(t);var m=d?d.index-p:t.length-p;if(m){var v=document.createTextNode(u.slice(p,p+m));if(s&&o<9){h.appendChild(E("span",[v]))}else{h.appendChild(v)}e.map.push(e.pos,e.pos+m,v);e.col+=m;e.pos+=m}if(!d){break}p+=m+1;var g=void 0;if(d[0]=="\t"){var y=e.cm.options.tabSize,x=y-e.col%y;g=h.appendChild(E("span",$(x),"cm-tab"));g.setAttribute("role","presentation");g.setAttribute("cm-text","\t");e.col+=x}else if(d[0]=="\r"||d[0]=="\n"){g=h.appendChild(E("span",d[0]=="\r"?"␍":"␤","cm-invalidchar"));g.setAttribute("cm-text",d[0]);e.col+=1}else{g=e.cm.options.specialCharPlaceholder(d[0]);g.setAttribute("cm-text",d[0]);if(s&&o<9){h.appendChild(E("span",[g]))}else{h.appendChild(g)}e.col+=1}e.map.push(e.pos,e.pos+1,g);e.pos++}}e.trailingSpace=u.charCodeAt(t.length-1)==32;if(r||i||n||f||a){var b=r||"";if(i){ +b+=i}if(n){b+=n}var w=E("span",[h],b,a);if(l){for(var k in l){if(l.hasOwnProperty(k)&&k!="style"&&k!="class"){w.setAttribute(k,l[k])}}}return e.content.appendChild(w)}e.content.appendChild(h)}function vr(e,t){if(e.length>1&&!/ /.test(e)){return e}var r=t,i="";for(var n=0;nu&&f.from<=u){break}}if(f.to>=c){return e(r,i,n,a,s,o,l)}e(r,i.slice(0,f.to-u),n,a,null,o,l);a=null;i=i.slice(f.to-u);u=f.to}}}function yr(e,t,r,i){var n=!i&&r.widgetNode;if(n){e.map.push(e.pos,e.pos+t,n)}if(!i&&e.cm.display.input.needsContentAttribute){if(!n){n=e.content.appendChild(document.createElement("span"))}n.setAttribute("cm-marker",r.id)}if(n){e.cm.display.input.setUneditable(n);e.content.appendChild(n)}e.pos+=t;e.trailingSpace=false}function xr(e,t,r){var i=e.markedSpans,n=e.text,a=0;if(!i){for(var s=1;sl||C.collapsed&&k.to==l&&k.from==l)){if(k.to!=null&&k.to!=l&&p>k.to){p=k.to;m=""}if(C.className){d+=" "+C.className}if(C.css){h=(h?h+";":"")+C.css}if(C.startStyle&&k.from==l){v+=" "+C.startStyle}if(C.endStyle&&k.to==p){(b||(b=[])).push(C.endStyle,k.to)}if(C.title){(y||(y={})).title=C.title}if(C.attributes){for(var S in C.attributes){(y||(y={}))[S]=C.attributes[S]}}if(C.collapsed&&(!g||Gt(g.marker,C)<0)){g=k}}else if(k.from>l&&p>k.from){p=k.from}}if(b){for(var L=0;L=o){break}var A=Math.min(o,p);while(true){if(c){var E=l+c.length;if(!g){var M=E>A?c.slice(0,A-l):c;t.addToken(t,M,f?f+d:d,v,l+M.length==p?m:"",h,y)}if(E>=A){c=c.slice(A-l);l=A;break}l=E;v=""}c=n.slice(a,a=r[u++]);f=hr(r[u++],t.cm.options)}}}function br(e,t,r){this.line=t;this.rest=Jt(t);this.size=this.rest?et(X(this.rest))-r+1:1;this.node=this.text=null;this.hidden=rr(e,t)}function wr(e,t,r){var i=[],n;for(var a=t;a2){a.push((l.bottom+u.top)/2-r.top)}}}a.push(r.bottom-r.top)}}function Yr(e,t,r){if(e.line==t){return{map:e.measure.map,cache:e.measure.cache}}for(var i=0;ir){return{map:e.measure.maps[n],cache:e.measure.caches[n],before:true}}}}function Qr(e,t){t=Qt(t);var r=et(t);var i=e.display.externalMeasured=new br(e.doc,t,r);i.lineN=r;var n=i.built=pr(e,i);i.text=n.pre;A(e.display.lineMeasure,n.pre);return i}function Zr(e,t,r,i){return ti(e,ei(e,t),r,i)}function Jr(e,t){if(t>=e.display.viewFrom&&t=r.lineN&&tt){a=l-o;n=a-1;if(t>=l){s="right"}}if(n!=null){i=e[u+2];if(o==l&&r==(i.insertLeft?"left":"right")){s=r}if(r=="left"&&n==0){while(u&&e[u-2]==e[u-3]&&e[u-1].insertLeft){i=e[(u-=3)+2];s="left"}}if(r=="right"&&n==l-o){while(u=0;n--){if((r=e[n]).left!=r.right){break}}}return r}function ai(e,t,r,i){var n=ii(t.map,r,i);var a=n.node,l=n.start,u=n.end,c=n.collapse;var f;if(a.nodeType==3){for(var h=0;h<4;h++){while(l&&ae(t.line.text.charAt(n.coverStart+l))){--l}while(n.coverStart+u0){c=i="right"}var p;if(e.options.lineWrapping&&(p=a.getClientRects()).length>1){f=p[i=="right"?p.length-1:0]}else{f=a.getBoundingClientRect()}}if(s&&o<9&&!l&&(!f||!f.left&&!f.right)){var d=a.parentNode.getClientRects()[0];if(d){f={left:d.left,right:d.left+Ei(e.display),top:d.top,bottom:d.bottom}}else{f=ri}}var m=f.top-t.rect.top,v=f.bottom-t.rect.top;var g=(m+v)/2;var y=t.view.measure.heights;var x=0;for(;x=i.text.length){l=i.text.length;u="before"}else if(l<=0){l=0;u="after"}if(!o){return s(u=="before"?l-1:l,u=="before")}function c(e,t,r){var i=o[t],n=i.level==1;return s(r?e-1:e,n!=r)}var f=ce(o,l,u);var h=ue;var p=c(l,f,u=="before");if(h!=null){p.other=c(l,h,u!="before")}return p}function gi(e,t){var r=0;t=ft(e.doc,t);if(!e.options.lineWrapping){r=Ei(e.display)*t.ch}var i=Ye(e.doc,t.line);var n=nr(i)+jr(e.display);return{left:r,right:r,top:n,bottom:n+i.height}}function yi(e,t,r,i,n){var a=nt(e,t,r);a.xRel=n;if(i){a.outside=i}return a}function xi(e,t,r){var i=e.doc;r+=e.display.viewOffset;if(r<0){return yi(i.first,0,null,-1,-1)}var n=tt(i,r),a=i.first+i.size-1;if(n>a){return yi(i.first+i.size-1,Ye(i,a).text.length,null,1,1)}if(t<0){t=0}var s=Ye(i,n);for(;;){var o=Ci(e,s,n,t,r);var l=Xt(s,o.ch+(o.xRel>0||o.outside>0?1:0));if(!l){return o}var u=l.find(1);if(u.line==n){return u}s=Ye(i,n=u.line)}}function bi(e,t,r,i){i-=hi(t);var n=t.text.length;var a=oe(function(t){return ti(e,r,t-1).bottom<=i},n,0);n=oe(function(t){return ti(e,r,t).top>i},a,n);return{begin:a,end:n}}function wi(e,t,r,i){if(!r){r=ei(e,t)}var n=pi(e,t,ti(e,r,i),"line").top;return bi(e,t,r,n)}function ki(e,t,r,i){return e.bottom<=r?false:e.top>r?true:(i?e.left:e.right)>t}function Ci(e,t,r,i,n){n-=nr(t);var a=ei(e,t);var s=hi(t);var o=0,l=t.text.length,u=true;var c=he(t,e.doc.direction);if(c){var f=(e.options.lineWrapping?Li:Si)(e,t,r,a,c,i,n);u=f.level!=1;o=u?f.from:f.to-1;l=u?f.to:f.from-1}var h=null,p=null;var d=oe(function(t){var r=ti(e,a,t);r.top+=s;r.bottom+=s;if(!ki(r,i,n,false)){return false}if(r.top<=n&&r.left<=i){h=t;p=r}return true},o,l);var m,v,g=false;if(p){var y=i-p.left=b.bottom?1:0}d=se(t.text,d,1);return yi(r,d,v,g,i-m)}function Si(e,t,r,i,n,a,s){var o=oe(function(o){var l=n[o],u=l.level!=1;return ki(vi(e,nt(r,u?l.to:l.from,u?"before":"after"),"line",t,i),a,s,true)},0,n.length-1);var l=n[o];if(o>0){var u=l.level!=1;var c=vi(e,nt(r,u?l.from:l.to,u?"after":"before"),"line",t,i);if(ki(c,a,s,true)&&c.top>s){l=n[o-1]}}return l}function Li(e,t,r,i,n,a,s){var o=bi(e,t,i,s);var l=o.begin;var u=o.end;if(/\s/.test(t.text.charAt(u-1))){u--}var c=null,f=null;for(var h=0;h=u||p.to<=l){continue}var d=p.level!=1;var m=ti(e,i,d?Math.min(u,p.to)-1:Math.max(l,p.from)).right;var v=mv){c=p;f=v}}if(!c){c=n[n.length-1]}if(c.fromu){c={from:c.from,to:u,level:c.level}}return c}var Ti;function Ai(e){if(e.cachedTextHeight!=null){return e.cachedTextHeight}if(Ti==null){Ti=E("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t){Ti.appendChild(document.createTextNode("x"));Ti.appendChild(E("br"))}Ti.appendChild(document.createTextNode("x"))}A(e.measure,Ti);var r=Ti.offsetHeight/50;if(r>3){e.cachedTextHeight=r}T(e.measure);return r||1}function Ei(e){if(e.cachedCharWidth!=null){return e.cachedCharWidth}var t=E("span","xxxxxxxxxx");var r=E("pre",[t],"CodeMirror-line-like");A(e.measure,r);var i=t.getBoundingClientRect(),n=(i.right-i.left)/10;if(n>2){e.cachedCharWidth=n}return n||10}function Mi(e){var t=e.display,r={},i={};var n=t.gutters.clientLeft;for(var a=t.gutters.firstChild,s=0;a;a=a.nextSibling,++s){var o=e.display.gutterSpecs[s].className;r[o]=a.offsetLeft+a.clientLeft+n;i[o]=a.clientWidth}return{fixedPos:Ni(t),gutterTotalWidth:t.gutters.offsetWidth,gutterLeft:r,gutterWidth:i,wrapperWidth:t.wrapper.clientWidth}}function Ni(e){return e.scroller.getBoundingClientRect().left-e.sizer.getBoundingClientRect().left}function _i(e){var t=Ai(e.display),r=e.options.lineWrapping;var i=r&&Math.max(5,e.display.scroller.clientWidth/Ei(e.display)-3);return function(n){if(rr(e.doc,n)){return 0}var a=0;if(n.widgets){for(var s=0;s=e.display.viewTo){return null}t-=e.display.viewFrom;if(t<0){return null}var r=e.display.view;for(var i=0;it)){n.updateLineNumbers=t}e.curOp.viewChanged=true;if(t>=n.viewTo){if(Mt&&er(e.doc,t)n.viewFrom){Vi(e)}else{n.viewFrom+=i;n.viewTo+=i}}else if(t<=n.viewFrom&&r>=n.viewTo){Vi(e)}else if(t<=n.viewFrom){var a=Fi(e,r,r+i,1);if(a){n.view=n.view.slice(a.index);n.viewFrom=a.lineN;n.viewTo+=i}else{Vi(e)}}else if(r>=n.viewTo){var s=Fi(e,t,t,-1);if(s){n.view=n.view.slice(0,s.index);n.viewTo=s.lineN}else{Vi(e)}}else{var o=Fi(e,t,t,-1);var l=Fi(e,r,r+i,1);if(o&&l){n.view=n.view.slice(0,o.index).concat(wr(e,o.lineN,l.lineN)).concat(n.view.slice(l.index));n.viewTo+=i}else{Vi(e)}}var u=n.externalMeasured;if(u){if(r=n.lineN&&t=i.viewTo){return}var a=i.view[Oi(e,t)];if(a.node==null){return}var s=a.changes||(a.changes=[]);if(z(s,r)==-1){s.push(r)}}function Vi(e){e.display.viewFrom=e.display.viewTo=e.doc.first;e.display.view=[];e.display.viewOffset=0}function Fi(e,t,r,i){var n=Oi(e,t),a,s=e.display.view;if(!Mt||r==e.doc.first+e.doc.size){return{index:n,lineN:r}}var o=e.display.viewFrom;for(var l=0;l0){if(n==s.length-1){return null}a=o+s[n].size-t;n++}else{a=o-t}t+=a;r+=a}while(er(e.doc,r)!=r){if(n==(i<0?0:s.length-1)){return null}r+=i*s[n-(i<0?1:0)].size;n+=i}return{index:n,lineN:r}}function Wi(e,t,r){var i=e.display,n=i.view;if(n.length==0||t>=i.viewTo||r<=i.viewFrom){i.view=wr(e,t,r);i.viewFrom=t}else{if(i.viewFrom>t){i.view=wr(e,t,i.viewFrom).concat(i.view)}else if(i.viewFromr){i.view=i.view.slice(0,Oi(e,r))}}i.viewTo=r}function zi(e){var t=e.display.view,r=0;for(var i=0;i=e.display.viewTo||o.to().line0){t.blinker=setInterval(function(){return t.cursorDiv.style.visibility=(r=!r)?"":"hidden"},e.options.cursorBlinkRate)}else if(e.options.cursorBlinkRate<0){t.cursorDiv.style.visibility="hidden"}}function Ki(e){if(!e.state.focused){e.display.input.focus();Xi(e)}}function $i(e){e.state.delayingBlurEvent=true;setTimeout(function(){if(e.state.delayingBlurEvent){e.state.delayingBlurEvent=false;Yi(e)}},100)}function Xi(e,t){if(e.state.delayingBlurEvent){e.state.delayingBlurEvent=false}if(e.options.readOnly=="nocursor"){return}if(!e.state.focused){ge(e,"focus",e,t);e.state.focused=true;I(e.display.wrapper,"CodeMirror-focused");if(!e.curOp&&e.display.selForContextMenu!=e.doc.sel){e.display.input.reset();if(l){setTimeout(function(){return e.display.input.reset(true)},20)}}e.display.input.receivedFocus()}qi(e)}function Yi(e,t){if(e.state.delayingBlurEvent){return}if(e.state.focused){ge(e,"blur",e,t);e.state.focused=false;L(e.display.wrapper,"CodeMirror-focused")}clearInterval(e.display.blinker);setTimeout(function(){if(!e.state.focused){e.display.shift=false}},150)}function Qi(e){var t=e.display;var r=t.lineDiv.offsetTop;for(var i=0;i.005||h<-.005){Je(n.line,l);Zi(n.line);if(n.rest){for(var p=0;pe.display.sizerWidth){var d=Math.ceil(u/Ei(e.display));if(d>e.display.maxLineLength){e.display.maxLineLength=d;e.display.maxLine=n.line;e.display.maxLineChanged=true}}}}function Zi(e){if(e.widgets){for(var t=0;t=s){a=tt(t,nr(Ye(t,l))-e.wrapper.clientHeight);s=l}}return{from:a,to:Math.max(s,a+1)}}function en(e,t){if(ye(e,"scrollCursorIntoView")){return}var r=e.display,i=r.sizer.getBoundingClientRect(),n=null;if(t.top+i.top<0){n=true}else if(t.bottom+i.top>(window.innerHeight||document.documentElement.clientHeight)){n=false}if(n!=null&&!d){var a=E("div","​",null,"position: absolute;\n top: "+(t.top-r.viewOffset-jr(e.display))+"px;\n height: "+(t.bottom-t.top+qr(e)+r.barHeight)+"px;\n left: "+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;");e.display.lineSpace.appendChild(a);a.scrollIntoView(n);e.display.lineSpace.removeChild(a)}}function tn(e,t,r,i){if(i==null){i=0}var n;if(!e.options.lineWrapping&&t==r){t=t.ch?nt(t.line,t.sticky=="before"?t.ch-1:t.ch,"after"):t;r=t.sticky=="before"?nt(t.line,t.ch+1,"before"):t}for(var a=0;a<5;a++){var s=false;var o=vi(e,t);var l=!r||r==t?o:vi(e,r);n={left:Math.min(o.left,l.left),top:Math.min(o.top,l.top)-i,right:Math.max(o.left,l.left),bottom:Math.max(o.bottom,l.bottom)+i};var u=nn(e,n);var c=e.doc.scrollTop,f=e.doc.scrollLeft;if(u.scrollTop!=null){fn(e,u.scrollTop);if(Math.abs(e.doc.scrollTop-c)>1){s=true}}if(u.scrollLeft!=null){pn(e,u.scrollLeft);if(Math.abs(e.doc.scrollLeft-f)>1){s=true}}if(!s){break}}return n}function rn(e,t){var r=nn(e,t);if(r.scrollTop!=null){fn(e,r.scrollTop)}if(r.scrollLeft!=null){pn(e,r.scrollLeft)}}function nn(e,t){var r=e.display,i=Ai(e.display);if(t.top<0){t.top=0}var n=e.curOp&&e.curOp.scrollTop!=null?e.curOp.scrollTop:r.scroller.scrollTop;var a=$r(e),s={};if(t.bottom-t.top>a){t.bottom=t.top+a}var o=e.doc.height+Ur(r);var l=t.topo-i;if(t.topn+a){var c=Math.min(t.top,(u?o:t.bottom)-a);if(c!=n){s.scrollTop=c}}var f=e.curOp&&e.curOp.scrollLeft!=null?e.curOp.scrollLeft:r.scroller.scrollLeft;var h=Kr(e)-(e.options.fixedGutter?r.gutters.offsetWidth:0);var p=t.right-t.left>h;if(p){t.right=t.left+h}if(t.left<10){s.scrollLeft=0}else if(t.lefth+f-3){s.scrollLeft=t.right+(p?0:10)-h}return s}function an(e,t){if(t==null){return}un(e);e.curOp.scrollTop=(e.curOp.scrollTop==null?e.doc.scrollTop:e.curOp.scrollTop)+t}function sn(e){un(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function on(e,t,r){if(t!=null||r!=null){un(e)}if(t!=null){e.curOp.scrollLeft=t}if(r!=null){e.curOp.scrollTop=r}}function ln(e,t){un(e);e.curOp.scrollToPos=t}function un(e){var t=e.curOp.scrollToPos;if(t){e.curOp.scrollToPos=null;var r=gi(e,t.from),i=gi(e,t.to);cn(e,r,i,t.margin)}}function cn(e,t,r,i){var n=nn(e,{left:Math.min(t.left,r.left),top:Math.min(t.top,r.top)-i,right:Math.max(t.right,r.right),bottom:Math.max(t.bottom,r.bottom)+i});on(e,n.scrollLeft,n.scrollTop)}function fn(e,t){if(Math.abs(e.doc.scrollTop-t)<2){return}if(!r){Hn(e,{top:t})}hn(e,t,true);if(r){Hn(e)}On(e,100)}function hn(e,t,r){t=Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t);if(e.display.scroller.scrollTop==t&&!r){return}e.doc.scrollTop=t;e.display.scrollbars.setScrollTop(t);if(e.display.scroller.scrollTop!=t){e.display.scroller.scrollTop=t}}function pn(e,t,r,i){t=Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth);if((r?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!i){return}e.doc.scrollLeft=t;qn(e);if(e.display.scroller.scrollLeft!=t){e.display.scroller.scrollLeft=t}e.display.scrollbars.setScrollLeft(t)}function dn(e){var t=e.display,r=t.gutters.offsetWidth;var i=Math.round(e.doc.height+Ur(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?r:0,docHeight:i,scrollHeight:i+qr(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:r}}var mn=function(e,t,r){this.cm=r;var i=this.vert=E("div",[E("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar");var n=this.horiz=E("div",[E("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");i.tabIndex=n.tabIndex=-1;e(i);e(n);de(i,"scroll",function(){if(i.clientHeight){t(i.scrollTop,"vertical")}});de(n,"scroll",function(){if(n.clientWidth){t(n.scrollLeft,"horizontal")}});this.checkedZeroWidth=false;if(s&&o<8){this.horiz.style.minHeight=this.vert.style.minWidth="18px"}};mn.prototype.update=function(e){var t=e.scrollWidth>e.clientWidth+1;var r=e.scrollHeight>e.clientHeight+1;var i=e.nativeBarWidth;if(r){this.vert.style.display="block";this.vert.style.bottom=t?i+"px":"0";var n=e.viewHeight-(t?i:0);this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+n)+"px"}else{this.vert.style.display="";this.vert.firstChild.style.height="0"}if(t){this.horiz.style.display="block";this.horiz.style.right=r?i+"px":"0";this.horiz.style.left=e.barLeft+"px";var a=e.viewWidth-e.barLeft-(r?i:0);this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+a)+"px"}else{this.horiz.style.display="";this.horiz.firstChild.style.width="0"}if(!this.checkedZeroWidth&&e.clientHeight>0){if(i==0){this.zeroWidthHack()}this.checkedZeroWidth=true}return{right:r?i:0,bottom:t?i:0}};mn.prototype.setScrollLeft=function(e){if(this.horiz.scrollLeft!=e){ +this.horiz.scrollLeft=e}if(this.disableHoriz){this.enableZeroWidthBar(this.horiz,this.disableHoriz,"horiz")}};mn.prototype.setScrollTop=function(e){if(this.vert.scrollTop!=e){this.vert.scrollTop=e}if(this.disableVert){this.enableZeroWidthBar(this.vert,this.disableVert,"vert")}};mn.prototype.zeroWidthHack=function(){var e=y&&!p?"12px":"18px";this.horiz.style.height=this.vert.style.width=e;this.horiz.style.pointerEvents=this.vert.style.pointerEvents="none";this.disableHoriz=new W;this.disableVert=new W};mn.prototype.enableZeroWidthBar=function(e,t,r){e.style.pointerEvents="auto";function i(){var n=e.getBoundingClientRect();var a=r=="vert"?document.elementFromPoint(n.right-1,(n.top+n.bottom)/2):document.elementFromPoint((n.right+n.left)/2,n.bottom-1);if(a!=e){e.style.pointerEvents="none"}else{t.set(1e3,i)}}t.set(1e3,i)};mn.prototype.clear=function(){var e=this.horiz.parentNode;e.removeChild(this.horiz);e.removeChild(this.vert)};var vn=function(){};vn.prototype.update=function(){return{bottom:0,right:0}};vn.prototype.setScrollLeft=function(){};vn.prototype.setScrollTop=function(){};vn.prototype.clear=function(){};function gn(e,t){if(!t){t=dn(e)}var r=e.display.barWidth,i=e.display.barHeight;yn(e,t);for(var n=0;n<4&&r!=e.display.barWidth||i!=e.display.barHeight;n++){if(r!=e.display.barWidth&&e.options.lineWrapping){Qi(e)}yn(e,dn(e));r=e.display.barWidth;i=e.display.barHeight}}function yn(e,t){var r=e.display;var i=r.scrollbars.update(t);r.sizer.style.paddingRight=(r.barWidth=i.right)+"px";r.sizer.style.paddingBottom=(r.barHeight=i.bottom)+"px";r.heightForcer.style.borderBottom=i.bottom+"px solid transparent";if(i.right&&i.bottom){r.scrollbarFiller.style.display="block";r.scrollbarFiller.style.height=i.bottom+"px";r.scrollbarFiller.style.width=i.right+"px"}else{r.scrollbarFiller.style.display=""}if(i.bottom&&e.options.coverGutterNextToScrollbar&&e.options.fixedGutter){r.gutterFiller.style.display="block";r.gutterFiller.style.height=i.bottom+"px";r.gutterFiller.style.width=t.gutterWidth+"px"}else{r.gutterFiller.style.display=""}}var xn={native:mn,null:vn};function bn(e){if(e.display.scrollbars){e.display.scrollbars.clear();if(e.display.scrollbars.addClass){L(e.display.wrapper,e.display.scrollbars.addClass)}}e.display.scrollbars=new xn[e.options.scrollbarStyle](function(t){e.display.wrapper.insertBefore(t,e.display.scrollbarFiller);de(t,"mousedown",function(){if(e.state.focused){setTimeout(function(){return e.display.input.focus()},0)}});t.setAttribute("cm-not-content","true")},function(t,r){if(r=="horizontal"){pn(e,t)}else{fn(e,t)}},e);if(e.display.scrollbars.addClass){I(e.display.wrapper,e.display.scrollbars.addClass)}}var wn=0;function kn(e){e.curOp={cm:e,viewChanged:false,startHeight:e.doc.height,forceUpdate:false,updateInput:0,typing:false,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:false,updateMaxLine:false,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:false,id:++wn};Cr(e.curOp)}function Cn(e){var t=e.curOp;if(t){Lr(t,function(e){for(var t=0;t=r.viewTo)||r.maxLineChanged&&t.options.lineWrapping;e.update=e.mustUpdate&&new Rn(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}function Tn(e){e.updatedDisplay=e.mustUpdate&&zn(e.cm,e.update)}function An(e){var t=e.cm,r=t.display;if(e.updatedDisplay){Qi(t)}e.barMeasure=dn(t);if(r.maxLineChanged&&!t.options.lineWrapping){e.adjustWidthTo=Zr(t,r.maxLine,r.maxLine.text.length).left+3;t.display.sizerWidth=e.adjustWidthTo;e.barMeasure.scrollWidth=Math.max(r.scroller.clientWidth,r.sizer.offsetLeft+e.adjustWidthTo+qr(t)+t.display.barWidth);e.maxScrollLeft=Math.max(0,r.sizer.offsetLeft+e.adjustWidthTo-Kr(t))}if(e.updatedDisplay||e.selectionChanged){e.preparedSelection=r.input.prepareSelection()}}function En(e){var t=e.cm;if(e.adjustWidthTo!=null){t.display.sizer.style.minWidth=e.adjustWidthTo+"px";if(e.maxScrollLeft=e.display.viewTo){return}var r=+new Date+e.options.workTime;var i=yt(e,t.highlightFrontier);var n=[];t.iter(i.line,Math.min(t.first+t.size,e.display.viewTo+500),function(a){if(i.line>=e.display.viewFrom){var s=a.styles;var o=a.text.length>e.options.maxHighlightLength?qe(t.mode,i.state):null;var l=vt(e,a,i,true);if(o){i.state=o}a.styles=l.styles;var u=a.styleClasses,c=l.classes;if(c){a.styleClasses=c}else if(u){a.styleClasses=null}var f=!s||s.length!=a.styles.length||u!=c&&(!u||!c||u.bgClass!=c.bgClass||u.textClass!=c.textClass);for(var h=0;!f&&hr){On(e,e.options.workDelay);return true}});t.highlightFrontier=i.line;t.modeFrontier=Math.max(t.modeFrontier,i.line);if(n.length){Nn(e,function(){for(var t=0;t=r.viewFrom&&t.visible.to<=r.viewTo&&(r.updateLineNumbers==null||r.updateLineNumbers>=r.viewTo)&&r.renderedView==r.view&&zi(e)==0){return false}if(Kn(e)){Vi(e);t.dims=Mi(e)}var n=i.first+i.size;var a=Math.max(t.visible.from-e.options.viewportMargin,i.first);var s=Math.min(n,t.visible.to+e.options.viewportMargin);if(r.viewFroms&&r.viewTo-s<20){s=Math.min(n,r.viewTo)}if(Mt){a=er(e.doc,a);s=tr(e.doc,s)}var o=a!=r.viewFrom||s!=r.viewTo||r.lastWrapHeight!=t.wrapperHeight||r.lastWrapWidth!=t.wrapperWidth;Wi(e,a,s);r.viewOffset=nr(Ye(e.doc,r.viewFrom));e.display.mover.style.top=r.viewOffset+"px";var l=zi(e);if(!o&&l==0&&!t.force&&r.renderedView==r.view&&(r.updateLineNumbers==null||r.updateLineNumbers>=r.viewTo)){return false}var u=Fn(e);if(l>4){r.lineDiv.style.display="none"}jn(e,r.updateLineNumbers,t.dims);if(l>4){r.lineDiv.style.display=""}r.renderedView=r.view;Wn(u);T(r.cursorDiv);T(r.selectionDiv);r.gutters.style.height=r.sizer.style.minHeight=0;if(o){r.lastWrapHeight=t.wrapperHeight;r.lastWrapWidth=t.wrapperWidth;On(e,400)}r.updateLineNumbers=null;return true}function Bn(e,t){var r=t.viewport;for(var i=true;;i=false){if(!i||!e.options.lineWrapping||t.oldDisplayWidth==Kr(e)){if(r&&r.top!=null){r={top:Math.min(e.doc.height+Ur(e.display)-$r(e),r.top)}}t.visible=Ji(e.display,e.doc,r);if(t.visible.from>=e.display.viewFrom&&t.visible.to<=e.display.viewTo){break}}if(!zn(e,t)){break}Qi(e);var n=dn(e);Bi(e);gn(e,n);Gn(e,n);t.force=false}t.signal(e,"update",e);if(e.display.viewFrom!=e.display.reportedViewFrom||e.display.viewTo!=e.display.reportedViewTo){t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo);e.display.reportedViewFrom=e.display.viewFrom;e.display.reportedViewTo=e.display.viewTo}}function Hn(e,t){var r=new Rn(e,t);if(zn(e,r)){Qi(e);Bn(e,r);var i=dn(e);Bi(e);gn(e,i);Gn(e,i);r.finish()}}function jn(e,t,r){var i=e.display,n=e.options.lineNumbers;var a=i.lineDiv,s=a.firstChild;function o(t){var r=t.nextSibling;if(l&&y&&e.display.currentWheelTarget==t){t.style.display="none"}else{t.parentNode.removeChild(t)}return r}var u=i.view,c=i.viewFrom;for(var f=0;f-1){d=false}Mr(e,h,c,r)}if(d){T(h.lineNumber);h.lineNumber.appendChild(document.createTextNode(it(e.options,c)))}s=h.node.nextSibling}c+=h.size}while(s){s=o(s)}}function Un(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px"}function Gn(e,t){e.display.sizer.style.minHeight=t.docHeight+"px";e.display.heightForcer.style.top=t.docHeight+"px";e.display.gutters.style.height=t.docHeight+e.display.barHeight+qr(e)+"px"}function qn(e){var t=e.display,r=t.view;if(!t.alignWidgets&&(!t.gutters.firstChild||!e.options.fixedGutter)){return}var i=Ni(t)-t.scroller.scrollLeft+e.doc.scrollLeft;var n=t.gutters.offsetWidth,a=i+"px";for(var s=0;so.clientWidth;var c=o.scrollHeight>o.clientHeight;if(!(n&&u||a&&c)){return}if(a&&y&&l){e:for(var h=t.target,p=s.view;h!=o;h=h.parentNode){for(var d=0;d=0&&at(e,n.to())<=0){return i}}return-1};var na=function(e,t){this.anchor=e;this.head=t};na.prototype.from=function(){return ut(this.anchor,this.head)};na.prototype.to=function(){return lt(this.anchor,this.head)};na.prototype.empty=function(){return this.head.line==this.anchor.line&&this.head.ch==this.anchor.ch};function aa(e,t,r){var i=e&&e.options.selectionsMayTouch;var n=t[r];t.sort(function(e,t){return at(e.from(),t.from())});r=z(t,n);for(var a=1;a0:l>=0){var u=ut(o.from(),s.from()),c=lt(o.to(),s.to());var f=o.empty()?s.from()==s.head:o.from()==o.head;if(a<=r){--r}t.splice(--a,2,new na(f?c:u,f?u:c))}}return new ia(t,r)}function sa(e,t){return new ia([new na(e,t||e)],0)}function oa(e){if(!e.text){return e.to}return nt(e.from.line+e.text.length-1,X(e.text).length+(e.text.length==1?e.from.ch:0))}function la(e,t){if(at(e,t.from)<0){return e}if(at(e,t.to)<=0){return oa(t)}var r=e.line+t.text.length-(t.to.line-t.from.line)-1,i=e.ch;if(e.line==t.to.line){i+=oa(t).ch-t.to.ch}return nt(r,i)}function ua(e,t){var r=[];for(var i=0;i1){e.remove(o.line+1,d-1)}e.insert(o.line+1,g)}Ar(e,"change",e,t)}function va(e,t,r){function i(e,n,a){if(e.linked){for(var s=0;s1&&!e.done[e.done.length-2].ranges){e.done.pop();return X(e.done)}}function Sa(e,t,r,i){var n=e.history;n.undone.length=0;var a=+new Date,s;var o;if((n.lastOp==i||n.lastOrigin==t.origin&&t.origin&&(t.origin.charAt(0)=="+"&&n.lastModTime>a-(e.cm?e.cm.options.historyEventDelay:500)||t.origin.charAt(0)=="*"))&&(s=Ca(n,n.lastOp==i))){o=X(s.changes);if(at(t.from,t.to)==0&&at(t.from,o.to)==0){o.to=oa(t)}else{s.changes.push(wa(e,t))}}else{var l=X(n.done);if(!l||!l.ranges){Aa(e.sel,n.done)}s={changes:[wa(e,t)],generation:n.generation};n.done.push(s);while(n.done.length>n.undoDepth){n.done.shift();if(!n.done[0].ranges){n.done.shift()}}}n.done.push(r);n.generation=++n.maxGeneration;n.lastModTime=n.lastSelTime=a;n.lastOp=n.lastSelOp=i;n.lastOrigin=n.lastSelOrigin=t.origin;if(!o){ge(e,"historyAdded")}}function La(e,t,r,i){var n=t.charAt(0);return n=="*"||n=="+"&&r.ranges.length==i.ranges.length&&r.somethingSelected()==i.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500)}function Ta(e,t,r,i){var n=e.history,a=i&&i.origin;if(r==n.lastSelOp||a&&n.lastSelOrigin==a&&(n.lastModTime==n.lastSelTime&&n.lastOrigin==a||La(e,a,X(n.done),t))){n.done[n.done.length-1]=t}else{Aa(t,n.done)}n.lastSelTime=+new Date;n.lastSelOrigin=a;n.lastSelOp=r;if(i&&i.clearRedo!==false){ka(n.undone)}}function Aa(e,t){var r=X(t);if(!(r&&r.ranges&&r.equals(e))){t.push(e)}}function Ea(e,t,r,i){var n=t["spans_"+e.id],a=0;e.iter(Math.max(e.first,r),Math.min(e.first+e.size,i),function(r){if(r.markedSpans){(n||(n=t["spans_"+e.id]={}))[a]=r.markedSpans}++a})}function Ma(e){if(!e){return null}var t;for(var r=0;r-1){X(o)[f]=u[f];delete u[f]}}}}}}return i}function Ia(e,t,r,i){if(i){var n=e.anchor;if(r){var a=at(t,n)<0;if(a!=at(r,n)<0){n=t;t=r}else if(a!=at(t,r)<0){t=r}}return new na(n,t)}else{return new na(r||t,t)}}function Oa(e,t,r,i,n){if(n==null){n=e.cm&&(e.cm.display.shift||e.extend)}za(e,new ia([Ia(e.sel.primary(),t,r,n)],0),i)}function Da(e,t,r){var i=[];var n=e.cm&&(e.cm.display.shift||e.extend);for(var a=0;a=t.ch:o.to>t.ch))){if(n){ge(l,"beforeCursorEnter");if(l.explicitlyCleared){if(!a.markedSpans){break}else{--s;continue}}}if(!l.atomic){continue}if(r){var f=l.find(i<0?1:-1),h=void 0;if(i<0?c:u){f=Ka(e,f,-i,f&&f.line==t.line?a:null)}if(f&&f.line==t.line&&(h=at(f,r))&&(i<0?h<0:h>0)){return Ga(e,f,t,i,n)}}var p=l.find(i<0?-1:1);if(i<0?u:c){p=Ka(e,p,i,p.line==t.line?a:null)}return p?Ga(e,p,t,i,n):null}}}return t}function qa(e,t,r,i,n){var a=i||1;var s=Ga(e,t,r,a,n)||!n&&Ga(e,t,r,a,true)||Ga(e,t,r,-a,n)||!n&&Ga(e,t,r,-a,true);if(!s){e.cantEdit=true;return nt(e.first,0)}return s}function Ka(e,t,r,i){if(r<0&&t.ch==0){if(t.line>e.first){return ft(e,nt(t.line-1))}else{return null}}else if(r>0&&t.ch==(i||Ye(e,t.line)).text.length){if(t.line=0;--n){Qa(e,{from:i[n].from,to:i[n].to,text:n?[""]:t.text,origin:t.origin})}}else{Qa(e,t)}}function Qa(e,t){if(t.text.length==1&&t.text[0]==""&&at(t.from,t.to)==0){return}var r=ua(e,t);Sa(e,t,r,e.cm?e.cm.curOp.id:NaN);es(e,t,r,Ft(e,t));var i=[];va(e,function(e,r){if(!r&&z(i,e.history)==-1){as(e.history,t);i.push(e.history)}es(e,t,null,Ft(e,t))})}function Za(e,t,r){var i=e.cm&&e.cm.state.suppressEdits;if(i&&!r){return}var n=e.history,a,s=e.sel;var o=t=="undo"?n.done:n.undone,l=t=="undo"?n.undone:n.done;var u=0;for(;u=0;--p){var d=h(p);if(d)return d.v}}function Ja(e,t){if(t==0){return}e.first+=t;e.sel=new ia(Y(e.sel.ranges,function(e){return new na(nt(e.anchor.line+t,e.anchor.ch),nt(e.head.line+t,e.head.ch))}),e.sel.primIndex);if(e.cm){Di(e.cm,e.first,e.first-t,t);for(var r=e.cm.display,i=r.viewFrom;ie.lastLine()){return}if(t.from.linea){t={from:t.from,to:nt(a,Ye(e,a).text.length),text:[t.text[0]],origin:t.origin}}t.removed=Qe(e,t.from,t.to);if(!r){r=ua(e,t)}if(e.cm){ts(e.cm,t,i)}else{ma(e,t,i)}Ba(e,r,j);if(e.cantEdit&&qa(e,nt(e.firstLine(),0))){e.cantEdit=false}}function ts(e,t,r){var i=e.doc,n=e.display,a=t.from,s=t.to;var o=false,l=a.line;if(!e.options.lineWrapping){l=et(Qt(Ye(i,a.line)));i.iter(l,s.line+1,function(e){if(e==n.maxLine){o=true;return true}})}if(i.sel.contains(t.from,t.to)>-1){xe(e)}ma(i,t,r,_i(e));if(!e.options.lineWrapping){i.iter(l,a.line+t.text.length,function(e){var t=ar(e);if(t>n.maxLineLength){n.maxLine=e;n.maxLineLength=t;n.maxLineChanged=true;o=false}});if(o){e.curOp.updateMaxLine=true}}At(i,a.line);On(e,400);var u=t.text.length-(s.line-a.line)-1;if(t.full){Di(e)}else if(a.line==s.line&&t.text.length==1&&!da(e.doc,t)){Ri(e,a.line,"text")}else{Di(e,a.line,s.line+1,u)}var c=be(e,"changes"),f=be(e,"change");if(f||c){var h={from:a,to:s,text:t.text,removed:t.removed,origin:t.origin};if(f){Ar(e,"change",e,h)}if(c){(e.curOp.changeObjs||(e.curOp.changeObjs=[])).push(h)}}e.display.selForContextMenu=null}function rs(e,t,r,i,n){var a;if(!i){i=r}if(at(i,r)<0){a=[i,r],r=a[0],i=a[1]}if(typeof t=="string"){t=e.splitLines(t)}Ya(e,{from:r,to:i,text:t,origin:n})}function is(e,t,r,i){if(r1||!(this.children[0]instanceof os))){var l=[];this.collapse(l);this.children=[new os(l)];this.children[0].parent=this}},collapse:function(e){var t=this;for(var r=0;r50){var o=a.lines.length%25+25;for(var l=o;l10);e.parent.maybeSpill()},iterN:function(e,t,r){var i=this;for(var n=0;nt.display.maxLineLength){t.display.maxLine=c;t.display.maxLineLength=f;t.display.maxLineChanged=true}}}if(n!=null&&t&&this.collapsed){Di(t,n,a+1)}this.lines.length=0;this.explicitlyCleared=true;if(this.atomic&&this.doc.cantEdit){this.doc.cantEdit=false;if(t){ja(t.doc)}}if(t){Ar(t,"markerCleared",t,this,n,a)}if(r){Cn(t)}if(this.parent){this.parent.clear()}};ps.prototype.find=function(e,t){var r=this;if(e==null&&this.type=="bookmark"){e=1}var i,n;for(var a=0;a0||s==0&&a.clearWhenEmpty!==false){return a}if(a.replacedWith){a.collapsed=true;a.widgetNode=M("span",[a.replacedWith],"CodeMirror-widget");if(!i.handleMouseEvents){a.widgetNode.setAttribute("cm-ignore-events","true")}if(i.insertLeft){a.widgetNode.insertLeft=true}}if(a.collapsed){if(Yt(e,t.line,t,r,a)||t.line!=r.line&&Yt(e,r.line,t,r,a)){throw new Error("Inserting collapsed marker partially overlapping an existing one")}_t()}if(a.addToHistory){Sa(e,{from:t,to:r,origin:"markText"},e.sel,NaN)}var o=t.line,l=e.cm,u;e.iter(o,r.line+1,function(e){if(l&&a.collapsed&&!l.options.lineWrapping&&Qt(e)==l.display.maxLine){u=true}if(a.collapsed&&o!=t.line){Je(e,0)}Dt(e,new Pt(a,o==t.line?t.ch:null,o==r.line?r.ch:null));++o});if(a.collapsed){e.iter(t.line,r.line+1,function(t){if(rr(e,t)){Je(t,0)}})}if(a.clearOnEnter){de(a,"beforeCursorEnter",function(){return a.clear()})}if(a.readOnly){Nt();if(e.history.done.length||e.history.undone.length){e.clearHistory()}}if(a.collapsed){a.id=++hs;a.atomic=true}if(l){if(u){l.curOp.updateMaxLine=true}if(a.collapsed){Di(l,t.line,r.line+1)}else if(a.className||a.startStyle||a.endStyle||a.css||a.attributes||a.title){for(var c=t.line;c<=r.line;c++){Ri(l,c,"text")}}if(a.atomic){ja(l.doc)}Ar(l,"markerAdded",l,a)}return a}var ms=function(e,t){var r=this;this.markers=e;this.primary=t;for(var i=0;i=0;u--){Ya(i,n[u])}if(l){Wa(this,l)}else if(this.cm){sn(this.cm)}}),undo:In(function(){Za(this,"undo")}),redo:In(function(){Za(this,"redo")}),undoSelection:In(function(){Za(this,"undo",true)}),redoSelection:In(function(){Za(this,"redo",true)}),setExtending:function(e){this.extend=e},getExtending:function(){return this.extend},historySize:function(){var e=this.history,t=0,r=0;for(var i=0;i=e.ch)){t.push(n.marker.parent||n.marker)}}}return t},findMarks:function(e,t,r){e=ft(this,e);t=ft(this,t);var i=[],n=e.line;this.iter(e.line,t.line+1,function(a){var s=a.markedSpans;if(s){for(var o=0;o=l.to||l.from==null&&n!=e.line||l.from!=null&&n==t.line&&l.from>=t.ch)&&(!r||r(l.marker))){i.push(l.marker.parent||l.marker)}}}++n});return i},getAllMarks:function(){var e=[];this.iter(function(t){var r=t.markedSpans;if(r){for(var i=0;ie){t=e;return true}e-=a;++r});return ft(this,nt(r,t))},indexFromPos:function(e){e=ft(this,e);var t=e.ch;if(e.linet){t=e.from}if(e.to!=null&&e.to-1){t.state.draggingText(e);setTimeout(function(){return t.display.input.focus()},20);return}try{var c=e.dataTransfer.getData("Text");if(c){var f;if(t.state.draggingText&&!t.state.draggingText.copy){f=t.listSelections()}Ba(t.doc,sa(r,r));if(f){for(var h=0;h=0;t--){rs(e.doc,"",i[t].from,i[t].to,"+delete")}sn(e)})}function Gs(e,t,r){var i=se(e.text,t+r,r);return i<0||i>e.text.length?null:i}function qs(e,t,r){var i=Gs(e,t.ch,r);return i==null?null:new nt(t.line,i,r<0?"after":"before")}function Ks(e,t,r,i,n){if(e){var a=he(r,t.doc.direction);if(a){var s=n<0?X(a):a[0];var o=n<0==(s.level==1);var l=o?"after":"before";var u;if(s.level>0||t.doc.direction=="rtl"){var c=ei(t,r);u=n<0?r.text.length-1:0;var f=ti(t,c,u).top;u=oe(function(e){return ti(t,c,e).top==f},n<0==(s.level==1)?s.from:s.to-1,u);if(l=="before"){u=Gs(r,u,1)}}else{u=n<0?s.to:s.from}return new nt(i,u,l)}}return new nt(i,n<0?r.text.length:0,n<0?"before":"after")}function $s(e,t,r,i){var n=he(t,e.doc.direction);if(!n){return qs(t,r,i)}if(r.ch>=t.text.length){r.ch=t.text.length;r.sticky="before"}else if(r.ch<=0){r.ch=0;r.sticky="after"}var a=ce(n,r.ch,r.sticky),s=n[a];if(e.doc.direction=="ltr"&&s.level%2==0&&(i>0?s.to>r.ch:s.from=s.from&&h>=c.begin:h<=s.to&&h<=c.end)){var p=f?"before":"after";return new nt(r.line,h,p)}}var d=function(e,t,i){var a=function(e,t){return t?new nt(r.line,o(e,1),"before"):new nt(r.line,e,"after")};for(;e>=0&&e0==(s.level!=1);var u=l?i.begin:o(i.end,-1);if(s.from<=u&&u0?c.end:o(c.begin,-1);if(v!=null&&!(i>0&&v==t.text.length)){m=d(i>0?0:n.length-1,i,u(v));if(m){return m}}return null}var Xs={selectAll:$a,singleSelection:function(e){return e.setSelection(e.getCursor("anchor"),e.getCursor("head"),j)},killLine:function(e){return Us(e,function(t){if(t.empty()){var r=Ye(e.doc,t.head.line).text.length;if(t.head.ch==r&&t.head.line0){n=new nt(n.line,n.ch+1);e.replaceRange(a.charAt(n.ch-1)+a.charAt(n.ch-2),nt(n.line,n.ch-2),n,"+transpose")}else if(n.line>e.doc.first){var s=Ye(e.doc,n.line-1).text;if(s){n=new nt(n.line,1);e.replaceRange(a.charAt(0)+e.doc.lineSeparator()+s.charAt(s.length-1),nt(n.line-1,s.length-1),n,"+transpose")}}}r.push(new na(n,n))}e.setSelections(r)})},newlineAndIndent:function(e){return Nn(e,function(){var t=e.listSelections();for(var r=t.length-1;r>=0;r--){e.replaceRange(e.doc.lineSeparator(),t[r].anchor,t[r].head,"+input")}t=e.listSelections();for(var i=0;ie&&at(t,this.pos)==0&&r==this.button};var po,mo;function vo(e,t){var r=+new Date;if(mo&&mo.compare(r,e,t)){po=mo=null;return"triple"}else if(po&&po.compare(r,e,t)){mo=new ho(r,e,t);po=null;return"double"}else{po=new ho(r,e,t);mo=null;return"single"}}function go(e){var t=this,r=t.display;if(ye(t,e)||r.activeTouch&&r.input.supportsTouch()){return}r.input.ensurePolled();r.shift=e.shiftKey;if(Hr(r,e)){if(!l){r.scroller.draggable=false;setTimeout(function(){return r.scroller.draggable=true},100)}return}if(To(t,e)){return}var i=Ii(t,e),n=Ae(e),a=i?vo(i,n):"single";window.focus();if(n==1&&t.state.selectingText){t.state.selectingText(e)}if(i&&yo(t,n,i,a,e)){return}if(n==1){if(i){bo(t,i,a,e)}else if(Te(e)==r.scroller){ke(e)}}else if(n==2){if(i){Oa(t.doc,i)}setTimeout(function(){return r.input.focus()},20)}else if(n==3){if(C){t.display.input.onContextMenu(e)}else{$i(t)}}}function yo(e,t,r,i,n){var a="Click";if(i=="double"){a="Double"+a}else if(i=="triple"){a="Triple"+a}a=(t==1?"Left":t==2?"Middle":"Right")+a;return ro(e,Bs(a,n),n,function(t){if(typeof t=="string"){t=Xs[t]}if(!t){return false}var i=false;try{if(e.isReadOnly()){e.state.suppressEdits=true}i=t(e,r)!=H}finally{e.state.suppressEdits=false}return i})}function xo(e,t,r){var i=e.getOption("configureMouse");var n=i?i(e,t,r):{};if(n.unit==null){var a=x?r.shiftKey&&r.metaKey:r.altKey;n.unit=a?"rectangle":t=="single"?"char":t=="double"?"word":"line"}if(n.extend==null||e.doc.extend){n.extend=e.doc.extend||r.shiftKey}if(n.addNew==null){n.addNew=y?r.metaKey:r.ctrlKey}if(n.moveOnDrag==null){n.moveOnDrag=!(y?r.altKey:r.ctrlKey)}return n}function bo(e,t,r,i){if(s){setTimeout(R(Ki,e),0)}else{e.curOp.focus=P()}var n=xo(e,r,i);var a=e.doc.sel,o;if(e.options.dragDrop&&Ee&&!e.isReadOnly()&&r=="single"&&(o=a.contains(t))>-1&&(at((o=a.ranges[o]).from(),t)<0||t.xRel>0)&&(at(o.to(),t)>0||t.xRel<0)){wo(e,i,t,n)}else{Co(e,i,t,n)}}function wo(e,t,r,i){var n=e.display,a=false;var u=_n(e,function(t){if(l){n.scroller.draggable=false}e.state.draggingText=false;ve(n.wrapper.ownerDocument,"mouseup",u);ve(n.wrapper.ownerDocument,"mousemove",c);ve(n.scroller,"dragstart",f);ve(n.scroller,"drop",u);if(!a){ke(t);if(!i.addNew){Oa(e.doc,r,null,null,i.extend)}if(l||s&&o==9){setTimeout(function(){n.wrapper.ownerDocument.body.focus();n.input.focus()},20)}else{n.input.focus()}}});var c=function(e){a=a||Math.abs(t.clientX-e.clientX)+Math.abs(t.clientY-e.clientY)>=10};var f=function(){return a=true};if(l){n.scroller.draggable=true}e.state.draggingText=u;u.copy=!i.moveOnDrag;if(n.scroller.dragDrop){n.scroller.dragDrop()}de(n.wrapper.ownerDocument,"mouseup",u);de(n.wrapper.ownerDocument,"mousemove",c);de(n.scroller,"dragstart",f);de(n.scroller,"drop",u);$i(e);setTimeout(function(){return n.input.focus()},20)}function ko(e,t,r){if(r=="char"){return new na(t,t)}if(r=="word"){return e.findWordAt(t)}if(r=="line"){return new na(nt(t.line,0),ft(e.doc,nt(t.line+1,0)))}var i=r(e,t);return new na(i.from,i.to)}function Co(e,t,r,i){var n=e.display,a=e.doc;ke(t);var s,o,l=a.sel,u=l.ranges;if(i.addNew&&!i.extend){o=a.sel.contains(r);if(o>-1){s=u[o]}else{s=new na(r,r)}}else{s=a.sel.primary();o=a.sel.primIndex}if(i.unit=="rectangle"){if(!i.addNew){s=new na(r,r)}r=Ii(e,t,true,true);o=-1}else{var c=ko(e,r,i.unit);if(i.extend){s=Ia(s,c.anchor,c.head,i.extend)}else{s=c}}if(!i.addNew){o=0;za(a,new ia([s],0),U);l=a.sel}else if(o==-1){o=u.length;za(a,aa(e,u.concat([s]),o),{scroll:false,origin:"*mouse"})}else if(u.length>1&&u[o].empty()&&i.unit=="char"&&!i.extend){za(a,aa(e,u.slice(0,o).concat(u.slice(o+1)),0),{scroll:false,origin:"*mouse"});l=a.sel}else{Ra(a,o,s,U)}var f=r;function h(t){if(at(f,t)==0){return}f=t;if(i.unit=="rectangle"){var n=[],u=e.options.tabSize;var c=F(Ye(a,r.line).text,r.ch,u);var h=F(Ye(a,t.line).text,t.ch,u);var p=Math.min(c,h),d=Math.max(c,h);for(var m=Math.min(r.line,t.line),v=Math.min(e.lastLine(),Math.max(r.line,t.line));m<=v;m++){var g=Ye(a,m).text,y=q(g,p,u);if(p==d){n.push(new na(nt(m,y),nt(m,y)))}else if(g.length>y){n.push(new na(nt(m,y),nt(m,q(g,d,u))))}}if(!n.length){n.push(new na(r,r))}za(a,aa(e,l.ranges.slice(0,o).concat(n),o),{origin:"*mouse",scroll:false});e.scrollIntoView(t)}else{var x=s;var b=ko(e,t,i.unit);var w=x.anchor,k;if(at(b.anchor,w)>0){k=b.head;w=ut(x.from(),b.anchor)}else{k=b.anchor;w=lt(x.to(),b.head)}var C=l.ranges.slice(0);C[o]=So(e,new na(ft(a,w),k));za(a,aa(e,C,o),U)}}var p=n.wrapper.getBoundingClientRect();var d=0;function m(t){var r=++d;var s=Ii(e,t,true,i.unit=="rectangle");if(!s){return}if(at(s,f)!=0){e.curOp.focus=P();h(s);var o=Ji(n,a);if(s.line>=o.to||s.linep.bottom?20:0;if(l){setTimeout(_n(e,function(){if(d!=r){return}n.scroller.scrollTop+=l;m(t)}),50)}}}function v(t){e.state.selectingText=false;d=Infinity;if(t){ke(t);n.input.focus()}ve(n.wrapper.ownerDocument,"mousemove",g);ve(n.wrapper.ownerDocument,"mouseup",y);a.history.lastSelOrigin=null}var g=_n(e,function(e){if(e.buttons===0||!Ae(e)){v(e)}else{m(e)}});var y=_n(e,v);e.state.selectingText=y;de(n.wrapper.ownerDocument,"mousemove",g);de(n.wrapper.ownerDocument,"mouseup",y)}function So(e,t){var r=t.anchor;var i=t.head;var n=Ye(e.doc,r.line);if(at(r,i)==0&&r.sticky==i.sticky){return t}var a=he(n);if(!a){return t}var s=ce(a,r.ch,r.sticky),o=a[s];if(o.from!=r.ch&&o.to!=r.ch){return t}var l=s+(o.from==r.ch==(o.level!=1)?0:1);if(l==0||l==a.length){return t}var u;if(i.line!=r.line){u=(i.line-r.line)*(e.doc.direction=="ltr"?1:-1)>0}else{var c=ce(a,i.ch,i.sticky);var f=c-s||(i.ch-r.ch)*(o.level==1?-1:1);if(c==l-1||c==l){u=f<0}else{u=f>0}}var h=a[l+(u?-1:0)];var p=u==(h.level==1);var d=p?h.from:h.to,m=p?"after":"before";return r.ch==d&&r.sticky==m?t:new na(new nt(r.line,d,m),i)}function Lo(e,t,r,i){var n,a;if(t.touches){n=t.touches[0].clientX;a=t.touches[0].clientY}else{try{n=t.clientX;a=t.clientY}catch(t){return false}}if(n>=Math.floor(e.display.gutters.getBoundingClientRect().right)){return false}if(i){ke(t)}var s=e.display;var o=s.lineDiv.getBoundingClientRect();if(a>o.bottom||!be(e,r)){return Se(t)}a-=o.top-s.viewOffset;for(var l=0;l=n){var c=tt(e.doc,a);var f=e.display.gutterSpecs[l];ge(e,r,e,c,f.className,t);return Se(t)}}}function To(e,t){return Lo(e,t,"gutterClick",true)}function Ao(e,t){if(Hr(e.display,t)||Eo(e,t)){return}if(ye(e,t,"contextmenu")){return}if(!C){e.display.input.onContextMenu(t)}}function Eo(e,t){if(!be(e,"gutterContextMenu")){return false}return Lo(e,t,"gutterContextMenu",false)}function Mo(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-");ui(e)}var No={toString:function(){return"CodeMirror.Init"}};var _o={};var Po={};function Io(e){var t=e.optionHandlers;function r(r,i,n,a){e.defaults[r]=i;if(n){t[r]=a?function(e,t,r){if(r!=No){n(e,t,r)}}:n}}e.defineOption=r;e.Init=No;r("value","",function(e,t){return e.setValue(t)},true);r("mode",null,function(e,t){e.doc.modeOption=t;ha(e)},true);r("indentUnit",2,ha,true);r("indentWithTabs",false);r("smartIndent",true);r("tabSize",4,function(e){pa(e);ui(e);Di(e)},true);r("lineSeparator",null,function(e,t){e.doc.lineSep=t;if(!t){return}var r=[],i=e.doc.first;e.doc.iter(function(e){for(var n=0;;){var a=e.text.indexOf(t,n);if(a==-1){break}n=a+t.length;r.push(nt(i,a))}i++});for(var n=r.length-1;n>=0;n--){rs(e.doc,t,r[n],nt(r[n].line,r[n].ch+t.length))}});r("specialChars",/[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g,function(e,t,r){e.state.specialChars=new RegExp(t.source+(t.test("\t")?"":"|\t"),"g");if(r!=No){e.refresh()}});r("specialCharPlaceholder",dr,function(e){return e.refresh()},true);r("electricChars",true);r("inputStyle",g?"contenteditable":"textarea",function(){throw new Error("inputStyle can not (yet) be changed in a running editor")},true);r("spellcheck",false,function(e,t){return e.getInputField().spellcheck=t},true);r("autocorrect",false,function(e,t){return e.getInputField().autocorrect=t},true);r("autocapitalize",false,function(e,t){return e.getInputField().autocapitalize=t},true);r("rtlMoveVisually",!b);r("wholeLineUpdateBefore",true);r("theme","default",function(e){Mo(e);Yn(e)},true);r("keyMap","default",function(e,t,r){var i=js(t);var n=r!=No&&js(r);if(n&&n.detach){n.detach(e,i)}if(i.attach){i.attach(e,n||null)}});r("extraKeys",null);r("configureMouse",null);r("lineWrapping",false,Do,true);r("gutters",[],function(e,t){e.display.gutterSpecs=$n(t,e.options.lineNumbers);Yn(e)},true);r("fixedGutter",true,function(e,t){e.display.gutters.style.left=t?Ni(e.display)+"px":"0";e.refresh()},true);r("coverGutterNextToScrollbar",false,function(e){return gn(e)},true);r("scrollbarStyle","native",function(e){bn(e);gn(e);e.display.scrollbars.setScrollTop(e.doc.scrollTop);e.display.scrollbars.setScrollLeft(e.doc.scrollLeft)},true);r("lineNumbers",false,function(e,t){e.display.gutterSpecs=$n(e.options.gutters,t);Yn(e)},true);r("firstLineNumber",1,Yn,true);r("lineNumberFormatter",function(e){return e},Yn,true);r("showCursorWhenSelecting",false,Bi,true);r("resetSelectionOnContextMenu",true);r("lineWiseCopyCut",true);r("pasteLinesPerSelection",true);r("selectionsMayTouch",false);r("readOnly",false,function(e,t){if(t=="nocursor"){Yi(e);e.display.input.blur()}e.display.input.readOnlyChanged(t)});r("disableInput",false,function(e,t){if(!t){e.display.input.reset()}},true);r("dragDrop",true,Oo);r("allowDropFileTypes",null);r("cursorBlinkRate",530);r("cursorScrollMargin",0);r("cursorHeight",1,Bi,true);r("singleCursorHeightPerLine",true,Bi,true);r("workTime",100);r("workDelay",100);r("flattenSpans",true,pa,true);r("addModeClass",false,pa,true);r("pollInterval",100);r("undoDepth",200,function(e,t){return e.doc.history.undoDepth=t});r("historyEventDelay",1250);r("viewportMargin",10,function(e){return e.refresh()},true);r("maxHighlightLength",1e4,pa,true);r("moveInputWithCursor",true,function(e,t){if(!t){e.display.input.resetPosition()}});r("tabindex",null,function(e,t){return e.display.input.getField().tabIndex=t||""});r("autofocus",null);r("direction","ltr",function(e,t){return e.doc.setDirection(t)},true);r("phrases",null)}function Oo(e,t,r){var i=r&&r!=No;if(!t!=!i){var n=e.display.dragFunctions;var a=t?de:ve;a(e.display.scroller,"dragstart",n.start);a(e.display.scroller,"dragenter",n.enter);a(e.display.scroller,"dragover",n.over);a(e.display.scroller,"dragleave",n.leave);a(e.display.scroller,"drop",n.drop)}}function Do(e){if(e.options.lineWrapping){I(e.display.wrapper,"CodeMirror-wrap");e.display.sizer.style.minWidth="";e.display.sizerWidth=null}else{L(e.display.wrapper,"CodeMirror-wrap");sr(e)}Pi(e);Di(e);ui(e);setTimeout(function(){return gn(e)},100)}function Ro(e,t){var r=this;if(!(this instanceof Ro)){return new Ro(e,t)}this.options=t=t?V(t):{};V(_o,t,false);var i=t.value;if(typeof i=="string"){i=new ws(i,t.mode,null,t.lineSeparator,t.direction)}else if(t.mode){i.modeOption=t.mode}this.doc=i;var n=new Ro.inputStyles[t.inputStyle](this);var a=this.display=new Qn(e,i,n,t);a.wrapper.CodeMirror=this;Mo(this);if(t.lineWrapping){this.display.wrapper.className+=" CodeMirror-wrap"}bn(this);this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:false,delayingBlurEvent:false,focused:false,suppressEdits:false,pasteIncoming:-1,cutIncoming:-1,selectingText:false,draggingText:false,highlight:new W,keySeq:null,specialChars:null};if(t.autofocus&&!g){a.input.focus()}if(s&&o<11){setTimeout(function(){return r.display.input.reset(true)},20)}Vo(this);Ms();kn(this);this.curOp.forceUpdate=true;ga(this,i);if(t.autofocus&&!g||this.hasFocus()){setTimeout(R(Xi,this),20)}else{Yi(this)}for(var u in Po){if(Po.hasOwnProperty(u)){Po[u](r,t[u],No)}}Kn(this);if(t.finishInit){t.finishInit(this)}for(var c=0;c20*20}de(t.scroller,"touchstart",function(n){if(!ye(e,n)&&!a(n)&&!To(e,n)){t.input.ensurePolled();clearTimeout(r);var s=+new Date;t.activeTouch={start:s,moved:false,prev:s-i.end<=300?i:null};if(n.touches.length==1){t.activeTouch.left=n.touches[0].pageX;t.activeTouch.top=n.touches[0].pageY}}});de(t.scroller,"touchmove",function(){if(t.activeTouch){t.activeTouch.moved=true}});de(t.scroller,"touchend",function(r){var i=t.activeTouch;if(i&&!Hr(t,r)&&i.left!=null&&!i.moved&&new Date-i.start<300){var a=e.coordsChar(t.activeTouch,"page"),s;if(!i.prev||l(i,i.prev)){s=new na(a,a)}else if(!i.prev.prev||l(i,i.prev.prev)){s=e.findWordAt(a)}else{s=new na(nt(a.line,0),ft(e.doc,nt(a.line+1,0)))}e.setSelection(s.anchor,s.head);e.focus();ke(r)}n()});de(t.scroller,"touchcancel",n);de(t.scroller,"scroll",function(){if(t.scroller.clientHeight){fn(e,t.scroller.scrollTop);pn(e,t.scroller.scrollLeft,true);ge(e,"scroll",e)}});de(t.scroller,"mousewheel",function(t){return ra(e,t)});de(t.scroller,"DOMMouseScroll",function(t){return ra(e,t)});de(t.wrapper,"scroll",function(){return t.wrapper.scrollTop=t.wrapper.scrollLeft=0});t.dragFunctions={enter:function(t){if(!ye(e,t)){Le(t)}},over:function(t){if(!ye(e,t)){Ls(e,t);Le(t)}},start:function(t){return Ss(e,t)},drop:_n(e,Cs),leave:function(t){if(!ye(e,t)){Ts(e)}}};var u=t.input.getField();de(u,"keyup",function(t){return uo.call(e,t)});de(u,"keydown",_n(e,oo));de(u,"keypress",_n(e,co));de(u,"focus",function(t){return Xi(e,t)});de(u,"blur",function(t){return Yi(e,t)})}var Fo=[];Ro.defineInitHook=function(e){return Fo.push(e)};function Wo(e,t,r,i){var n=e.doc,a;if(r==null){r="add"}if(r=="smart"){if(!n.mode.indent){r="prev"}else{a=yt(e,t).state}}var s=e.options.tabSize;var o=Ye(n,t),l=F(o.text,null,s);if(o.stateAfter){o.stateAfter=null}var u=o.text.match(/^\s*/)[0],c;if(!i&&!/\S/.test(o.text)){c=0;r="not"}else if(r=="smart"){c=n.mode.indent(a,o.text.slice(u.length),o.text);if(c==H||c>150){if(!i){return}r="prev"}}if(r=="prev"){if(t>n.first){c=F(Ye(n,t-1).text,null,s)}else{c=0}}else if(r=="add"){c=l+e.options.indentUnit}else if(r=="subtract"){c=l-e.options.indentUnit}else if(typeof r=="number"){c=l+r}c=Math.max(0,c);var f="",h=0;if(e.options.indentWithTabs){for(var p=Math.floor(c/s);p;--p){h+=s;f+="\t"}}if(hs;var l=Ie(t),u=null;if(o&&i.ranges.length>1){if(zo&&zo.text.join("\n")==t){if(i.ranges.length%zo.text.length==0){u=[];for(var c=0;c=0;h--){var p=i.ranges[h];var d=p.from(),m=p.to();if(p.empty()){if(r&&r>0){d=nt(d.line,d.ch-r)}else if(e.state.overwrite&&!o){m=nt(m.line,Math.min(Ye(a,m.line).text.length,m.ch+X(l).length))}else if(o&&zo&&zo.lineWise&&zo.text.join("\n")==t){d=m=nt(d.line,0)}}var v={from:d,to:m,text:u?u[h%u.length]:l,origin:n||(o?"paste":e.state.cutIncoming>s?"cut":"+input")};Ya(e.doc,v);Ar(e,"inputRead",e,v)}if(t&&!o){Uo(e,t)}sn(e);if(e.curOp.updateInput<2){e.curOp.updateInput=f}e.curOp.typing=true;e.state.pasteIncoming=e.state.cutIncoming=-1}function jo(e,t){var r=e.clipboardData&&e.clipboardData.getData("Text");if(r){e.preventDefault();if(!t.isReadOnly()&&!t.options.disableInput){Nn(t,function(){return Ho(t,r,0,null,"paste")})}return true}}function Uo(e,t){if(!e.options.electricChars||!e.options.smartIndent){return}var r=e.doc.sel;for(var i=r.ranges.length-1;i>=0;i--){var n=r.ranges[i];if(n.head.ch>100||i&&r.ranges[i-1].head.line==n.head.line){continue}var a=e.getModeAt(n.head);var s=false;if(a.electricChars){for(var o=0;o-1){s=Wo(e,n.head.line,"smart");break}}}else if(a.electricInput){if(a.electricInput.test(Ye(e.doc,n.head.line).text.slice(0,n.head.ch))){s=Wo(e,n.head.line,"smart")}}if(s){Ar(e,"electricInput",e,n.head.line)}}}function Go(e){var t=[],r=[];for(var i=0;i0){Ra(t.doc,n,new na(s,c[n].to()),j)}}else if(a.head.line>i){Wo(t,a.head.line,e,true);i=a.head.line;if(n==t.doc.sel.primIndex){sn(t)}}}}),getTokenAt:function(e,t){return Ct(this,e,t)},getLineTokens:function(e,t){return Ct(this,nt(e),t,true)},getTokenTypeAt:function(e){e=ft(this.doc,e);var t=gt(this,Ye(this.doc,e.line));var r=0,i=(t.length-1)/2,n=e.ch;var a;if(n==0){a=t[2]}else{for(;;){var s=r+i>>1;if((s?t[s*2-1]:0)>=n){i=s}else if(t[s*2+1]a){e=a;i=true}n=Ye(this.doc,e)}else{n=e}return pi(this,n,{top:0,left:0},t||"page",r||i).top+(i?this.doc.height-nr(n):0)},defaultTextHeight:function(){return Ai(this.display)},defaultCharWidth:function(){return Ei(this.display)},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(e,t,r,i,n){var a=this.display;e=vi(this,ft(this.doc,e));var s=e.bottom,o=e.left;t.style.position="absolute";t.setAttribute("cm-ignore-events","true");this.display.input.setUneditable(t);a.sizer.appendChild(t);if(i=="over"){s=e.top}else if(i=="above"||i=="near"){var l=Math.max(a.wrapper.clientHeight,this.doc.height),u=Math.max(a.sizer.clientWidth,a.lineSpace.clientWidth);if((i=="above"||e.bottom+t.offsetHeight>l)&&e.top>t.offsetHeight){s=e.top-t.offsetHeight}else if(e.bottom+t.offsetHeight<=l){s=e.bottom}if(o+t.offsetWidth>u){o=u-t.offsetWidth}}t.style.top=s+"px";t.style.left=t.style.right="";if(n=="right"){o=a.sizer.clientWidth-t.offsetWidth;t.style.right="0px"}else{if(n=="left"){o=0}else if(n=="middle"){o=(a.sizer.clientWidth-t.offsetWidth)/2}t.style.left=o+"px"}if(r){rn(this,{left:o,top:s,right:o+t.offsetWidth,bottom:s+t.offsetHeight})}},triggerOnKeyDown:Pn(oo),triggerOnKeyPress:Pn(co),triggerOnKeyUp:uo,triggerOnMouseDown:Pn(go),execCommand:function(e){if(Xs.hasOwnProperty(e)){return Xs[e].call(null,this)}},triggerElectric:Pn(function(e){Uo(this,e)}),findPosH:function(e,t,r,i){var n=this;var a=1;if(t<0){a=-1;t=-t}var s=ft(this.doc,e);for(var o=0;o0&&o(r.charAt(i-1))){--i}while(n.5){Pi(this)}ge(this,"refresh",this)}),swapDoc:Pn(function(e){var t=this.doc;t.cm=null;if(this.state.selectingText){this.state.selectingText()}ga(this,e);ui(this);this.display.input.reset();on(this,e.scrollLeft,e.scrollTop);this.curOp.forceScroll=true;Ar(this,"swapDoc",this,t);return t}),phrase:function(e){var t=this.options.phrases;return t&&Object.prototype.hasOwnProperty.call(t,e)?t[e]:e},getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}};we(e);e.registerHelper=function(t,i,n){if(!r.hasOwnProperty(t)){r[t]=e[t]={_global:[]}}r[t][i]=n};e.registerGlobalHelper=function(t,i,n,a){e.registerHelper(t,i,a);r[t]._global.push({pred:n,val:a})}}function Xo(e,t,r,i,n){var a=t;var s=r;var o=Ye(e,t.line);function l(){var i=t.line+r;if(i=e.first+e.size){return false}t=new nt(i,t.ch,t.sticky);return o=Ye(e,i)}function u(i){var a;if(n){a=$s(e.cm,o,t,r)}else{a=qs(o,t,r)}if(a==null){if(!i&&l()){t=Ks(n,e.cm,o,t.line,r)}else{return false}}else{t=a}return true}if(i=="char"){u()}else if(i=="column"){u(true)}else if(i=="word"||i=="group"){var c=null,f=i=="group";var h=e.cm&&e.cm.getHelper(t,"wordChars");for(var p=true;;p=false){if(r<0&&!u(!p)){break}var d=o.text.charAt(t.ch)||"\n";var m=re(d,h)?"w":f&&d=="\n"?"n":!f||/\s/.test(d)?null:"p";if(f&&!p&&!m){m="s"}if(c&&c!=m){if(r<0){r=1;u();t.sticky="after"}break}if(m){c=m}if(r>0&&!u(!p)){break}}}var v=qa(e,t,a,s,true);if(st(a,v)){v.hitSide=true}return v}function Yo(e,t,r,i){var n=e.doc,a=t.left,s;if(i=="page"){var o=Math.min(e.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight);var l=Math.max(o-.5*Ai(e.display),3);s=(r>0?t.bottom:t.top)+r*l}else if(i=="line"){s=r>0?t.bottom+3:t.top-3}var u;for(;;){u=xi(e,a,s);if(!u.outside){break}if(r<0?s<=0:s>=n.height){u.hitSide=true;break}s+=r*5}return u}var Qo=function(e){this.cm=e;this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null;this.polling=new W;this.composing=null;this.gracePeriod=false;this.readDOMTimeout=null};Qo.prototype.init=function(e){var t=this;var r=this,i=r.cm;var n=r.div=e.lineDiv;qo(n,i.options.spellcheck,i.options.autocorrect,i.options.autocapitalize);de(n,"paste",function(e){if(ye(i,e)||jo(e,i)){return}if(o<=11){setTimeout(_n(i,function(){return t.updateFromDOM()}),20)}});de(n,"compositionstart",function(e){t.composing={data:e.data,done:false}});de(n,"compositionupdate",function(e){if(!t.composing){t.composing={data:e.data,done:false}}});de(n,"compositionend",function(e){if(t.composing){if(e.data!=t.composing.data){t.readFromDOMSoon()}t.composing.done=true}});de(n,"touchstart",function(){return r.forceCompositionEnd()});de(n,"input",function(){if(!t.composing){t.readFromDOMSoon()}});function a(e){if(ye(i,e)){return}if(i.somethingSelected()){Bo({lineWise:false,text:i.getSelections()});if(e.type=="cut"){i.replaceSelection("",null,"cut")}}else if(!i.options.lineWiseCopyCut){return}else{var t=Go(i);Bo({lineWise:true,text:t.text});if(e.type=="cut"){i.operation(function(){i.setSelections(t.ranges,0,j) +;i.replaceSelection("",null,"cut")})}}if(e.clipboardData){e.clipboardData.clearData();var a=zo.text.join("\n");e.clipboardData.setData("Text",a);if(e.clipboardData.getData("Text")==a){e.preventDefault();return}}var s=Ko(),o=s.firstChild;i.display.lineSpace.insertBefore(s,i.display.lineSpace.firstChild);o.value=zo.text.join("\n");var l=document.activeElement;D(o);setTimeout(function(){i.display.lineSpace.removeChild(s);l.focus();if(l==n){r.showPrimarySelection()}},50)}de(n,"copy",a);de(n,"cut",a)};Qo.prototype.prepareSelection=function(){var e=Hi(this.cm,false);e.focus=this.cm.state.focused;return e};Qo.prototype.showSelection=function(e,t){if(!e||!this.cm.display.view.length){return}if(e.focus||t){this.showPrimarySelection()}this.showMultipleSelections(e)};Qo.prototype.getSelection=function(){return this.cm.display.wrapper.ownerDocument.getSelection()};Qo.prototype.showPrimarySelection=function(){var e=this.getSelection(),t=this.cm,i=t.doc.sel.primary();var n=i.from(),a=i.to();if(t.display.viewTo==t.display.viewFrom||n.line>=t.display.viewTo||a.line=t.display.viewFrom&&Zo(t,n)||{node:l[0].measure.map[2],offset:0};var c=a.linee.firstLine()){i=nt(i.line-1,Ye(e.doc,i.line-1).length)}if(n.ch==Ye(e.doc,n.line).text.length&&n.linet.viewTo-1){return false}var a,s,o;if(i.line==t.viewFrom||(a=Oi(e,i.line))==0){s=et(t.view[0].line);o=t.view[0].node}else{s=et(t.view[a].line);o=t.view[a-1].node.nextSibling}var l=Oi(e,n.line);var u,c;if(l==t.view.length-1){u=t.viewTo-1;c=t.lineDiv.lastChild}else{u=et(t.view[l+1].line)-1;c=t.view[l+1].node.previousSibling}if(!o){return false}var f=e.doc.splitLines(tl(e,o,c,s,u));var h=Qe(e.doc,nt(s,0),nt(u,Ye(e.doc,u).text.length));while(f.length>1&&h.length>1){if(X(f)==X(h)){f.pop();h.pop();u--}else if(f[0]==h[0]){f.shift();h.shift();s++}else{break}}var p=0,d=0;var m=f[0],v=h[0],g=Math.min(m.length,v.length);while(pi.ch&&y.charCodeAt(y.length-d-1)==x.charCodeAt(x.length-d-1)){p--;d++}}f[f.length-1]=y.slice(0,y.length-d).replace(/^\u200b+/,"");f[0]=f[0].slice(p).replace(/\u200b+$/,"");var w=nt(s,p);var k=nt(u,h.length?X(h).length-d:0);if(f.length>1||f[0]||at(w,k)){rs(e.doc,f,w,k,"+input");return true}};Qo.prototype.ensurePolled=function(){this.forceCompositionEnd()};Qo.prototype.reset=function(){this.forceCompositionEnd()};Qo.prototype.forceCompositionEnd=function(){if(!this.composing){return}clearTimeout(this.readDOMTimeout);this.composing=null;this.updateFromDOM();this.div.blur();this.div.focus()};Qo.prototype.readFromDOMSoon=function(){var e=this;if(this.readDOMTimeout!=null){return}this.readDOMTimeout=setTimeout(function(){e.readDOMTimeout=null;if(e.composing){if(e.composing.done){e.composing=null}else{return}}e.updateFromDOM()},80)};Qo.prototype.updateFromDOM=function(){var e=this;if(this.cm.isReadOnly()||!this.pollContent()){Nn(this.cm,function(){return Di(e.cm)})}};Qo.prototype.setUneditable=function(e){e.contentEditable="false"};Qo.prototype.onKeyPress=function(e){if(e.charCode==0||this.composing){return}e.preventDefault();if(!this.cm.isReadOnly()){_n(this.cm,Ho)(this.cm,String.fromCharCode(e.charCode==null?e.keyCode:e.charCode),0)}};Qo.prototype.readOnlyChanged=function(e){this.div.contentEditable=String(e!="nocursor")};Qo.prototype.onContextMenu=function(){};Qo.prototype.resetPosition=function(){};Qo.prototype.needsContentAttribute=true;function Zo(e,t){var r=Jr(e,t.line);if(!r||r.hidden){return null}var i=Ye(e.doc,t.line);var n=Yr(r,i,t.line);var a=he(i,e.doc.direction),s="left";if(a){var o=ce(a,t.ch);s=o%2?"right":"left"}var l=ii(n.map,t.ch,s);l.offset=l.collapse=="right"?l.end:l.start;return l}function Jo(e){for(var t=e;t;t=t.parentNode){if(/CodeMirror-gutter-wrapper/.test(t.className)){return true}}return false}function el(e,t){if(t){e.bad=true}return e}function tl(e,t,r,i,n){var a="",s=false,o=e.doc.lineSeparator(),l=false;function u(e){return function(t){return t.id==e}}function c(){if(s){a+=o;if(l){a+=o}s=l=false}}function f(e){if(e){c();a+=e}}function h(t){if(t.nodeType==1){var r=t.getAttribute("cm-text");if(r){f(r);return}var a=t.getAttribute("cm-marker"),p;if(a){var d=e.findMarks(nt(i,0),nt(n+1,0),u(+a));if(d.length&&(p=d[0].find(0))){f(Qe(e.doc,p.from,p.to).join(o))}return}if(t.getAttribute("contenteditable")=="false"){return}var m=/^(pre|div|p|li|table|br)$/i.test(t.nodeName);if(!/^br$/i.test(t.nodeName)&&t.textContent.length==0){return}if(m){c()}for(var v=0;v=9&&t.hasSelection){t.hasSelection=null}r.poll()});de(n,"paste",function(e){if(ye(i,e)||jo(e,i)){return}i.state.pasteIncoming=+new Date;r.fastPoll()});function a(e){if(ye(i,e)){return}if(i.somethingSelected()){Bo({lineWise:false,text:i.getSelections()})}else if(!i.options.lineWiseCopyCut){return}else{var t=Go(i);Bo({lineWise:true,text:t.text});if(e.type=="cut"){i.setSelections(t.ranges,null,j)}else{r.prevInput="";n.value=t.text.join("\n");D(n)}}if(e.type=="cut"){i.state.cutIncoming=+new Date}}de(n,"cut",a);de(n,"copy",a);de(e.scroller,"paste",function(t){if(Hr(e,t)||ye(i,t)){return}if(!n.dispatchEvent){i.state.pasteIncoming=+new Date;r.focus();return}var a=new Event("paste");a.clipboardData=t.clipboardData;n.dispatchEvent(a)});de(e.lineSpace,"selectstart",function(t){if(!Hr(e,t)){ke(t)}});de(n,"compositionstart",function(){var e=i.getCursor("from");if(r.composing){r.composing.range.clear()}r.composing={start:e,range:i.markText(e,i.getCursor("to"),{className:"CodeMirror-composing"})}});de(n,"compositionend",function(){if(r.composing){r.poll();r.composing.range.clear();r.composing=null}})};nl.prototype.createField=function(e){this.wrapper=Ko();this.textarea=this.wrapper.firstChild};nl.prototype.prepareSelection=function(){var e=this.cm,t=e.display,r=e.doc;var i=Hi(e);if(e.options.moveInputWithCursor){var n=vi(e,r.sel.primary().head,"div");var a=t.wrapper.getBoundingClientRect(),s=t.lineDiv.getBoundingClientRect();i.teTop=Math.max(0,Math.min(t.wrapper.clientHeight-10,n.top+s.top-a.top));i.teLeft=Math.max(0,Math.min(t.wrapper.clientWidth-10,n.left+s.left-a.left))}return i};nl.prototype.showSelection=function(e){var t=this.cm,r=t.display;A(r.cursorDiv,e.cursors);A(r.selectionDiv,e.selection);if(e.teTop!=null){this.wrapper.style.top=e.teTop+"px";this.wrapper.style.left=e.teLeft+"px"}};nl.prototype.reset=function(e){if(this.contextMenuPending||this.composing){return}var t=this.cm;if(t.somethingSelected()){this.prevInput="";var r=t.getSelection();this.textarea.value=r;if(t.state.focused){D(this.textarea)}if(s&&o>=9){this.hasSelection=r}}else if(!e){this.prevInput=this.textarea.value="";if(s&&o>=9){this.hasSelection=null}}};nl.prototype.getField=function(){return this.textarea};nl.prototype.supportsTouch=function(){return false};nl.prototype.focus=function(){if(this.cm.options.readOnly!="nocursor"&&(!g||P()!=this.textarea)){try{this.textarea.focus()}catch(e){}}};nl.prototype.blur=function(){this.textarea.blur()};nl.prototype.resetPosition=function(){this.wrapper.style.top=this.wrapper.style.left=0};nl.prototype.receivedFocus=function(){this.slowPoll()};nl.prototype.slowPoll=function(){var e=this;if(this.pollingFast){return}this.polling.set(this.cm.options.pollInterval,function(){e.poll();if(e.cm.state.focused){e.slowPoll()}})};nl.prototype.fastPoll=function(){var e=false,t=this;t.pollingFast=true;function r(){var i=t.poll();if(!i&&!e){e=true;t.polling.set(60,r)}else{t.pollingFast=false;t.slowPoll()}}t.polling.set(20,r)};nl.prototype.poll=function(){var e=this;var t=this.cm,r=this.textarea,i=this.prevInput;if(this.contextMenuPending||!t.state.focused||Oe(r)&&!i&&!this.composing||t.isReadOnly()||t.options.disableInput||t.state.keySeq){return false}var n=r.value;if(n==i&&!t.somethingSelected()){return false}if(s&&o>=9&&this.hasSelection===n||y&&/[\uf700-\uf7ff]/.test(n)){t.display.input.reset();return false}if(t.doc.sel==t.display.selForContextMenu){var a=n.charCodeAt(0);if(a==8203&&!i){i="​"}if(a==8666){this.reset();return this.cm.execCommand("undo")}}var l=0,u=Math.min(i.length,n.length);while(l1e3||n.indexOf("\n")>-1){r.value=e.prevInput=""}else{e.prevInput=n}if(e.composing){e.composing.range.clear();e.composing.range=t.markText(e.composing.start,t.getCursor("to"),{className:"CodeMirror-composing"})}});return true};nl.prototype.ensurePolled=function(){if(this.pollingFast&&this.poll()){this.pollingFast=false}};nl.prototype.onKeyPress=function(){if(s&&o>=9){this.hasSelection=null}this.fastPoll()};nl.prototype.onContextMenu=function(e){var t=this,r=t.cm,i=r.display,n=t.textarea;if(t.contextMenuPending){t.contextMenuPending()}var a=Ii(r,e),u=i.scroller.scrollTop;if(!a||f){return}var c=r.options.resetSelectionOnContextMenu;if(c&&r.doc.sel.contains(a)==-1){_n(r,za)(r.doc,sa(a),j)}var h=n.style.cssText,p=t.wrapper.style.cssText;var d=t.wrapper.offsetParent.getBoundingClientRect();t.wrapper.style.cssText="position: static";n.style.cssText="position: absolute; width: 30px; height: 30px;\n top: "+(e.clientY-d.top-5)+"px; left: "+(e.clientX-d.left-5)+"px;\n z-index: 1000; background: "+(s?"rgba(255, 255, 255, .05)":"transparent")+";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";var m;if(l){m=window.scrollY}i.input.focus();if(l){window.scrollTo(null,m)}i.input.reset();if(!r.somethingSelected()){n.value=t.prevInput=" "}t.contextMenuPending=g;i.selForContextMenu=r.doc.sel;clearTimeout(i.detectingSelectAll);function v(){if(n.selectionStart!=null){var e=r.somethingSelected();var a="​"+(e?n.value:"");n.value="⇚";n.value=a;t.prevInput=e?"":"​";n.selectionStart=1;n.selectionEnd=a.length;i.selForContextMenu=r.doc.sel}}function g(){if(t.contextMenuPending!=g){return}t.contextMenuPending=false;t.wrapper.style.cssText=p;n.style.cssText=h;if(s&&o<9){i.scrollbars.setScrollTop(i.scroller.scrollTop=u)}if(n.selectionStart!=null){if(!s||s&&o<9){v()}var e=0,a=function(){if(i.selForContextMenu==r.doc.sel&&n.selectionStart==0&&n.selectionEnd>0&&t.prevInput=="​"){_n(r,$a)(r)}else if(e++<10){i.detectingSelectAll=setTimeout(a,500)}else{i.selForContextMenu=null;i.input.reset()}};i.detectingSelectAll=setTimeout(a,200)}}if(s&&o>=9){v()}if(C){Le(e);var y=function(){ve(window,"mouseup",y);setTimeout(g,20)};de(window,"mouseup",y)}else{setTimeout(g,50)}};nl.prototype.readOnlyChanged=function(e){if(!e){this.reset()}this.textarea.disabled=e=="nocursor"};nl.prototype.setUneditable=function(){};nl.prototype.needsContentAttribute=false;function al(e,t){t=t?V(t):{};t.value=e.value;if(!t.tabindex&&e.tabIndex){t.tabindex=e.tabIndex}if(!t.placeholder&&e.placeholder){t.placeholder=e.placeholder}if(t.autofocus==null){var r=P();t.autofocus=r==e||e.getAttribute("autofocus")!=null&&r==document.body}function i(){e.value=o.getValue()}var n;if(e.form){de(e.form,"submit",i);if(!t.leaveSubmitMethodAlone){var a=e.form;n=a.submit;try{var s=a.submit=function(){i();a.submit=n;a.submit();a.submit=s}}catch(e){}}}t.finishInit=function(r){r.save=i;r.getTextArea=function(){return e};r.toTextArea=function(){r.toTextArea=isNaN;i();e.parentNode.removeChild(r.getWrapperElement());e.style.display="";if(e.form){ve(e.form,"submit",i);if(!t.leaveSubmitMethodAlone&&typeof e.form.submit=="function"){e.form.submit=n}}}};e.style.display="none";var o=Ro(function(t){return e.parentNode.insertBefore(t,e.nextSibling)},t);return o}function sl(e){e.off=ve;e.on=de;e.wheelEventPixels=ta;e.Doc=ws;e.splitLines=Ie;e.countColumn=F;e.findColumn=q;e.isWordChar=te;e.Pass=H;e.signal=ge;e.Line=or;e.changeEnd=oa;e.scrollbarModel=xn;e.Pos=nt;e.cmpPos=at;e.modes=Fe;e.mimeModes=We;e.resolveMode=He;e.getMode=je;e.modeExtensions=Ue;e.extendMode=Ge;e.copyState=qe;e.startState=$e;e.innerMode=Ke;e.commands=Xs;e.keyMap=Rs;e.keyName=Hs;e.isModifierKey=zs;e.lookupKey=Ws;e.normalizeKeyMap=Fs;e.StringStream=Xe;e.SharedTextMarker=ms;e.TextMarker=ps;e.LineWidget=us;e.e_preventDefault=ke;e.e_stopPropagation=Ce;e.e_stop=Le;e.addClass=I;e.contains=_;e.rmClass=L;e.keyNames=Ps}Io(Ro);$o(Ro);var ol="iter insert remove copy getEditor constructor".split(" ");for(var ll in ws.prototype){if(ws.prototype.hasOwnProperty(ll)&&z(ol,ll)<0){Ro.prototype[ll]=function(e){return function(){return e.apply(this.doc,arguments)}}(ws.prototype[ll])}}we(ws);Ro.inputStyles={textarea:nl,contenteditable:Qo};Ro.defineMode=function(e){if(!Ro.defaults.mode&&e!="null"){Ro.defaults.mode=e}ze.apply(this,arguments)};Ro.defineMIME=Be;Ro.defineMode("null",function(){return{token:function(e){return e.skipToEnd()}}});Ro.defineMIME("text/plain","null");Ro.defineExtension=function(e,t){Ro.prototype[e]=t};Ro.defineDocExtension=function(e,t){ws.prototype[e]=t};Ro.fromTextArea=al;sl(Ro);Ro.version="5.49.2";return Ro});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror"],e);else e(CodeMirror)})(function(e){"use strict";e.defineMode("javascript",function(t,r){var i=t.indentUnit;var n=r.statementIndent;var a=r.jsonld;var s=r.json||a;var o=r.typescript;var l=r.wordCharacters||/[\w$\xa1-\uffff]/;var u=function(){function e(e){return{type:e,style:"keyword"}}var t=e("keyword a"),r=e("keyword b"),i=e("keyword c"),n=e("keyword d");var a=e("operator"),s={type:"atom",style:"atom"};return{if:e("if"),while:t,with:t,else:r,do:r,try:r,finally:r,return:n,break:n,continue:n,new:e("new"),delete:i,void:i,throw:i,debugger:e("debugger"),var:e("var"),const:e("var"),let:e("var"),function:e("function"),catch:e("catch"),for:e("for"),switch:e("switch"),case:e("case"),default:e("default"),in:a,typeof:a,instanceof:a,true:s,false:s,null:s,undefined:s,NaN:s,Infinity:s,this:e("this"),class:e("class"),super:e("atom"),yield:i,export:e("export"),import:e("import"),extends:i,await:i}}();var c=/[+\-*&%=<>!?|~^@]/;var f=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;function h(e){var t=false,r,i=false;while((r=e.next())!=null){if(!t){if(r=="/"&&!i)return;if(r=="[")i=true;else if(i&&r=="]")i=false}t=!t&&r=="\\"}}var p,d;function m(e,t,r){p=e;d=r;return t}function v(e,t){var r=e.next();if(r=='"'||r=="'"){t.tokenize=g(r);return t.tokenize(e,t)}else if(r=="."&&e.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)){return m("number","number")}else if(r=="."&&e.match("..")){return m("spread","meta")}else if(/[\[\]{}\(\),;\:\.]/.test(r)){return m(r)}else if(r=="="&&e.eat(">")){return m("=>","operator")}else if(r=="0"&&e.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)){return m("number","number")}else if(/\d/.test(r)){e.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);return m("number","number")}else if(r=="/"){if(e.eat("*")){t.tokenize=y;return y(e,t)}else if(e.eat("/")){e.skipToEnd();return m("comment","comment")}else if(et(e,t,1)){h(e);e.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);return m("regexp","string-2")}else{e.eat("=");return m("operator","operator",e.current())}}else if(r=="`"){t.tokenize=x;return x(e,t)}else if(r=="#"){e.skipToEnd();return m("error","error")}else if(r=="<"&&e.match("!--")||r=="-"&&e.match("->")){e.skipToEnd();return m("comment","comment")}else if(c.test(r)){if(r!=">"||!t.lexical||t.lexical.type!=">"){if(e.eat("=")){if(r=="!"||r=="=")e.eat("=")}else if(/[<>*+\-]/.test(r)){e.eat(r);if(r==">")e.eat(r)}}return m("operator","operator",e.current())}else if(l.test(r)){e.eatWhile(l);var i=e.current();if(t.lastType!="."){if(u.propertyIsEnumerable(i)){var n=u[i];return m(n.type,n.style,i)}if(i=="async"&&e.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/,false))return m("async","keyword",i)}return m("variable","variable",i)}}function g(e){return function(t,r){var i=false,n;if(a&&t.peek()=="@"&&t.match(f)){r.tokenize=v;return m("jsonld-keyword","meta")}while((n=t.next())!=null){if(n==e&&!i)break;i=!i&&n=="\\"}if(!i)r.tokenize=v;return m("string","string")}}function y(e,t){var r=false,i;while(i=e.next()){if(i=="/"&&r){t.tokenize=v;break}r=i=="*"}return m("comment","comment")}function x(e,t){var r=false,i;while((i=e.next())!=null){if(!r&&(i=="`"||i=="$"&&e.eat("{"))){t.tokenize=v;break}r=!r&&i=="\\"}return m("quasi","string-2",e.current())}var b="([{}])";function w(e,t){if(t.fatArrowAt)t.fatArrowAt=null;var r=e.string.indexOf("=>",e.start);if(r<0)return;if(o){var i=/:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(e.string.slice(e.start,r));if(i)r=i.index}var n=0,a=false;for(var s=r-1;s>=0;--s){var u=e.string.charAt(s);var c=b.indexOf(u);if(c>=0&&c<3){if(!n){++s;break}if(--n==0){if(u=="(")a=true;break}}else if(c>=3&&c<6){++n}else if(l.test(u)){a=true}else if(/["'\/`]/.test(u)){for(;;--s){if(s==0)return;var f=e.string.charAt(s-1);if(f==u&&e.string.charAt(s-2)!="\\"){s--;break}}}else if(a&&!n){++s;break}}if(a&&!n)t.fatArrowAt=s}var k={atom:true,number:true,variable:true,string:true,regexp:true,this:true,"jsonld-keyword":true};function C(e,t,r,i,n,a){this.indented=e;this.column=t;this.type=r;this.prev=n;this.info=a;if(i!=null)this.align=i}function S(e,t){for(var r=e.localVars;r;r=r.next)if(r.name==t)return true;for(var i=e.context;i;i=i.prev){for(var r=i.vars;r;r=r.next)if(r.name==t)return true}}function L(e,t,r,i,n){var a=e.cc;T.state=e;T.stream=n;T.marked=null,T.cc=a;T.style=t;if(!e.lexical.hasOwnProperty("align"))e.lexical.align=true;while(true){var o=a.length?a.pop():s?U:H;if(o(r,i)){while(a.length&&a[a.length-1].lex)a.pop()();if(T.marked)return T.marked;if(r=="variable"&&S(e,i))return"variable-2";return t}}}var T={state:null,column:null,marked:null,cc:null};function A(){for(var e=arguments.length-1;e>=0;e--)T.cc.push(arguments[e])}function E(){A.apply(null,arguments);return true}function M(e,t){for(var r=t;r;r=r.next)if(r.name==e)return true;return false}function N(e){var t=T.state;T.marked="def";if(t.context){if(t.lexical.info=="var"&&t.context&&t.context.block){var i=_(e,t.context);if(i!=null){t.context=i;return}}else if(!M(e,t.localVars)){t.localVars=new O(e,t.localVars);return}}if(r.globalVars&&!M(e,t.globalVars))t.globalVars=new O(e,t.globalVars)}function _(e,t){if(!t){return null}else if(t.block){var r=_(e,t.prev);if(!r)return null;if(r==t.prev)return t;return new I(r,t.vars,true)}else if(M(e,t.vars)){return t}else{return new I(t.prev,new O(e,t.vars),false)}}function P(e){return e=="public"||e=="private"||e=="protected"||e=="abstract"||e=="readonly"}function I(e,t,r){this.prev=e;this.vars=t;this.block=r}function O(e,t){this.name=e;this.next=t}var D=new O("this",new O("arguments",null));function R(){T.state.context=new I(T.state.context,T.state.localVars,false);T.state.localVars=D}function V(){T.state.context=new I(T.state.context,T.state.localVars,true);T.state.localVars=null}function F(){T.state.localVars=T.state.context.vars;T.state.context=T.state.context.prev}F.lex=true;function W(e,t){var r=function(){var r=T.state,i=r.indented;if(r.lexical.type=="stat")i=r.lexical.indented;else for(var n=r.lexical;n&&n.type==")"&&n.align;n=n.prev)i=n.indented;r.lexical=new C(i,T.stream.column(),e,null,r.lexical,t)};r.lex=true;return r}function z(){var e=T.state;if(e.lexical.prev){if(e.lexical.type==")")e.indented=e.lexical.indented;e.lexical=e.lexical.prev}}z.lex=true;function B(e){function t(r){if(r==e)return E();else if(e==";"||r=="}"||r==")"||r=="]")return A();else return E(t)}return t}function H(e,t){if(e=="var")return E(W("vardef",t),Se,B(";"),z);if(e=="keyword a")return E(W("form"),q,H,z);if(e=="keyword b")return E(W("form"),H,z);if(e=="keyword d")return T.stream.match(/^\s*$/,false)?E():E(W("stat"),$,B(";"),z);if(e=="debugger")return E(B(";"));if(e=="{")return E(W("}"),V,fe,z,F);if(e==";")return E();if(e=="if"){if(T.state.lexical.info=="else"&&T.state.cc[T.state.cc.length-1]==z)T.state.cc.pop()();return E(W("form"),q,H,z,Ne)}if(e=="function")return E(Oe);if(e=="for")return E(W("form"),_e,H,z);if(e=="class"||o&&t=="interface"){T.marked="keyword";return E(W("form",e=="class"?e:t),We,z)}if(e=="variable"){if(o&&t=="declare"){T.marked="keyword";return E(H)}else if(o&&(t=="module"||t=="enum"||t=="type")&&T.stream.match(/^\s*\w/,false)){T.marked="keyword";if(t=="enum")return E(Qe);else if(t=="type")return E(Re,B("operator"),ve,B(";"));else return E(W("form"),Le,B("{"),W("}"),fe,z,z)}else if(o&&t=="namespace"){T.marked="keyword";return E(W("form"),U,H,z)}else if(o&&t=="abstract"){T.marked="keyword";return E(H)}else{return E(W("stat"),ne)}}if(e=="switch")return E(W("form"),q,B("{"),W("}","switch"),V,fe,z,z,F);if(e=="case")return E(U,B(":"));if(e=="default")return E(B(":"));if(e=="catch")return E(W("form"),R,j,H,z,F);if(e=="export")return E(W("stat"),je,z);if(e=="import")return E(W("stat"),Ge,z);if(e=="async")return E(H);if(t=="@")return E(U,H);return A(W("stat"),U,B(";"),z)}function j(e){if(e=="(")return E(Ve,B(")"))}function U(e,t){return K(e,t,false)}function G(e,t){return K(e,t,true)}function q(e){if(e!="(")return A();return E(W(")"),U,B(")"),z)}function K(e,t,r){if(T.state.fatArrowAt==T.stream.start){var i=r?ee:J;if(e=="(")return E(R,W(")"),ue(Ve,")"),z,B("=>"),i,F);else if(e=="variable")return A(R,Le,B("=>"),i,F)}var n=r?Y:X;if(k.hasOwnProperty(e))return E(n);if(e=="function")return E(Oe,n);if(e=="class"||o&&t=="interface"){T.marked="keyword";return E(W("form"),Fe,z)}if(e=="keyword c"||e=="async")return E(r?G:U);if(e=="(")return E(W(")"),$,B(")"),z,n);if(e=="operator"||e=="spread")return E(r?G:U);if(e=="[")return E(W("]"),Ye,z,n);if(e=="{")return ce(se,"}",null,n);if(e=="quasi")return A(Q,n);if(e=="new")return E(te(r));if(e=="import")return E(U);return E()}function $(e){if(e.match(/[;\}\)\],]/))return A();return A(U)}function X(e,t){if(e==",")return E(U);return Y(e,t,false)}function Y(e,t,r){var i=r==false?X:Y;var n=r==false?U:G;if(e=="=>")return E(R,r?ee:J,F);if(e=="operator"){if(/\+\+|--/.test(t)||o&&t=="!")return E(i);if(o&&t=="<"&&T.stream.match(/^([^>]|<.*?>)*>\s*\(/,false))return E(W(">"),ue(ve,">"),z,i);if(t=="?")return E(U,B(":"),n);return E(n)}if(e=="quasi"){return A(Q,i)}if(e==";")return;if(e=="(")return ce(G,")","call",i);if(e==".")return E(ae,i);if(e=="[")return E(W("]"),$,B("]"),z,i);if(o&&t=="as"){T.marked="keyword";return E(ve,i)}if(e=="regexp"){T.state.lastType=T.marked="operator";T.stream.backUp(T.stream.pos-T.stream.start-1);return E(n)}}function Q(e,t){if(e!="quasi")return A();if(t.slice(t.length-2)!="${")return E(Q);return E(U,Z)}function Z(e){if(e=="}"){T.marked="string-2";T.state.tokenize=x;return E(Q)}}function J(e){w(T.stream,T.state);return A(e=="{"?H:U)}function ee(e){w(T.stream,T.state);return A(e=="{"?H:G)}function te(e){return function(t){if(t==".")return E(e?ie:re);else if(t=="variable"&&o)return E(we,e?Y:X);else return A(e?G:U)}}function re(e,t){if(t=="target"){T.marked="keyword";return E(X)}}function ie(e,t){if(t=="target"){T.marked="keyword";return E(Y)}}function ne(e){if(e==":")return E(z,H);return A(X,B(";"),z)}function ae(e){if(e=="variable"){T.marked="property";return E()}}function se(e,t){if(e=="async"){T.marked="property";return E(se)}else if(e=="variable"||T.style=="keyword"){T.marked="property";if(t=="get"||t=="set")return E(oe);var r;if(o&&T.state.fatArrowAt==T.stream.start&&(r=T.stream.match(/^\s*:\s*/,false)))T.state.fatArrowAt=T.stream.pos+r[0].length;return E(le)}else if(e=="number"||e=="string"){T.marked=a?"property":T.style+" property";return E(le)}else if(e=="jsonld-keyword"){return E(le)}else if(o&&P(t)){T.marked="keyword";return E(se)}else if(e=="["){return E(U,he,B("]"),le)}else if(e=="spread"){return E(G,le)}else if(t=="*"){T.marked="keyword";return E(se)}else if(e==":"){return A(le)}}function oe(e){if(e!="variable")return A(le);T.marked="property";return E(Oe)}function le(e){if(e==":")return E(G);if(e=="(")return A(Oe)}function ue(e,t,r){function i(n,a){if(r?r.indexOf(n)>-1:n==","){var s=T.state.lexical;if(s.info=="call")s.pos=(s.pos||0)+1;return E(function(r,i){if(r==t||i==t)return A();return A(e)},i)}if(n==t||a==t)return E();if(r&&r.indexOf(";")>-1)return A(e);return E(B(t))}return function(r,n){if(r==t||n==t)return E();return A(e,i)}}function ce(e,t,r){for(var i=3;i"),ve)}function ge(e){if(e=="=>")return E(ve)}function ye(e,t){if(e=="variable"||T.style=="keyword"){T.marked="property";return E(ye)}else if(t=="?"||e=="number"||e=="string"){return E(ye)}else if(e==":"){return E(ve)}else if(e=="["){return E(B("variable"),pe,B("]"),ye)}else if(e=="("){return A(De,ye)}}function xe(e,t){if(e=="variable"&&T.stream.match(/^\s*[?:]/,false)||t=="?")return E(xe);if(e==":")return E(ve);if(e=="spread")return E(xe);return A(ve)}function be(e,t){if(t=="<")return E(W(">"),ue(ve,">"),z,be);if(t=="|"||e=="."||t=="&")return E(ve);if(e=="[")return E(ve,B("]"),be);if(t=="extends"||t=="implements"){T.marked="keyword";return E(ve)}if(t=="?")return E(ve,B(":"),ve)}function we(e,t){if(t=="<")return E(W(">"),ue(ve,">"),z,be)}function ke(){return A(ve,Ce)}function Ce(e,t){if(t=="=")return E(ve)}function Se(e,t){if(t=="enum"){T.marked="keyword";return E(Qe)}return A(Le,he,Ee,Me)}function Le(e,t){if(o&&P(t)){T.marked="keyword";return E(Le)}if(e=="variable"){N(t);return E()}if(e=="spread")return E(Le);if(e=="[")return ce(Ae,"]");if(e=="{")return ce(Te,"}")}function Te(e,t){if(e=="variable"&&!T.stream.match(/^\s*:/,false)){N(t);return E(Ee)}if(e=="variable")T.marked="property";if(e=="spread")return E(Le);if(e=="}")return A();if(e=="[")return E(U,B("]"),B(":"),Te);return E(B(":"),Le,Ee)}function Ae(){return A(Le,Ee)}function Ee(e,t){if(t=="=")return E(G)}function Me(e){if(e==",")return E(Se)}function Ne(e,t){if(e=="keyword b"&&t=="else")return E(W("form","else"),H,z)}function _e(e,t){if(t=="await")return E(_e);if(e=="(")return E(W(")"),Pe,z)}function Pe(e){if(e=="var")return E(Se,Ie);if(e=="variable")return E(Ie);return A(Ie)}function Ie(e,t){if(e==")")return E();if(e==";")return E(Ie);if(t=="in"||t=="of"){T.marked="keyword";return E(U,Ie)}return A(U,Ie)}function Oe(e,t){if(t=="*"){T.marked="keyword";return E(Oe) +}if(e=="variable"){N(t);return E(Oe)}if(e=="(")return E(R,W(")"),ue(Ve,")"),z,de,H,F);if(o&&t=="<")return E(W(">"),ue(ke,">"),z,Oe)}function De(e,t){if(t=="*"){T.marked="keyword";return E(De)}if(e=="variable"){N(t);return E(De)}if(e=="(")return E(R,W(")"),ue(Ve,")"),z,de,F);if(o&&t=="<")return E(W(">"),ue(ke,">"),z,De)}function Re(e,t){if(e=="keyword"||e=="variable"){T.marked="type";return E(Re)}else if(t=="<"){return E(W(">"),ue(ke,">"),z)}}function Ve(e,t){if(t=="@")E(U,Ve);if(e=="spread")return E(Ve);if(o&&P(t)){T.marked="keyword";return E(Ve)}if(o&&e=="this")return E(he,Ee);return A(Le,he,Ee)}function Fe(e,t){if(e=="variable")return We(e,t);return ze(e,t)}function We(e,t){if(e=="variable"){N(t);return E(ze)}}function ze(e,t){if(t=="<")return E(W(">"),ue(ke,">"),z,ze);if(t=="extends"||t=="implements"||o&&e==","){if(t=="implements")T.marked="keyword";return E(o?ve:U,ze)}if(e=="{")return E(W("}"),Be,z)}function Be(e,t){if(e=="async"||e=="variable"&&(t=="static"||t=="get"||t=="set"||o&&P(t))&&T.stream.match(/^\s+[\w$\xa1-\uffff]/,false)){T.marked="keyword";return E(Be)}if(e=="variable"||T.style=="keyword"){T.marked="property";return E(o?He:Oe,Be)}if(e=="number"||e=="string")return E(o?He:Oe,Be);if(e=="[")return E(U,he,B("]"),o?He:Oe,Be);if(t=="*"){T.marked="keyword";return E(Be)}if(o&&e=="(")return A(De,Be);if(e==";"||e==",")return E(Be);if(e=="}")return E();if(t=="@")return E(U,Be)}function He(e,t){if(t=="?")return E(He);if(e==":")return E(ve,Ee);if(t=="=")return E(G);var r=T.state.lexical.prev,i=r&&r.info=="interface";return A(i?De:Oe)}function je(e,t){if(t=="*"){T.marked="keyword";return E(Xe,B(";"))}if(t=="default"){T.marked="keyword";return E(U,B(";"))}if(e=="{")return E(ue(Ue,"}"),Xe,B(";"));return A(H)}function Ue(e,t){if(t=="as"){T.marked="keyword";return E(B("variable"))}if(e=="variable")return A(G,Ue)}function Ge(e){if(e=="string")return E();if(e=="(")return A(U);return A(qe,Ke,Xe)}function qe(e,t){if(e=="{")return ce(qe,"}");if(e=="variable")N(t);if(t=="*")T.marked="keyword";return E($e)}function Ke(e){if(e==",")return E(qe,Ke)}function $e(e,t){if(t=="as"){T.marked="keyword";return E(qe)}}function Xe(e,t){if(t=="from"){T.marked="keyword";return E(U)}}function Ye(e){if(e=="]")return E();return A(ue(G,"]"))}function Qe(){return A(W("form"),Le,B("{"),W("}"),ue(Ze,"}"),z,z)}function Ze(){return A(Le,Ee)}function Je(e,t){return e.lastType=="operator"||e.lastType==","||c.test(t.charAt(0))||/[,.]/.test(t.charAt(0))}function et(e,t,r){return t.tokenize==v&&/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(t.lastType)||t.lastType=="quasi"&&/\{\s*$/.test(e.string.slice(0,e.pos-(r||0)))}return{startState:function(e){var t={tokenize:v,lastType:"sof",cc:[],lexical:new C((e||0)-i,0,"block",false),localVars:r.localVars,context:r.localVars&&new I(null,null,false),indented:e||0};if(r.globalVars&&typeof r.globalVars=="object")t.globalVars=r.globalVars;return t},token:function(e,t){if(e.sol()){if(!t.lexical.hasOwnProperty("align"))t.lexical.align=false;t.indented=e.indentation();w(e,t)}if(t.tokenize!=y&&e.eatSpace())return null;var r=t.tokenize(e,t);if(p=="comment")return r;t.lastType=p=="operator"&&(d=="++"||d=="--")?"incdec":p;return L(t,r,p,d,e)},indent:function(t,a){if(t.tokenize==y)return e.Pass;if(t.tokenize!=v)return 0;var s=a&&a.charAt(0),o=t.lexical,l;if(!/^\s*else\b/.test(a))for(var u=t.cc.length-1;u>=0;--u){var c=t.cc[u];if(c==z)o=o.prev;else if(c!=Ne)break}while((o.type=="stat"||o.type=="form")&&(s=="}"||(l=t.cc[t.cc.length-1])&&(l==X||l==Y)&&!/^[,\.=+\-*:?[\(]/.test(a)))o=o.prev;if(n&&o.type==")"&&o.prev.type=="stat")o=o.prev;var f=o.type,h=s==f;if(f=="vardef")return o.indented+(t.lastType=="operator"||t.lastType==","?o.info.length+1:0);else if(f=="form"&&s=="{")return o.indented;else if(f=="form")return o.indented+i;else if(f=="stat")return o.indented+(Je(t,a)?n||i:0);else if(o.info=="switch"&&!h&&r.doubleIndentSwitch!=false)return o.indented+(/^(?:case|default)\b/.test(a)?i:2*i);else if(o.align)return o.column+(h?0:1);else return o.indented+(h?0:i)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:s?null:"/*",blockCommentEnd:s?null:"*/",blockCommentContinue:s?null:" * ",lineComment:s?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:s?"json":"javascript",jsonldMode:a,jsonMode:s,expressionAllowed:et,skipExpression:function(e){var t=e.cc[e.cc.length-1];if(t==U||t==G)e.cc.pop()}}});e.registerHelper("wordChars","javascript",/[\w$]/);e.defineMIME("text/javascript","javascript");e.defineMIME("text/ecmascript","javascript");e.defineMIME("application/javascript","javascript");e.defineMIME("application/x-javascript","javascript");e.defineMIME("application/ecmascript","javascript");e.defineMIME("application/json",{name:"javascript",json:true});e.defineMIME("application/x-json",{name:"javascript",json:true});e.defineMIME("application/ld+json",{name:"javascript",jsonld:true});e.defineMIME("text/typescript",{name:"javascript",typescript:true});e.defineMIME("application/typescript",{name:"javascript",typescript:true})});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror"],e);else e(CodeMirror)})(function(e){"use strict";e.defineMode("css",function(t,r){var i=r.inline;if(!r.propertyKeywords)r=e.resolveMode("text/css");var n=t.indentUnit,a=r.tokenHooks,s=r.documentTypes||{},o=r.mediaTypes||{},l=r.mediaFeatures||{},u=r.mediaValueKeywords||{},c=r.propertyKeywords||{},f=r.nonStandardPropertyKeywords||{},h=r.fontProperties||{},p=r.counterDescriptors||{},d=r.colorKeywords||{},m=r.valueKeywords||{},v=r.allowNested,g=r.lineComment,y=r.supportsAtComponent===true;var x,b;function w(e,t){x=t;return e}function k(e,t){var r=e.next();if(a[r]){var i=a[r](e,t);if(i!==false)return i}if(r=="@"){e.eatWhile(/[\w\\\-]/);return w("def",e.current())}else if(r=="="||(r=="~"||r=="|")&&e.eat("=")){return w(null,"compare")}else if(r=='"'||r=="'"){t.tokenize=C(r);return t.tokenize(e,t)}else if(r=="#"){e.eatWhile(/[\w\\\-]/);return w("atom","hash")}else if(r=="!"){e.match(/^\s*\w*/);return w("keyword","important")}else if(/\d/.test(r)||r=="."&&e.eat(/\d/)){e.eatWhile(/[\w.%]/);return w("number","unit")}else if(r==="-"){if(/[\d.]/.test(e.peek())){e.eatWhile(/[\w.%]/);return w("number","unit")}else if(e.match(/^-[\w\\\-]*/)){e.eatWhile(/[\w\\\-]/);if(e.match(/^\s*:/,false))return w("variable-2","variable-definition");return w("variable-2","variable")}else if(e.match(/^\w+-/)){return w("meta","meta")}}else if(/[,+>*\/]/.test(r)){return w(null,"select-op")}else if(r=="."&&e.match(/^-?[_a-z][_a-z0-9-]*/i)){return w("qualifier","qualifier")}else if(/[:;{}\[\]\(\)]/.test(r)){return w(null,r)}else if(e.match(/[\w-.]+(?=\()/)){if(/^(url(-prefix)?|domain|regexp)$/.test(e.current().toLowerCase())){t.tokenize=S}return w("variable callee","variable")}else if(/[\w\\\-]/.test(r)){e.eatWhile(/[\w\\\-]/);return w("property","word")}else{return w(null,null)}}function C(e){return function(t,r){var i=false,n;while((n=t.next())!=null){if(n==e&&!i){if(e==")")t.backUp(1);break}i=!i&&n=="\\"}if(n==e||!i&&e!=")")r.tokenize=null;return w("string","string")}}function S(e,t){e.next();if(!e.match(/\s*[\"\')]/,false))t.tokenize=C(")");else t.tokenize=null;return w(null,"(")}function L(e,t,r){this.type=e;this.indent=t;this.prev=r}function T(e,t,r,i){e.context=new L(r,t.indentation()+(i===false?0:n),e.context);return r}function A(e){if(e.context.prev)e.context=e.context.prev;return e.context.type}function E(e,t,r){return _[r.context.type](e,t,r)}function M(e,t,r,i){for(var n=i||1;n>0;n--)r.context=r.context.prev;return E(e,t,r)}function N(e){var t=e.current().toLowerCase();if(m.hasOwnProperty(t))b="atom";else if(d.hasOwnProperty(t))b="keyword";else b="variable"}var _={};_.top=function(e,t,r){if(e=="{"){return T(r,t,"block")}else if(e=="}"&&r.context.prev){return A(r)}else if(y&&/@component/i.test(e)){return T(r,t,"atComponentBlock")}else if(/^@(-moz-)?document$/i.test(e)){return T(r,t,"documentTypes")}else if(/^@(media|supports|(-moz-)?document|import)$/i.test(e)){return T(r,t,"atBlock")}else if(/^@(font-face|counter-style)/i.test(e)){r.stateArg=e;return"restricted_atBlock_before"}else if(/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(e)){return"keyframes"}else if(e&&e.charAt(0)=="@"){return T(r,t,"at")}else if(e=="hash"){b="builtin"}else if(e=="word"){b="tag"}else if(e=="variable-definition"){return"maybeprop"}else if(e=="interpolation"){return T(r,t,"interpolation")}else if(e==":"){return"pseudo"}else if(v&&e=="("){return T(r,t,"parens")}return r.context.type};_.block=function(e,t,r){if(e=="word"){var i=t.current().toLowerCase();if(c.hasOwnProperty(i)){b="property";return"maybeprop"}else if(f.hasOwnProperty(i)){b="string-2";return"maybeprop"}else if(v){b=t.match(/^\s*:(?:\s|$)/,false)?"property":"tag";return"block"}else{b+=" error";return"maybeprop"}}else if(e=="meta"){return"block"}else if(!v&&(e=="hash"||e=="qualifier")){b="error";return"block"}else{return _.top(e,t,r)}};_.maybeprop=function(e,t,r){if(e==":")return T(r,t,"prop");return E(e,t,r)};_.prop=function(e,t,r){if(e==";")return A(r);if(e=="{"&&v)return T(r,t,"propBlock");if(e=="}"||e=="{")return M(e,t,r);if(e=="(")return T(r,t,"parens");if(e=="hash"&&!/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(t.current())){b+=" error"}else if(e=="word"){N(t)}else if(e=="interpolation"){return T(r,t,"interpolation")}return"prop"};_.propBlock=function(e,t,r){if(e=="}")return A(r);if(e=="word"){b="property";return"maybeprop"}return r.context.type};_.parens=function(e,t,r){if(e=="{"||e=="}")return M(e,t,r);if(e==")")return A(r);if(e=="(")return T(r,t,"parens");if(e=="interpolation")return T(r,t,"interpolation");if(e=="word")N(t);return"parens"};_.pseudo=function(e,t,r){if(e=="meta")return"pseudo";if(e=="word"){b="variable-3";return r.context.type}return E(e,t,r)};_.documentTypes=function(e,t,r){if(e=="word"&&s.hasOwnProperty(t.current())){b="tag";return r.context.type}else{return _.atBlock(e,t,r)}};_.atBlock=function(e,t,r){if(e=="(")return T(r,t,"atBlock_parens");if(e=="}"||e==";")return M(e,t,r);if(e=="{")return A(r)&&T(r,t,v?"block":"top");if(e=="interpolation")return T(r,t,"interpolation");if(e=="word"){var i=t.current().toLowerCase();if(i=="only"||i=="not"||i=="and"||i=="or")b="keyword";else if(o.hasOwnProperty(i))b="attribute";else if(l.hasOwnProperty(i))b="property";else if(u.hasOwnProperty(i))b="keyword";else if(c.hasOwnProperty(i))b="property";else if(f.hasOwnProperty(i))b="string-2";else if(m.hasOwnProperty(i))b="atom";else if(d.hasOwnProperty(i))b="keyword";else b="error"}return r.context.type};_.atComponentBlock=function(e,t,r){if(e=="}")return M(e,t,r);if(e=="{")return A(r)&&T(r,t,v?"block":"top",false);if(e=="word")b="error";return r.context.type};_.atBlock_parens=function(e,t,r){if(e==")")return A(r);if(e=="{"||e=="}")return M(e,t,r,2);return _.atBlock(e,t,r)};_.restricted_atBlock_before=function(e,t,r){if(e=="{")return T(r,t,"restricted_atBlock");if(e=="word"&&r.stateArg=="@counter-style"){b="variable";return"restricted_atBlock_before"}return E(e,t,r)};_.restricted_atBlock=function(e,t,r){if(e=="}"){r.stateArg=null;return A(r)}if(e=="word"){if(r.stateArg=="@font-face"&&!h.hasOwnProperty(t.current().toLowerCase())||r.stateArg=="@counter-style"&&!p.hasOwnProperty(t.current().toLowerCase()))b="error";else b="property";return"maybeprop"}return"restricted_atBlock"};_.keyframes=function(e,t,r){if(e=="word"){b="variable";return"keyframes"}if(e=="{")return T(r,t,"top");return E(e,t,r)};_.at=function(e,t,r){if(e==";")return A(r);if(e=="{"||e=="}")return M(e,t,r);if(e=="word")b="tag";else if(e=="hash")b="builtin";return"at"};_.interpolation=function(e,t,r){if(e=="}")return A(r);if(e=="{"||e==";")return M(e,t,r);if(e=="word")b="variable";else if(e!="variable"&&e!="("&&e!=")")b="error";return"interpolation"};return{startState:function(e){return{tokenize:null,state:i?"block":"top",stateArg:null,context:new L(i?"block":"top",e||0,null)}},token:function(e,t){if(!t.tokenize&&e.eatSpace())return null;var r=(t.tokenize||k)(e,t);if(r&&typeof r=="object"){x=r[1];r=r[0]}b=r;if(x!="comment")t.state=_[t.state](x,e,t);return b},indent:function(e,t){var r=e.context,i=t&&t.charAt(0);var a=r.indent;if(r.type=="prop"&&(i=="}"||i==")"))r=r.prev;if(r.prev){if(i=="}"&&(r.type=="block"||r.type=="top"||r.type=="interpolation"||r.type=="restricted_atBlock")){r=r.prev;a=r.indent}else if(i==")"&&(r.type=="parens"||r.type=="atBlock_parens")||i=="{"&&(r.type=="at"||r.type=="atBlock")){a=Math.max(0,r.indent-n)}}return a},electricChars:"}",blockCommentStart:"/*",blockCommentEnd:"*/",blockCommentContinue:" * ",lineComment:g,fold:"brace"}});function t(e){var t={};for(var r=0;r"));else return null}else if(e.match("--")){return r(d("comment","--\x3e"))}else if(e.match("DOCTYPE",true,true)){e.eatWhile(/[\w\._\-]/);return r(m(1))}else{return null}}else if(e.eat("?")){e.eatWhile(/[\w\._\-]/);t.tokenize=d("meta","?>");return"meta"}else{u=e.eat("/")?"closeTag":"openTag";t.tokenize=h;return"tag bracket"}}else if(i=="&"){var n;if(e.eat("#")){if(e.eat("x")){n=e.eatWhile(/[a-fA-F\d]/)&&e.eat(";")}else{n=e.eatWhile(/[\d]/)&&e.eat(";")}}else{n=e.eatWhile(/[\w\.\-:]/)&&e.eat(";")}return n?"atom":"error"}else{e.eatWhile(/[^&<]/);return null}}f.isInText=true;function h(e,t){var r=e.next();if(r==">"||r=="/"&&e.eat(">")){t.tokenize=f;u=r==">"?"endTag":"selfcloseTag";return"tag bracket"}else if(r=="="){u="equals";return null}else if(r=="<"){t.tokenize=f;t.state=x;t.tagName=t.tagStart=null;var i=t.tokenize(e,t);return i?i+" tag error":"tag error"}else if(/[\'\"]/.test(r)){t.tokenize=p(r);t.stringStartCol=e.column();return t.tokenize(e,t)}else{e.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);return"word"}}function p(e){var t=function(t,r){while(!t.eol()){if(t.next()==e){r.tokenize=h;break}}return"string"};t.isInAttribute=true;return t}function d(e,t){return function(r,i){while(!r.eol()){if(r.match(t)){i.tokenize=f;break}r.next()}return e}}function m(e){return function(t,r){var i;while((i=t.next())!=null){if(i=="<"){r.tokenize=m(e+1);return r.tokenize(t,r)}else if(i==">"){if(e==1){r.tokenize=f;break}else{r.tokenize=m(e-1);return r.tokenize(t,r)}}}return"meta"}}function v(e,t,r){this.prev=e.context;this.tagName=t;this.indent=e.indented;this.startOfLine=r;if(s.doNotIndent.hasOwnProperty(t)||e.context&&e.context.noIndent)this.noIndent=true}function g(e){if(e.context)e.context=e.context.prev}function y(e,t){var r;while(true){if(!e.context){return}r=e.context.tagName;if(!s.contextGrabbers.hasOwnProperty(r)||!s.contextGrabbers[r].hasOwnProperty(t)){return}g(e)}}function x(e,t,r){if(e=="openTag"){r.tagStart=t.column();return b}else if(e=="closeTag"){return w}else{return x}}function b(e,t,r){if(e=="word"){r.tagName=t.current();c="tag";return S}else if(s.allowMissingTagName&&e=="endTag"){c="tag bracket";return S(e,t,r)}else{c="error";return b}}function w(e,t,r){if(e=="word"){var i=t.current();if(r.context&&r.context.tagName!=i&&s.implicitlyClosed.hasOwnProperty(r.context.tagName))g(r);if(r.context&&r.context.tagName==i||s.matchClosing===false){c="tag";return k}else{c="tag error";return C}}else if(s.allowMissingTagName&&e=="endTag"){c="tag bracket";return k(e,t,r)}else{c="error";return C}}function k(e,t,r){if(e!="endTag"){c="error";return k}g(r);return x}function C(e,t,r){c="error";return k(e,t,r)}function S(e,t,r){if(e=="word"){c="attribute";return L}else if(e=="endTag"||e=="selfcloseTag"){var i=r.tagName,n=r.tagStart;r.tagName=r.tagStart=null;if(e=="selfcloseTag"||s.autoSelfClosers.hasOwnProperty(i)){y(r,i)}else{y(r,i);r.context=new v(r,i,n==r.indented)}return x}c="error";return S}function L(e,t,r){if(e=="equals")return T;if(!s.allowMissing)c="error";return S(e,t,r)}function T(e,t,r){if(e=="string")return A;if(e=="word"&&s.allowUnquoted){c="string";return S}c="error";return S(e,t,r)}function A(e,t,r){if(e=="string")return A;return S(e,t,r)}return{startState:function(e){var t={tokenize:f,state:x,indented:e||0,tagName:null,tagStart:null,context:null};if(e!=null)t.baseIndent=e;return t},token:function(e,t){if(!t.tagName&&e.sol())t.indented=e.indentation();if(e.eatSpace())return null;u=null;var r=t.tokenize(e,t);if((r||u)&&r!="comment"){c=null;t.state=t.state(u||r,e,t);if(c)r=c=="error"?r+" error":c}return r},indent:function(t,r,i){var n=t.context;if(t.tokenize.isInAttribute){if(t.tagStart==t.indented)return t.stringStartCol+1;else return t.indented+a}if(n&&n.noIndent)return e.Pass;if(t.tokenize!=h&&t.tokenize!=f)return i?i.match(/^(\s*)/)[0].length:0;if(t.tagName){if(s.multilineTagIndentPastTag!==false)return t.tagStart+t.tagName.length+2;else return t.tagStart+a*(s.multilineTagIndentFactor||1)}if(s.alignCDATA&&/$/,blockCommentStart:"\x3c!--",blockCommentEnd:"--\x3e",configuration:s.htmlMode?"html":"xml",helperType:s.htmlMode?"html":"xml",skipAttribute:function(e){if(e.state==T)e.state=S},xmlCurrentTag:function(e){return e.tagName?{name:e.tagName,close:e.type=="closeTag"}:null},xmlCurrentContext:function(e){var t=[];for(var r=e.context;r;r=r.prev)if(r.tagName)t.push(r.tagName);return t.reverse()}}});e.defineMIME("text/xml","xml");e.defineMIME("application/xml","xml");if(!e.mimeModes.hasOwnProperty("text/html"))e.defineMIME("text/html",{name:"xml",htmlMode:true})});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"),require("../xml/xml"),require("../javascript/javascript"),require("../css/css"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror","../xml/xml","../javascript/javascript","../css/css"],e);else e(CodeMirror)})(function(e){"use strict";var t={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};function r(e,t,r){var i=e.current(),n=i.search(t);if(n>-1){e.backUp(i.length-n)}else if(i.match(/<\/?$/)){e.backUp(i.length);if(!e.match(t,false))e.match(i)}return r}var i={};function n(e){var t=i[e];if(t)return t;return i[e]=new RegExp("\\s+"+e+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")}function a(e,t){var r=e.match(n(t));return r?/^\s*(.*?)\s*$/.exec(r[2])[1]:""}function s(e,t){return new RegExp((t?"^":"")+"","i")}function o(e,t){for(var r in e){var i=t[r]||(t[r]=[]);var n=e[r];for(var a=n.length-1;a>=0;a--)i.unshift(n[a])}}function l(e,t){for(var r=0;r=0;h--)u.script.unshift(["type",f[h].matches,f[h].mode]);function p(t,n){var o=a.token(t,n.htmlState),c=/\btag\b/.test(o),f;if(c&&!/[<>\s\/]/.test(t.current())&&(f=n.htmlState.tagName&&n.htmlState.tagName.toLowerCase())&&u.hasOwnProperty(f)){n.inTag=f+" "}else if(n.inTag&&c&&/>$/.test(t.current())){var h=/^([\S]+) (.*)/.exec(n.inTag);n.inTag=null;var d=t.current()==">"&&l(u[h[1]],h[2]);var m=e.getMode(i,d);var v=s(h[1],true),g=s(h[1],false);n.token=function(e,t){if(e.match(v,false)){t.token=p;t.localState=t.localMode=null;return null}return r(e,g,t.localMode.token(e,t.localState))};n.localMode=m;n.localState=e.startState(m,a.indent(n.htmlState,"",""))}else if(n.inTag){n.inTag+=t.current();if(t.eol())n.inTag+=" "}return o}return{startState:function(){var t=e.startState(a);return{token:p,inTag:null,localMode:null,localState:null,htmlState:t}},copyState:function(t){var r;if(t.localState){r=e.copyState(t.localMode,t.localState)}return{token:t.token,inTag:t.inTag,localMode:t.localMode,localState:r,htmlState:e.copyState(a,t.htmlState)}},token:function(e,t){return t.token(e,t)},indent:function(t,r,i){if(!t.localMode||/^\s*<\//.test(r))return a.indent(t.htmlState,r,i);else if(t.localMode.indent)return t.localMode.indent(t.localState,r,i);else return e.Pass},innerMode:function(e){return{state:e.localState||e.htmlState,mode:e.localMode||a}}}},"xml","javascript","css");e.defineMIME("text/html","htmlmixed")});(function(e){if(typeof exports=="object"&&typeof module=="object")e(require("../../lib/codemirror"));else if(typeof define=="function"&&define.amd)define(["../../lib/codemirror"],e);else e(CodeMirror)})(function(e){var t=/MSIE \d/.test(navigator.userAgent)&&(document.documentMode==null||document.documentMode<8);var r=e.Pos;var i={"(":")>",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<","<":">>",">":"<<"};function n(e){return e&&e.bracketRegex||/[(){}[\]]/}function a(e,t,a){var o=e.getLineHandle(t.line),l=t.ch-1;var u=a&&a.afterCursor;if(u==null)u=/(^| )cm-fat-cursor($| )/.test(e.getWrapperElement().className);var c=n(a);var f=!u&&l>=0&&c.test(o.text.charAt(l))&&i[o.text.charAt(l)]||c.test(o.text.charAt(l+1))&&i[o.text.charAt(++l)];if(!f)return null;var h=f.charAt(1)==">"?1:-1;if(a&&a.strict&&h>0!=(l==t.ch))return null;var p=e.getTokenTypeAt(r(t.line,l+1));var d=s(e,r(t.line,l+(h>0?1:0)),h,p||null,a);if(d==null)return null;return{from:r(t.line,l),to:d&&d.pos,match:d&&d.ch==f.charAt(0),forward:h>0}}function s(e,t,a,s,o){var l=o&&o.maxScanLineLength||1e4;var u=o&&o.maxScanLines||1e3;var c=[];var f=n(o);var h=a>0?Math.min(t.line+u,e.lastLine()+1):Math.max(e.firstLine()-1,t.line-u);for(var p=t.line;p!=h;p+=a){var d=e.getLine(p);if(!d)continue;var m=a>0?0:d.length-1,v=a>0?d.length:-1;if(d.length>l)continue;if(p==t.line)m=t.ch-(a<0?1:0);for(;m!=v;m+=a){var g=d.charAt(m);if(f.test(g)&&(s===undefined||e.getTokenTypeAt(r(p,m+1))==s)){var y=i[g];if(y&&y.charAt(1)==">"==a>0)c.push(g);else if(!c.length)return{pos:r(p,m),ch:g};else c.pop()}}}return p-a==(a>0?e.lastLine():e.firstLine())?false:null}function o(e,i,n){var s=e.state.matchBrackets.maxHighlightLineLength||1e3;var o=[],l=e.listSelections();for(var u=0;ue){return false}r+=t[i+1];if(r>=e){return true}}}function h(e,t){if(e<65){return e===36}if(e<91){return true}if(e<97){return e===95}if(e<123){return true}if(e<=65535){return e>=170&&o.test(String.fromCharCode(e))}if(t===false){return false}return f(e,u)}function p(e,t){if(e<48){return e===36}if(e<58){return true}if(e<65){return false}if(e<91){return true}if(e<97){return e===95}if(e<123){return true}if(e<=65535){return e>=170&&l.test(String.fromCharCode(e))}if(t===false){return false}return f(e,u)||f(e,c)}var d=function e(t,r){if(r===void 0)r={};this.label=t;this.keyword=r.keyword;this.beforeExpr=!!r.beforeExpr;this.startsExpr=!!r.startsExpr;this.isLoop=!!r.isLoop;this.isAssign=!!r.isAssign;this.prefix=!!r.prefix;this.postfix=!!r.postfix;this.binop=r.binop||null;this.updateContext=null};function m(e,t){return new d(e,{beforeExpr:true,binop:t})}var v={beforeExpr:true};var g={startsExpr:true};var y={};function x(e,t){if(t===void 0)t={};t.keyword=e;return y[e]=new d(e,t)}var b={num:new d("num",g),regexp:new d("regexp",g),string:new d("string",g),name:new d("name",g),eof:new d("eof"),bracketL:new d("[",{beforeExpr:true,startsExpr:true}),bracketR:new d("]"),braceL:new d("{",{beforeExpr:true,startsExpr:true}),braceR:new d("}"),parenL:new d("(",{beforeExpr:true,startsExpr:true}),parenR:new d(")"),comma:new d(",",v),semi:new d(";",v),colon:new d(":",v),dot:new d("."),question:new d("?",v),arrow:new d("=>",v),template:new d("template"),invalidTemplate:new d("invalidTemplate"),ellipsis:new d("...",v),backQuote:new d("`",g),dollarBraceL:new d("${",{beforeExpr:true,startsExpr:true}),eq:new d("=",{beforeExpr:true,isAssign:true}),assign:new d("_=",{beforeExpr:true,isAssign:true}),incDec:new d("++/--",{prefix:true,postfix:true,startsExpr:true}),prefix:new d("!/~",{beforeExpr:true,prefix:true,startsExpr:true}),logicalOR:m("||",1),logicalAND:m("&&",2),bitwiseOR:m("|",3),bitwiseXOR:m("^",4),bitwiseAND:m("&",5),equality:m("==/!=/===/!==",6),relational:m("/<=/>=",7),bitShift:m("<>/>>>",8),plusMin:new d("+/-",{beforeExpr:true,binop:9,prefix:true,startsExpr:true}),modulo:m("%",10),star:m("*",10),slash:m("/",10),starstar:new d("**",{beforeExpr:true}),_break:x("break"),_case:x("case",v),_catch:x("catch"),_continue:x("continue"),_debugger:x("debugger"),_default:x("default",v),_do:x("do",{isLoop:true,beforeExpr:true}),_else:x("else",v),_finally:x("finally"),_for:x("for",{isLoop:true}),_function:x("function",g),_if:x("if"),_return:x("return",v),_switch:x("switch"),_throw:x("throw",v),_try:x("try"),_var:x("var"),_const:x("const"),_while:x("while",{isLoop:true}),_with:x("with"),_new:x("new",{beforeExpr:true,startsExpr:true}),_this:x("this",g),_super:x("super",g),_class:x("class",g),_extends:x("extends",v),_export:x("export"),_import:x("import"),_null:x("null",g),_true:x("true",g),_false:x("false",g),_in:x("in",{beforeExpr:true,binop:7}),_instanceof:x("instanceof",{beforeExpr:true,binop:7}),_typeof:x("typeof",{beforeExpr:true,prefix:true,startsExpr:true}),_void:x("void",{beforeExpr:true,prefix:true,startsExpr:true}),_delete:x("delete",{beforeExpr:true,prefix:true,startsExpr:true})};var w=/\r\n?|\n|\u2028|\u2029/;var k=new RegExp(w.source,"g");function C(e,t){return e===10||e===13||!t&&(e===8232||e===8233)}var S=/[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;var L=/(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g;var T=Object.prototype;var A=T.hasOwnProperty;var E=T.toString;function M(e,t){return A.call(e,t)}var N=Array.isArray||function(e){return E.call(e)==="[object Array]"};var _=function e(t,r){this.line=t;this.column=r};_.prototype.offset=function e(t){return new _(this.line,this.column+t)};var P=function e(t,r,i){this.start=r;this.end=i;if(t.sourceFile!==null){this.source=t.sourceFile}};function I(e,t){for(var r=1,i=0;;){k.lastIndex=i;var n=k.exec(e);if(n&&n.index=2015){t.ecmaVersion-=2009}if(t.allowReserved==null){t.allowReserved=t.ecmaVersion<5}if(N(t.onToken)){var i=t.onToken;t.onToken=function(e){return i.push(e)}}if(N(t.onComment)){t.onComment=R(t,t.onComment)}return t}function R(e,t){return function(r,i,n,a,s,o){var l={type:r?"Block":"Line",value:i,start:n,end:a};if(e.locations){l.loc=new P(this,s,o)}if(e.ranges){l.range=[n,a]}t.push(l)}}var V={};function F(e){return new RegExp("^(?:"+e.replace(/ /g,"|")+")$")}var W=function e(r,n,a){this.options=r=D(r);this.sourceFile=r.sourceFile;this.keywords=F(i[r.ecmaVersion>=6?6:5]);var s="";if(!r.allowReserved){for(var o=r.ecmaVersion;;o--){if(s=t[o]){break}}if(r.sourceType==="module"){s+=" await"}}this.reservedWords=F(s);var l=(s?s+" ":"")+t.strict;this.reservedWordsStrict=F(l);this.reservedWordsStrictBind=F(l+" "+t.strictBind);this.input=String(n);this.containsEsc=false;this.loadPlugins(r.plugins);if(a){this.pos=a;this.lineStart=this.input.lastIndexOf("\n",a-1)+1;this.curLine=this.input.slice(0,this.lineStart).split(w).length}else{this.pos=this.lineStart=0;this.curLine=1}this.type=b.eof;this.value=null;this.start=this.end=this.pos;this.startLoc=this.endLoc=this.curPosition();this.lastTokEndLoc=this.lastTokStartLoc=null;this.lastTokStart=this.lastTokEnd=this.pos;this.context=this.initialContext();this.exprAllowed=true;this.inModule=r.sourceType==="module";this.strict=this.inModule||this.strictDirective(this.pos);this.potentialArrowAt=-1;this.inFunction=this.inGenerator=this.inAsync=false;this.yieldPos=this.awaitPos=0;this.labels=[];if(this.pos===0&&r.allowHashBang&&this.input.slice(0,2)==="#!"){this.skipLineComment(2)}this.scopeStack=[];this.enterFunctionScope();this.regexpState=null};W.prototype.isKeyword=function e(t){return this.keywords.test(t)};W.prototype.isReservedWord=function e(t){return this.reservedWords.test(t)};W.prototype.extend=function e(t,r){this[t]=r(this[t])};W.prototype.loadPlugins=function e(t){var r=this;for(var i in t){var n=V[i];if(!n){throw new Error("Plugin '"+i+"' not found")}n(r,t[i])}};W.prototype.parse=function e(){var t=this.options.program||this.startNode();this.nextToken();return this.parseTopLevel(t)};var z=W.prototype;var B=/^(?:'((?:\\.|[^'])*?)'|"((?:\\.|[^"])*?)"|;)/;z.strictDirective=function(e){var t=this;for(;;){L.lastIndex=e;e+=L.exec(t.input)[0].length;var r=B.exec(t.input.slice(e));if(!r){return false}if((r[1]||r[2])==="use strict"){return true}e+=r[0].length}};z.eat=function(e){if(this.type===e){this.next();return true}else{return false}};z.isContextual=function(e){return this.type===b.name&&this.value===e&&!this.containsEsc};z.eatContextual=function(e){if(!this.isContextual(e)){return false}this.next();return true};z.expectContextual=function(e){if(!this.eatContextual(e)){this.unexpected()}};z.canInsertSemicolon=function(){return this.type===b.eof||this.type===b.braceR||w.test(this.input.slice(this.lastTokEnd,this.start))};z.insertSemicolon=function(){if(this.canInsertSemicolon()){if(this.options.onInsertedSemicolon){this.options.onInsertedSemicolon(this.lastTokEnd,this.lastTokEndLoc)}return true}};z.semicolon=function(){if(!this.eat(b.semi)&&!this.insertSemicolon()){this.unexpected()}};z.afterTrailingComma=function(e,t){if(this.type===e){if(this.options.onTrailingComma){this.options.onTrailingComma(this.lastTokStart,this.lastTokStartLoc)}if(!t){this.next()}return true}};z.expect=function(e){this.eat(e)||this.unexpected()};z.unexpected=function(e){this.raise(e!=null?e:this.start,"Unexpected token")};function H(){this.shorthandAssign=this.trailingComma=this.parenthesizedAssign=this.parenthesizedBind=this.doubleProto=-1}z.checkPatternErrors=function(e,t){if(!e){return}if(e.trailingComma>-1){this.raiseRecoverable(e.trailingComma,"Comma is not permitted after the rest element")}var r=t?e.parenthesizedAssign:e.parenthesizedBind;if(r>-1){this.raiseRecoverable(r,"Parenthesized pattern")}};z.checkExpressionErrors=function(e,t){if(!e){return false}var r=e.shorthandAssign;var i=e.doubleProto;if(!t){return r>=0||i>=0}if(r>=0){this.raise(r,"Shorthand property assignments are valid only in destructuring patterns")}if(i>=0){this.raiseRecoverable(i,"Redefinition of __proto__ property")}};z.checkYieldAwaitInDefaultParams=function(){if(this.yieldPos&&(!this.awaitPos||this.yieldPos=6){e.sourceType=this.options.sourceType}return this.finishNode(e,"Program")};var U={kind:"loop"};var G={kind:"switch"};j.isLet=function(){if(this.options.ecmaVersion<6||!this.isContextual("let")){return false}L.lastIndex=this.pos;var e=L.exec(this.input);var t=this.pos+e[0].length,r=this.input.charCodeAt(t);if(r===91||r===123){return true}if(h(r,true)){var i=t+1;while(p(this.input.charCodeAt(i),true)){++i}var a=this.input.slice(t,i);if(!n.test(a)){return true}}return false};j.isAsyncFunction=function(){if(this.options.ecmaVersion<8||!this.isContextual("async")){return false}L.lastIndex=this.pos;var e=L.exec(this.input);var t=this.pos+e[0].length;return!w.test(this.input.slice(this.pos,t))&&this.input.slice(t,t+8)==="function"&&(t+8===this.input.length||!p(this.input.charAt(t+8)))};j.parseStatement=function(e,t,r){var i=this.type,n=this.startNode(),a;if(this.isLet()){i=b._var;a="let"}switch(i){case b._break:case b._continue:return this.parseBreakContinueStatement(n,i.keyword);case b._debugger:return this.parseDebuggerStatement(n);case b._do:return this.parseDoStatement(n);case b._for:return this.parseForStatement(n);case b._function:if(!e&&this.options.ecmaVersion>=6){this.unexpected()}return this.parseFunctionStatement(n,false);case b._class:if(!e){this.unexpected()}return this.parseClass(n,true);case b._if:return this.parseIfStatement(n);case b._return:return this.parseReturnStatement(n);case b._switch:return this.parseSwitchStatement(n);case b._throw:return this.parseThrowStatement(n);case b._try:return this.parseTryStatement(n);case b._const:case b._var:a=a||this.value;if(!e&&a!=="var"){this.unexpected()}return this.parseVarStatement(n,a);case b._while:return this.parseWhileStatement(n);case b._with:return this.parseWithStatement(n);case b.braceL:return this.parseBlock();case b.semi:return this.parseEmptyStatement(n);case b._export:case b._import:if(!this.options.allowImportExportEverywhere){if(!t){this.raise(this.start,"'import' and 'export' may only appear at the top level")}if(!this.inModule){this.raise(this.start,"'import' and 'export' may appear only with 'sourceType: module'")}}return i===b._import?this.parseImport(n):this.parseExport(n,r);default:if(this.isAsyncFunction()){if(!e){this.unexpected()}this.next();return this.parseFunctionStatement(n,true)}var s=this.value,o=this.parseExpression();if(i===b.name&&o.type==="Identifier"&&this.eat(b.colon)){return this.parseLabeledStatement(n,s,o)}else{return this.parseExpressionStatement(n,o)}}};j.parseBreakContinueStatement=function(e,t){var r=this;var i=t==="break";this.next();if(this.eat(b.semi)||this.insertSemicolon()){e.label=null}else if(this.type!==b.name){this.unexpected()}else{e.label=this.parseIdent();this.semicolon()}var n=0;for(;n=6){this.eat(b.semi)}else{this.semicolon()}return this.finishNode(e,"DoWhileStatement")};j.parseForStatement=function(e){this.next();var t=this.options.ecmaVersion>=9&&(this.inAsync||!this.inFunction&&this.options.allowAwaitOutsideFunction)&&this.eatContextual("await")?this.lastTokStart:-1;this.labels.push(U);this.enterLexicalScope();this.expect(b.parenL);if(this.type===b.semi){if(t>-1){this.unexpected(t)}return this.parseFor(e,null)}var r=this.isLet();if(this.type===b._var||this.type===b._const||r){var i=this.startNode(),n=r?"let":this.value;this.next();this.parseVar(i,true,n);this.finishNode(i,"VariableDeclaration");if((this.type===b._in||this.options.ecmaVersion>=6&&this.isContextual("of"))&&i.declarations.length===1&&!(n!=="var"&&i.declarations[0].init)){if(this.options.ecmaVersion>=9){if(this.type===b._in){if(t>-1){this.unexpected(t)}}else{e.await=t>-1}}return this.parseForIn(e,i)}if(t>-1){this.unexpected(t)}return this.parseFor(e,i)}var a=new H;var s=this.parseExpression(true,a);if(this.type===b._in||this.options.ecmaVersion>=6&&this.isContextual("of")){if(this.options.ecmaVersion>=9){if(this.type===b._in){if(t>-1){this.unexpected(t)}}else{e.await=t>-1}}this.toAssignable(s,false,a);this.checkLVal(s);return this.parseForIn(e,s)}else{this.checkExpressionErrors(a,true)}if(t>-1){this.unexpected(t)}return this.parseFor(e,s)};j.parseFunctionStatement=function(e,t){this.next();return this.parseFunction(e,true,false,t)};j.parseIfStatement=function(e){this.next();e.test=this.parseParenExpression();e.consequent=this.parseStatement(!this.strict&&this.type===b._function);e.alternate=this.eat(b._else)?this.parseStatement(!this.strict&&this.type===b._function):null;return this.finishNode(e,"IfStatement")};j.parseReturnStatement=function(e){if(!this.inFunction&&!this.options.allowReturnOutsideFunction){this.raise(this.start,"'return' outside of function")}this.next();if(this.eat(b.semi)||this.insertSemicolon()){e.argument=null}else{e.argument=this.parseExpression();this.semicolon()}return this.finishNode(e,"ReturnStatement")};j.parseSwitchStatement=function(e){var t=this;this.next();e.discriminant=this.parseParenExpression();e.cases=[];this.expect(b.braceL);this.labels.push(G);this.enterLexicalScope();var r;for(var i=false;this.type!==b.braceR;){if(t.type===b._case||t.type===b._default){var n=t.type===b._case;if(r){t.finishNode(r,"SwitchCase")}e.cases.push(r=t.startNode());r.consequent=[];t.next();if(n){r.test=t.parseExpression()}else{if(i){t.raiseRecoverable(t.lastTokStart,"Multiple default clauses")}i=true;r.test=null}t.expect(b.colon)}else{if(!r){t.unexpected()}r.consequent.push(t.parseStatement(true))}}this.exitLexicalScope();if(r){this.finishNode(r,"SwitchCase")}this.next();this.labels.pop();return this.finishNode(e,"SwitchStatement")};j.parseThrowStatement=function(e){this.next();if(w.test(this.input.slice(this.lastTokEnd,this.start))){this.raise(this.lastTokEnd,"Illegal newline after throw")}e.argument=this.parseExpression();this.semicolon();return this.finishNode(e,"ThrowStatement")};var q=[];j.parseTryStatement=function(e){this.next();e.block=this.parseBlock();e.handler=null;if(this.type===b._catch){var t=this.startNode();this.next();if(this.eat(b.parenL)){t.param=this.parseBindingAtom();this.enterLexicalScope();this.checkLVal(t.param,"let");this.expect(b.parenR)}else{if(this.options.ecmaVersion<10){this.unexpected()}t.param=null;this.enterLexicalScope()}t.body=this.parseBlock(false);this.exitLexicalScope();e.handler=this.finishNode(t,"CatchClause")} +e.finalizer=this.eat(b._finally)?this.parseBlock():null;if(!e.handler&&!e.finalizer){this.raise(e.start,"Missing catch or finally clause")}return this.finishNode(e,"TryStatement")};j.parseVarStatement=function(e,t){this.next();this.parseVar(e,false,t);this.semicolon();return this.finishNode(e,"VariableDeclaration")};j.parseWhileStatement=function(e){this.next();e.test=this.parseParenExpression();this.labels.push(U);e.body=this.parseStatement(false);this.labels.pop();return this.finishNode(e,"WhileStatement")};j.parseWithStatement=function(e){if(this.strict){this.raise(this.start,"'with' in strict mode")}this.next();e.object=this.parseParenExpression();e.body=this.parseStatement(false);return this.finishNode(e,"WithStatement")};j.parseEmptyStatement=function(e){this.next();return this.finishNode(e,"EmptyStatement")};j.parseLabeledStatement=function(e,t,r){var i=this;for(var n=0,a=i.labels;n=0;l--){var u=i.labels[l];if(u.statementStart===e.start){u.statementStart=i.start;u.kind=o}else{break}}this.labels.push({name:t,kind:o,statementStart:this.start});e.body=this.parseStatement(true);if(e.body.type==="ClassDeclaration"||e.body.type==="VariableDeclaration"&&e.body.kind!=="var"||e.body.type==="FunctionDeclaration"&&(this.strict||e.body.generator||e.body.async)){this.raiseRecoverable(e.body.start,"Invalid labeled declaration")}this.labels.pop();e.label=r;return this.finishNode(e,"LabeledStatement")};j.parseExpressionStatement=function(e,t){e.expression=t;this.semicolon();return this.finishNode(e,"ExpressionStatement")};j.parseBlock=function(e){var t=this;if(e===void 0)e=true;var r=this.startNode();r.body=[];this.expect(b.braceL);if(e){this.enterLexicalScope()}while(!this.eat(b.braceR)){var i=t.parseStatement(true);r.body.push(i)}if(e){this.exitLexicalScope()}return this.finishNode(r,"BlockStatement")};j.parseFor=function(e,t){e.init=t;this.expect(b.semi);e.test=this.type===b.semi?null:this.parseExpression();this.expect(b.semi);e.update=this.type===b.parenR?null:this.parseExpression();this.expect(b.parenR);this.exitLexicalScope();e.body=this.parseStatement(false);this.labels.pop();return this.finishNode(e,"ForStatement")};j.parseForIn=function(e,t){var r=this.type===b._in?"ForInStatement":"ForOfStatement";this.next();if(r==="ForInStatement"){if(t.type==="AssignmentPattern"||t.type==="VariableDeclaration"&&t.declarations[0].init!=null&&(this.strict||t.declarations[0].id.type!=="Identifier")){this.raise(t.start,"Invalid assignment in for-in loop head")}}e.left=t;e.right=r==="ForInStatement"?this.parseExpression():this.parseMaybeAssign();this.expect(b.parenR);this.exitLexicalScope();e.body=this.parseStatement(false);this.labels.pop();return this.finishNode(e,r)};j.parseVar=function(e,t,r){var i=this;e.declarations=[];e.kind=r;for(;;){var n=i.startNode();i.parseVarId(n,r);if(i.eat(b.eq)){n.init=i.parseMaybeAssign(t)}else if(r==="const"&&!(i.type===b._in||i.options.ecmaVersion>=6&&i.isContextual("of"))){i.unexpected()}else if(n.id.type!=="Identifier"&&!(t&&(i.type===b._in||i.isContextual("of")))){i.raise(i.lastTokEnd,"Complex binding patterns require an initialization value")}else{n.init=null}e.declarations.push(i.finishNode(n,"VariableDeclarator"));if(!i.eat(b.comma)){break}}return e};j.parseVarId=function(e,t){e.id=this.parseBindingAtom(t);this.checkLVal(e.id,t,false)};j.parseFunction=function(e,t,r,i){this.initFunction(e);if(this.options.ecmaVersion>=9||this.options.ecmaVersion>=6&&!i){e.generator=this.eat(b.star)}if(this.options.ecmaVersion>=8){e.async=!!i}if(t){e.id=t==="nullableID"&&this.type!==b.name?null:this.parseIdent();if(e.id){this.checkLVal(e.id,this.inModule&&!this.inFunction?"let":"var")}}var n=this.inGenerator,a=this.inAsync,s=this.yieldPos,o=this.awaitPos,l=this.inFunction;this.inGenerator=e.generator;this.inAsync=e.async;this.yieldPos=0;this.awaitPos=0;this.inFunction=true;this.enterFunctionScope();if(!t){e.id=this.type===b.name?this.parseIdent():null}this.parseFunctionParams(e);this.parseFunctionBody(e,r);this.inGenerator=n;this.inAsync=a;this.yieldPos=s;this.awaitPos=o;this.inFunction=l;return this.finishNode(e,t?"FunctionDeclaration":"FunctionExpression")};j.parseFunctionParams=function(e){this.expect(b.parenL);e.params=this.parseBindingList(b.parenR,false,this.options.ecmaVersion>=8);this.checkYieldAwaitInDefaultParams()};j.parseClass=function(e,t){var r=this;this.next();this.parseClassId(e,t);this.parseClassSuper(e);var i=this.startNode();var n=false;i.body=[];this.expect(b.braceL);while(!this.eat(b.braceR)){var a=r.parseClassMember(i);if(a&&a.type==="MethodDefinition"&&a.kind==="constructor"){if(n){r.raise(a.start,"Duplicate constructor in the same class")}n=true}}e.body=this.finishNode(i,"ClassBody");return this.finishNode(e,t?"ClassDeclaration":"ClassExpression")};j.parseClassMember=function(e){var t=this;if(this.eat(b.semi)){return null}var r=this.startNode();var i=function(e,i){if(i===void 0)i=false;var n=t.start,a=t.startLoc;if(!t.eatContextual(e)){return false}if(t.type!==b.parenL&&(!i||!t.canInsertSemicolon())){return true}if(r.key){t.unexpected()}r.computed=false;r.key=t.startNodeAt(n,a);r.key.name=e;t.finishNode(r.key,"Identifier");return false};r.kind="method";r.static=i("static");var n=this.eat(b.star);var a=false;if(!n){if(this.options.ecmaVersion>=8&&i("async",true)){a=true;n=this.options.ecmaVersion>=9&&this.eat(b.star)}else if(i("get")){r.kind="get"}else if(i("set")){r.kind="set"}}if(!r.key){this.parsePropertyName(r)}var s=r.key;if(!r.computed&&!r.static&&(s.type==="Identifier"&&s.name==="constructor"||s.type==="Literal"&&s.value==="constructor")){if(r.kind!=="method"){this.raise(s.start,"Constructor can't have get/set modifier")}if(n){this.raise(s.start,"Constructor can't be a generator")}if(a){this.raise(s.start,"Constructor can't be an async method")}r.kind="constructor"}else if(r.static&&s.type==="Identifier"&&s.name==="prototype"){this.raise(s.start,"Classes may not have a static property named prototype")}this.parseClassMethod(e,r,n,a);if(r.kind==="get"&&r.value.params.length!==0){this.raiseRecoverable(r.value.start,"getter should have no params")}if(r.kind==="set"&&r.value.params.length!==1){this.raiseRecoverable(r.value.start,"setter should have exactly one param")}if(r.kind==="set"&&r.value.params[0].type==="RestElement"){this.raiseRecoverable(r.value.params[0].start,"Setter cannot use rest params")}return r};j.parseClassMethod=function(e,t,r,i){t.value=this.parseMethod(r,i);e.body.push(this.finishNode(t,"MethodDefinition"))};j.parseClassId=function(e,t){e.id=this.type===b.name?this.parseIdent():t===true?this.unexpected():null};j.parseClassSuper=function(e){e.superClass=this.eat(b._extends)?this.parseExprSubscripts():null};j.parseExport=function(e,t){var r=this;this.next();if(this.eat(b.star)){this.expectContextual("from");if(this.type!==b.string){this.unexpected()}e.source=this.parseExprAtom();this.semicolon();return this.finishNode(e,"ExportAllDeclaration")}if(this.eat(b._default)){this.checkExport(t,"default",this.lastTokStart);var i;if(this.type===b._function||(i=this.isAsyncFunction())){var n=this.startNode();this.next();if(i){this.next()}e.declaration=this.parseFunction(n,"nullableID",false,i)}else if(this.type===b._class){var a=this.startNode();e.declaration=this.parseClass(a,"nullableID")}else{e.declaration=this.parseMaybeAssign();this.semicolon()}return this.finishNode(e,"ExportDefaultDeclaration")}if(this.shouldParseExportStatement()){e.declaration=this.parseStatement(true);if(e.declaration.type==="VariableDeclaration"){this.checkVariableExport(t,e.declaration.declarations)}else{this.checkExport(t,e.declaration.id.name,e.declaration.id.start)}e.specifiers=[];e.source=null}else{e.declaration=null;e.specifiers=this.parseExportSpecifiers(t);if(this.eatContextual("from")){if(this.type!==b.string){this.unexpected()}e.source=this.parseExprAtom()}else{for(var s=0,o=e.specifiers;s=6&&e){switch(e.type){case"Identifier":if(this.inAsync&&e.name==="await"){this.raise(e.start,"Can not use 'await' as identifier inside an async function")}break;case"ObjectPattern":case"ArrayPattern":case"RestElement":break;case"ObjectExpression":e.type="ObjectPattern";if(r){this.checkPatternErrors(r,true)}for(var n=0,a=e.properties;n=9&&e.type==="SpreadElement"){return}if(this.options.ecmaVersion>=6&&(e.computed||e.method||e.shorthand)){return}var i=e.key;var n;switch(i.type){case"Identifier":n=i.name;break;case"Literal":n=String(i.value);break;default:return}var a=e.kind;if(this.options.ecmaVersion>=6){if(n==="__proto__"&&a==="init"){if(t.proto){if(r&&r.doubleProto<0){r.doubleProto=i.start}else{this.raiseRecoverable(i.start,"Redefinition of __proto__ property")}}t.proto=true}return}n="$"+n;var s=t[n];if(s){var o;if(a==="init"){o=this.strict&&s.init||s.get||s.set}else{o=s.init||s[a]}if(o){this.raiseRecoverable(i.start,"Redefinition of property")}}else{s=t[n]={init:false,get:false,set:false}}s[a]=true};$.parseExpression=function(e,t){var r=this;var i=this.start,n=this.startLoc;var a=this.parseMaybeAssign(e,t);if(this.type===b.comma){var s=this.startNodeAt(i,n);s.expressions=[a];while(this.eat(b.comma)){s.expressions.push(r.parseMaybeAssign(e,t))}return this.finishNode(s,"SequenceExpression")}return a};$.parseMaybeAssign=function(e,t,r){if(this.inGenerator&&this.isContextual("yield")){return this.parseYield()}var i=false,n=-1,a=-1;if(t){n=t.parenthesizedAssign;a=t.trailingComma;t.parenthesizedAssign=t.trailingComma=-1}else{t=new H;i=true}var s=this.start,o=this.startLoc;if(this.type===b.parenL||this.type===b.name){this.potentialArrowAt=this.start}var l=this.parseMaybeConditional(e,t);if(r){l=r.call(this,l,s,o)}if(this.type.isAssign){var u=this.startNodeAt(s,o);u.operator=this.value;u.left=this.type===b.eq?this.toAssignable(l,false,t):l;if(!i){H.call(t)}t.shorthandAssign=-1;this.checkLVal(l);this.next();u.right=this.parseMaybeAssign(e);return this.finishNode(u,"AssignmentExpression")}else{if(i){this.checkExpressionErrors(t,true)}}if(n>-1){t.parenthesizedAssign=n}if(a>-1){t.trailingComma=a}return l};$.parseMaybeConditional=function(e,t){var r=this.start,i=this.startLoc;var n=this.parseExprOps(e,t);if(this.checkExpressionErrors(t)){return n}if(this.eat(b.question)){var a=this.startNodeAt(r,i);a.test=n;a.consequent=this.parseMaybeAssign();this.expect(b.colon);a.alternate=this.parseMaybeAssign(e);return this.finishNode(a,"ConditionalExpression")}return n};$.parseExprOps=function(e,t){var r=this.start,i=this.startLoc;var n=this.parseMaybeUnary(t,false);if(this.checkExpressionErrors(t)){return n}return n.start===r&&n.type==="ArrowFunctionExpression"?n:this.parseExprOp(n,r,i,-1,e)};$.parseExprOp=function(e,t,r,i,n){var a=this.type.binop;if(a!=null&&(!n||this.type!==b._in)){if(a>i){var s=this.type===b.logicalOR||this.type===b.logicalAND;var o=this.value;this.next();var l=this.start,u=this.startLoc;var c=this.parseExprOp(this.parseMaybeUnary(null,false),l,u,a,n);var f=this.buildBinary(t,r,e,c,o,s);return this.parseExprOp(f,t,r,i,n)}}return e};$.buildBinary=function(e,t,r,i,n,a){var s=this.startNodeAt(e,t);s.left=r;s.operator=n;s.right=i;return this.finishNode(s,a?"LogicalExpression":"BinaryExpression")};$.parseMaybeUnary=function(e,t){var r=this;var i=this.start,n=this.startLoc,a;if(this.isContextual("await")&&(this.inAsync||!this.inFunction&&this.options.allowAwaitOutsideFunction)){a=this.parseAwait();t=true}else if(this.type.prefix){var s=this.startNode(),o=this.type===b.incDec;s.operator=this.value;s.prefix=true;this.next();s.argument=this.parseMaybeUnary(null,true);this.checkExpressionErrors(e,true);if(o){this.checkLVal(s.argument)}else if(this.strict&&s.operator==="delete"&&s.argument.type==="Identifier"){this.raiseRecoverable(s.start,"Deleting local variable in strict mode")}else{t=true}a=this.finishNode(s,o?"UpdateExpression":"UnaryExpression")}else{a=this.parseExprSubscripts(e);if(this.checkExpressionErrors(e)){return a}while(this.type.postfix&&!this.canInsertSemicolon()){var l=r.startNodeAt(i,n);l.operator=r.value;l.prefix=false;l.argument=a;r.checkLVal(a);r.next();a=r.finishNode(l,"UpdateExpression")}}if(!t&&this.eat(b.starstar)){return this.buildBinary(i,n,a,this.parseMaybeUnary(null,false),"**",false)}else{return a}};$.parseExprSubscripts=function(e){var t=this.start,r=this.startLoc;var i=this.parseExprAtom(e);var n=i.type==="ArrowFunctionExpression"&&this.input.slice(this.lastTokStart,this.lastTokEnd)!==")";if(this.checkExpressionErrors(e)||n){return i}var a=this.parseSubscripts(i,t,r);if(e&&a.type==="MemberExpression"){if(e.parenthesizedAssign>=a.start){e.parenthesizedAssign=-1}if(e.parenthesizedBind>=a.start){e.parenthesizedBind=-1}}return a};$.parseSubscripts=function(e,t,r,i){var n=this;var a=this.options.ecmaVersion>=8&&e.type==="Identifier"&&e.name==="async"&&this.lastTokEnd===e.end&&!this.canInsertSemicolon()&&this.input.slice(e.start,e.end)==="async";for(var s=void 0;;){if((s=n.eat(b.bracketL))||n.eat(b.dot)){var o=n.startNodeAt(t,r);o.object=e;o.property=s?n.parseExpression():n.parseIdent(true);o.computed=!!s;if(s){n.expect(b.bracketR)}e=n.finishNode(o,"MemberExpression")}else if(!i&&n.eat(b.parenL)){var l=new H,u=n.yieldPos,c=n.awaitPos;n.yieldPos=0;n.awaitPos=0;var f=n.parseExprList(b.parenR,n.options.ecmaVersion>=8,false,l);if(a&&!n.canInsertSemicolon()&&n.eat(b.arrow)){n.checkPatternErrors(l,false);n.checkYieldAwaitInDefaultParams();n.yieldPos=u;n.awaitPos=c;return n.parseArrowExpression(n.startNodeAt(t,r),f,true)}n.checkExpressionErrors(l,true);n.yieldPos=u||n.yieldPos;n.awaitPos=c||n.awaitPos;var h=n.startNodeAt(t,r);h.callee=e;h.arguments=f;e=n.finishNode(h,"CallExpression")}else if(n.type===b.backQuote){var p=n.startNodeAt(t,r);p.tag=e;p.quasi=n.parseTemplate({isTagged:true});e=n.finishNode(p,"TaggedTemplateExpression")}else{return e}}};$.parseExprAtom=function(e){var t,r=this.potentialArrowAt===this.start;switch(this.type){case b._super:if(!this.inFunction){this.raise(this.start,"'super' outside of function or class")}t=this.startNode();this.next();if(this.type!==b.dot&&this.type!==b.bracketL&&this.type!==b.parenL){this.unexpected()}return this.finishNode(t,"Super");case b._this:t=this.startNode();this.next();return this.finishNode(t,"ThisExpression");case b.name:var i=this.start,n=this.startLoc,a=this.containsEsc;var s=this.parseIdent(this.type!==b.name);if(this.options.ecmaVersion>=8&&!a&&s.name==="async"&&!this.canInsertSemicolon()&&this.eat(b._function)){return this.parseFunction(this.startNodeAt(i,n),false,false,true)}if(r&&!this.canInsertSemicolon()){if(this.eat(b.arrow)){return this.parseArrowExpression(this.startNodeAt(i,n),[s],false)}if(this.options.ecmaVersion>=8&&s.name==="async"&&this.type===b.name&&!a){s=this.parseIdent();if(this.canInsertSemicolon()||!this.eat(b.arrow)){this.unexpected()}return this.parseArrowExpression(this.startNodeAt(i,n),[s],true)}}return s;case b.regexp:var o=this.value;t=this.parseLiteral(o.value);t.regex={pattern:o.pattern,flags:o.flags};return t;case b.num:case b.string:return this.parseLiteral(this.value);case b._null:case b._true:case b._false:t=this.startNode();t.value=this.type===b._null?null:this.type===b._true;t.raw=this.type.keyword;this.next();return this.finishNode(t,"Literal");case b.parenL:var l=this.start,u=this.parseParenAndDistinguishExpression(r);if(e){if(e.parenthesizedAssign<0&&!this.isSimpleAssignTarget(u)){e.parenthesizedAssign=l}if(e.parenthesizedBind<0){e.parenthesizedBind=l}}return u;case b.bracketL:t=this.startNode();this.next();t.elements=this.parseExprList(b.bracketR,true,true,e);return this.finishNode(t,"ArrayExpression");case b.braceL:return this.parseObj(false,e);case b._function:t=this.startNode();this.next();return this.parseFunction(t,false);case b._class:return this.parseClass(this.startNode(),false);case b._new:return this.parseNew();case b.backQuote:return this.parseTemplate();default:this.unexpected()}};$.parseLiteral=function(e){var t=this.startNode();t.value=e;t.raw=this.input.slice(this.start,this.end);this.next();return this.finishNode(t,"Literal")};$.parseParenExpression=function(){this.expect(b.parenL);var e=this.parseExpression();this.expect(b.parenR);return e};$.parseParenAndDistinguishExpression=function(e){var t=this;var r=this.start,i=this.startLoc,n,a=this.options.ecmaVersion>=8;if(this.options.ecmaVersion>=6){this.next();var s=this.start,o=this.startLoc;var l=[],u=true,c=false;var f=new H,h=this.yieldPos,p=this.awaitPos,d;this.yieldPos=0;this.awaitPos=0;while(this.type!==b.parenR){u?u=false:t.expect(b.comma);if(a&&t.afterTrailingComma(b.parenR,true)){c=true;break}else if(t.type===b.ellipsis){d=t.start;l.push(t.parseParenItem(t.parseRestBinding()));if(t.type===b.comma){t.raise(t.start,"Comma is not permitted after the rest element")}break}else{l.push(t.parseMaybeAssign(false,f,t.parseParenItem))}}var m=this.start,v=this.startLoc;this.expect(b.parenR);if(e&&!this.canInsertSemicolon()&&this.eat(b.arrow)){this.checkPatternErrors(f,false);this.checkYieldAwaitInDefaultParams();this.yieldPos=h;this.awaitPos=p;return this.parseParenArrowList(r,i,l)}if(!l.length||c){this.unexpected(this.lastTokStart)}if(d){this.unexpected(d)}this.checkExpressionErrors(f,true);this.yieldPos=h||this.yieldPos;this.awaitPos=p||this.awaitPos;if(l.length>1){n=this.startNodeAt(s,o);n.expressions=l;this.finishNodeAt(n,"SequenceExpression",m,v)}else{n=l[0]}}else{n=this.parseParenExpression()}if(this.options.preserveParens){var g=this.startNodeAt(r,i);g.expression=n;return this.finishNode(g,"ParenthesizedExpression")}else{return n}};$.parseParenItem=function(e){return e};$.parseParenArrowList=function(e,t,r){return this.parseArrowExpression(this.startNodeAt(e,t),r)};var X=[];$.parseNew=function(){var e=this.startNode();var t=this.parseIdent(true);if(this.options.ecmaVersion>=6&&this.eat(b.dot)){e.meta=t;var r=this.containsEsc;e.property=this.parseIdent(true);if(e.property.name!=="target"||r){this.raiseRecoverable(e.property.start,"The only valid meta property for new is new.target")}if(!this.inFunction){this.raiseRecoverable(e.start,"new.target can only be used in functions")}return this.finishNode(e,"MetaProperty")}var i=this.start,n=this.startLoc;e.callee=this.parseSubscripts(this.parseExprAtom(),i,n,true);if(this.eat(b.parenL)){e.arguments=this.parseExprList(b.parenR,this.options.ecmaVersion>=8,false)}else{e.arguments=X}return this.finishNode(e,"NewExpression")};$.parseTemplateElement=function(e){var t=e.isTagged;var r=this.startNode();if(this.type===b.invalidTemplate){if(!t){this.raiseRecoverable(this.start,"Bad escape sequence in untagged template literal")}r.value={raw:this.value,cooked:null}}else{r.value={raw:this.input.slice(this.start,this.end).replace(/\r\n?/g,"\n"),cooked:this.value}}this.next();r.tail=this.type===b.backQuote;return this.finishNode(r,"TemplateElement")};$.parseTemplate=function(e){var t=this;if(e===void 0)e={};var r=e.isTagged;if(r===void 0)r=false;var i=this.startNode();this.next();i.expressions=[];var n=this.parseTemplateElement({isTagged:r});i.quasis=[n];while(!n.tail){if(t.type===b.eof){t.raise(t.pos,"Unterminated template literal")}t.expect(b.dollarBraceL);i.expressions.push(t.parseExpression());t.expect(b.braceR);i.quasis.push(n=t.parseTemplateElement({isTagged:r}))}this.next();return this.finishNode(i,"TemplateLiteral")};$.isAsyncProp=function(e){return!e.computed&&e.key.type==="Identifier"&&e.key.name==="async"&&(this.type===b.name||this.type===b.num||this.type===b.string||this.type===b.bracketL||this.type.keyword||this.options.ecmaVersion>=9&&this.type===b.star)&&!w.test(this.input.slice(this.lastTokEnd,this.start))};$.parseObj=function(e,t){var r=this;var i=this.startNode(),n=true,a={};i.properties=[];this.next();while(!this.eat(b.braceR)){if(!n){r.expect(b.comma);if(r.afterTrailingComma(b.braceR)){break}}else{n=false}var s=r.parseProperty(e,t);if(!e){r.checkPropClash(s,a,t)}i.properties.push(s)}return this.finishNode(i,e?"ObjectPattern":"ObjectExpression")};$.parseProperty=function(e,t){var r=this.startNode(),i,n,a,s;if(this.options.ecmaVersion>=9&&this.eat(b.ellipsis)){if(e){r.argument=this.parseIdent(false);if(this.type===b.comma){this.raise(this.start,"Comma is not permitted after the rest element")}return this.finishNode(r,"RestElement")}if(this.type===b.parenL&&t){if(t.parenthesizedAssign<0){t.parenthesizedAssign=this.start}if(t.parenthesizedBind<0){t.parenthesizedBind=this.start}}r.argument=this.parseMaybeAssign(false,t);if(this.type===b.comma&&t&&t.trailingComma<0){t.trailingComma=this.start}return this.finishNode(r,"SpreadElement")}if(this.options.ecmaVersion>=6){r.method=false;r.shorthand=false;if(e||t){a=this.start;s=this.startLoc}if(!e){i=this.eat(b.star)}}var o=this.containsEsc;this.parsePropertyName(r);if(!e&&!o&&this.options.ecmaVersion>=8&&!i&&this.isAsyncProp(r)){n=true;i=this.options.ecmaVersion>=9&&this.eat(b.star);this.parsePropertyName(r,t)}else{n=false}this.parsePropertyValue(r,e,i,n,a,s,t,o);return this.finishNode(r,"Property")};$.parsePropertyValue=function(e,t,r,i,n,a,s,o){if((r||i)&&this.type===b.colon){this.unexpected()}if(this.eat(b.colon)){e.value=t?this.parseMaybeDefault(this.start,this.startLoc):this.parseMaybeAssign(false,s);e.kind="init"}else if(this.options.ecmaVersion>=6&&this.type===b.parenL){if(t){this.unexpected()}e.kind="init";e.method=true;e.value=this.parseMethod(r,i)}else if(!t&&!o&&this.options.ecmaVersion>=5&&!e.computed&&e.key.type==="Identifier"&&(e.key.name==="get"||e.key.name==="set")&&(this.type!==b.comma&&this.type!==b.braceR)){if(r||i){this.unexpected()}e.kind=e.key.name;this.parsePropertyName(e);e.value=this.parseMethod(false);var l=e.kind==="get"?0:1;if(e.value.params.length!==l){var u=e.value.start;if(e.kind==="get"){this.raiseRecoverable(u,"getter should have no params")}else{this.raiseRecoverable(u,"setter should have exactly one param")}}else{if(e.kind==="set"&&e.value.params[0].type==="RestElement"){this.raiseRecoverable(e.value.params[0].start,"Setter cannot use rest params")}}}else if(this.options.ecmaVersion>=6&&!e.computed&&e.key.type==="Identifier"){this.checkUnreserved(e.key);e.kind="init";if(t){e.value=this.parseMaybeDefault(n,a,e.key)}else if(this.type===b.eq&&s){if(s.shorthandAssign<0){s.shorthandAssign=this.start}e.value=this.parseMaybeDefault(n,a,e.key)}else{e.value=e.key}e.shorthand=true}else{this.unexpected()}};$.parsePropertyName=function(e){if(this.options.ecmaVersion>=6){if(this.eat(b.bracketL)){e.computed=true;e.key=this.parseMaybeAssign();this.expect(b.bracketR);return e.key}else{e.computed=false}}return e.key=this.type===b.num||this.type===b.string?this.parseExprAtom():this.parseIdent(true)};$.initFunction=function(e){e.id=null;if(this.options.ecmaVersion>=6){e.generator=false;e.expression=false}if(this.options.ecmaVersion>=8){e.async=false}};$.parseMethod=function(e,t){var r=this.startNode(),i=this.inGenerator,n=this.inAsync,a=this.yieldPos,s=this.awaitPos,o=this.inFunction;this.initFunction(r);if(this.options.ecmaVersion>=6){r.generator=e}if(this.options.ecmaVersion>=8){r.async=!!t}this.inGenerator=r.generator;this.inAsync=r.async;this.yieldPos=0;this.awaitPos=0;this.inFunction=true;this.enterFunctionScope();this.expect(b.parenL);r.params=this.parseBindingList(b.parenR,false,this.options.ecmaVersion>=8);this.checkYieldAwaitInDefaultParams();this.parseFunctionBody(r,false);this.inGenerator=i;this.inAsync=n;this.yieldPos=a;this.awaitPos=s;this.inFunction=o;return this.finishNode(r,"FunctionExpression")};$.parseArrowExpression=function(e,t,r){var i=this.inGenerator,n=this.inAsync,a=this.yieldPos,s=this.awaitPos,o=this.inFunction;this.enterFunctionScope();this.initFunction(e);if(this.options.ecmaVersion>=8){e.async=!!r}this.inGenerator=false;this.inAsync=e.async;this.yieldPos=0;this.awaitPos=0;this.inFunction=true;e.params=this.toAssignableList(t,true);this.parseFunctionBody(e,true);this.inGenerator=i;this.inAsync=n;this.yieldPos=a;this.awaitPos=s;this.inFunction=o;return this.finishNode(e,"ArrowFunctionExpression")};$.parseFunctionBody=function(e,t){var r=t&&this.type!==b.braceL;var i=this.strict,n=false;if(r){e.body=this.parseMaybeAssign();e.expression=true;this.checkParams(e,false)}else{var a=this.options.ecmaVersion>=7&&!this.isSimpleParamList(e.params);if(!i||a){n=this.strictDirective(this.end);if(n&&a){this.raiseRecoverable(e.start,"Illegal 'use strict' directive in function with non-simple parameter list")}}var s=this.labels;this.labels=[];if(n){this.strict=true}this.checkParams(e,!i&&!n&&!t&&this.isSimpleParamList(e.params));e.body=this.parseBlock(false);e.expression=false;this.adaptDirectivePrologue(e.body.body);this.labels=s}this.exitFunctionScope();if(this.strict&&e.id){ +this.checkLVal(e.id,"none")}this.strict=i};$.isSimpleParamList=function(e){for(var t=0,r=e;t0)t[r]=arguments[r+1];for(var i=0,n=t;i=1;t--){var r=e.context[t];if(r.token==="function"){return r.generator}}return false};ne.updateContext=function(e){var t,r=this.type;if(r.keyword&&e===b.dot){this.exprAllowed=false}else if(t=r.updateContext){t.call(this,e)}else{this.exprAllowed=r.beforeExpr}};b.parenR.updateContext=b.braceR.updateContext=function(){if(this.context.length===1){this.exprAllowed=true;return}var e=this.context.pop();if(e===ie.b_stat&&this.curContext().token==="function"){e=this.context.pop()}this.exprAllowed=!e.isExpr};b.braceL.updateContext=function(e){this.context.push(this.braceIsBlock(e)?ie.b_stat:ie.b_expr);this.exprAllowed=true};b.dollarBraceL.updateContext=function(){this.context.push(ie.b_tmpl);this.exprAllowed=true};b.parenL.updateContext=function(e){var t=e===b._if||e===b._for||e===b._with||e===b._while;this.context.push(t?ie.p_stat:ie.p_expr);this.exprAllowed=true};b.incDec.updateContext=function(){};b._function.updateContext=b._class.updateContext=function(e){if(e.beforeExpr&&e!==b.semi&&e!==b._else&&!((e===b.colon||e===b.braceL)&&this.curContext()===ie.b_stat)){this.context.push(ie.f_expr)}else{this.context.push(ie.f_stat)}this.exprAllowed=false};b.backQuote.updateContext=function(){if(this.curContext()===ie.q_tmpl){this.context.pop()}else{this.context.push(ie.q_tmpl)}this.exprAllowed=false};b.star.updateContext=function(e){if(e===b._function){var t=this.context.length-1;if(this.context[t]===ie.f_expr){this.context[t]=ie.f_expr_gen}else{this.context[t]=ie.f_gen}}this.exprAllowed=true};b.name.updateContext=function(e){var t=false;if(this.options.ecmaVersion>=6&&e!==b.dot){if(this.value==="of"&&!this.exprAllowed||this.value==="yield"&&this.inGeneratorContext()){t=true}}this.exprAllowed=t};var ae={$LONE:["ASCII","ASCII_Hex_Digit","AHex","Alphabetic","Alpha","Any","Assigned","Bidi_Control","Bidi_C","Bidi_Mirrored","Bidi_M","Case_Ignorable","CI","Cased","Changes_When_Casefolded","CWCF","Changes_When_Casemapped","CWCM","Changes_When_Lowercased","CWL","Changes_When_NFKC_Casefolded","CWKCF","Changes_When_Titlecased","CWT","Changes_When_Uppercased","CWU","Dash","Default_Ignorable_Code_Point","DI","Deprecated","Dep","Diacritic","Dia","Emoji","Emoji_Component","Emoji_Modifier","Emoji_Modifier_Base","Emoji_Presentation","Extender","Ext","Grapheme_Base","Gr_Base","Grapheme_Extend","Gr_Ext","Hex_Digit","Hex","IDS_Binary_Operator","IDSB","IDS_Trinary_Operator","IDST","ID_Continue","IDC","ID_Start","IDS","Ideographic","Ideo","Join_Control","Join_C","Logical_Order_Exception","LOE","Lowercase","Lower","Math","Noncharacter_Code_Point","NChar","Pattern_Syntax","Pat_Syn","Pattern_White_Space","Pat_WS","Quotation_Mark","QMark","Radical","Regional_Indicator","RI","Sentence_Terminal","STerm","Soft_Dotted","SD","Terminal_Punctuation","Term","Unified_Ideograph","UIdeo","Uppercase","Upper","Variation_Selector","VS","White_Space","space","XID_Continue","XIDC","XID_Start","XIDS"],General_Category:["Cased_Letter","LC","Close_Punctuation","Pe","Connector_Punctuation","Pc","Control","Cc","cntrl","Currency_Symbol","Sc","Dash_Punctuation","Pd","Decimal_Number","Nd","digit","Enclosing_Mark","Me","Final_Punctuation","Pf","Format","Cf","Initial_Punctuation","Pi","Letter","L","Letter_Number","Nl","Line_Separator","Zl","Lowercase_Letter","Ll","Mark","M","Combining_Mark","Math_Symbol","Sm","Modifier_Letter","Lm","Modifier_Symbol","Sk","Nonspacing_Mark","Mn","Number","N","Open_Punctuation","Ps","Other","C","Other_Letter","Lo","Other_Number","No","Other_Punctuation","Po","Other_Symbol","So","Paragraph_Separator","Zp","Private_Use","Co","Punctuation","P","punct","Separator","Z","Space_Separator","Zs","Spacing_Mark","Mc","Surrogate","Cs","Symbol","S","Titlecase_Letter","Lt","Unassigned","Cn","Uppercase_Letter","Lu"],Script:["Adlam","Adlm","Ahom","Anatolian_Hieroglyphs","Hluw","Arabic","Arab","Armenian","Armn","Avestan","Avst","Balinese","Bali","Bamum","Bamu","Bassa_Vah","Bass","Batak","Batk","Bengali","Beng","Bhaiksuki","Bhks","Bopomofo","Bopo","Brahmi","Brah","Braille","Brai","Buginese","Bugi","Buhid","Buhd","Canadian_Aboriginal","Cans","Carian","Cari","Caucasian_Albanian","Aghb","Chakma","Cakm","Cham","Cherokee","Cher","Common","Zyyy","Coptic","Copt","Qaac","Cuneiform","Xsux","Cypriot","Cprt","Cyrillic","Cyrl","Deseret","Dsrt","Devanagari","Deva","Duployan","Dupl","Egyptian_Hieroglyphs","Egyp","Elbasan","Elba","Ethiopic","Ethi","Georgian","Geor","Glagolitic","Glag","Gothic","Goth","Grantha","Gran","Greek","Grek","Gujarati","Gujr","Gurmukhi","Guru","Han","Hani","Hangul","Hang","Hanunoo","Hano","Hatran","Hatr","Hebrew","Hebr","Hiragana","Hira","Imperial_Aramaic","Armi","Inherited","Zinh","Qaai","Inscriptional_Pahlavi","Phli","Inscriptional_Parthian","Prti","Javanese","Java","Kaithi","Kthi","Kannada","Knda","Katakana","Kana","Kayah_Li","Kali","Kharoshthi","Khar","Khmer","Khmr","Khojki","Khoj","Khudawadi","Sind","Lao","Laoo","Latin","Latn","Lepcha","Lepc","Limbu","Limb","Linear_A","Lina","Linear_B","Linb","Lisu","Lycian","Lyci","Lydian","Lydi","Mahajani","Mahj","Malayalam","Mlym","Mandaic","Mand","Manichaean","Mani","Marchen","Marc","Masaram_Gondi","Gonm","Meetei_Mayek","Mtei","Mende_Kikakui","Mend","Meroitic_Cursive","Merc","Meroitic_Hieroglyphs","Mero","Miao","Plrd","Modi","Mongolian","Mong","Mro","Mroo","Multani","Mult","Myanmar","Mymr","Nabataean","Nbat","New_Tai_Lue","Talu","Newa","Nko","Nkoo","Nushu","Nshu","Ogham","Ogam","Ol_Chiki","Olck","Old_Hungarian","Hung","Old_Italic","Ital","Old_North_Arabian","Narb","Old_Permic","Perm","Old_Persian","Xpeo","Old_South_Arabian","Sarb","Old_Turkic","Orkh","Oriya","Orya","Osage","Osge","Osmanya","Osma","Pahawh_Hmong","Hmng","Palmyrene","Palm","Pau_Cin_Hau","Pauc","Phags_Pa","Phag","Phoenician","Phnx","Psalter_Pahlavi","Phlp","Rejang","Rjng","Runic","Runr","Samaritan","Samr","Saurashtra","Saur","Sharada","Shrd","Shavian","Shaw","Siddham","Sidd","SignWriting","Sgnw","Sinhala","Sinh","Sora_Sompeng","Sora","Soyombo","Soyo","Sundanese","Sund","Syloti_Nagri","Sylo","Syriac","Syrc","Tagalog","Tglg","Tagbanwa","Tagb","Tai_Le","Tale","Tai_Tham","Lana","Tai_Viet","Tavt","Takri","Takr","Tamil","Taml","Tangut","Tang","Telugu","Telu","Thaana","Thaa","Thai","Tibetan","Tibt","Tifinagh","Tfng","Tirhuta","Tirh","Ugaritic","Ugar","Vai","Vaii","Warang_Citi","Wara","Yi","Yiii","Zanabazar_Square","Zanb"]};Array.prototype.push.apply(ae.$LONE,ae.General_Category);ae.gc=ae.General_Category;ae.sc=ae.Script_Extensions=ae.scx=ae.Script;var se=W.prototype;var oe=function e(t){this.parser=t;this.validFlags="gim"+(t.options.ecmaVersion>=6?"uy":"")+(t.options.ecmaVersion>=9?"s":"");this.source="";this.flags="";this.start=0;this.switchU=false;this.switchN=false;this.pos=0;this.lastIntValue=0;this.lastStringValue="";this.lastAssertionIsQuantifiable=false;this.numCapturingParens=0;this.maxBackReference=0;this.groupNames=[];this.backReferenceNames=[]};oe.prototype.reset=function e(t,r,i){var n=i.indexOf("u")!==-1;this.start=t|0;this.source=r+"";this.flags=i;this.switchU=n&&this.parser.options.ecmaVersion>=6;this.switchN=n&&this.parser.options.ecmaVersion>=9};oe.prototype.raise=function e(t){this.parser.raiseRecoverable(this.start,"Invalid regular expression: /"+this.source+"/: "+t)};oe.prototype.at=function e(t){var r=this.source;var i=r.length;if(t>=i){return-1}var n=r.charCodeAt(t);if(!this.switchU||n<=55295||n>=57344||t+1>=i){return n}return(n<<10)+r.charCodeAt(t+1)-56613888};oe.prototype.nextIndex=function e(t){var r=this.source;var i=r.length;if(t>=i){return i}var n=r.charCodeAt(t);if(!this.switchU||n<=55295||n>=57344||t+1>=i){return t+1}return t+2};oe.prototype.current=function e(){return this.at(this.pos)};oe.prototype.lookahead=function e(){return this.at(this.nextIndex(this.pos))};oe.prototype.advance=function e(){this.pos=this.nextIndex(this.pos)};oe.prototype.eat=function e(t){if(this.current()===t){this.advance();return true}return false};function le(e){if(e<=65535){return String.fromCharCode(e)}e-=65536;return String.fromCharCode((e>>10)+55296,(e&1023)+56320)}se.validateRegExpFlags=function(e){var t=this;var r=e.validFlags;var i=e.flags;for(var n=0;n-1){t.raise(e.start,"Duplicate regular expression flag")}}};se.validateRegExpPattern=function(e){this.regexp_pattern(e);if(!e.switchN&&this.options.ecmaVersion>=9&&e.groupNames.length>0){e.switchN=true;this.regexp_pattern(e)}};se.regexp_pattern=function(e){e.pos=0;e.lastIntValue=0;e.lastStringValue="";e.lastAssertionIsQuantifiable=false;e.numCapturingParens=0;e.maxBackReference=0;e.groupNames.length=0;e.backReferenceNames.length=0;this.regexp_disjunction(e);if(e.pos!==e.source.length){if(e.eat(41)){e.raise("Unmatched ')'")}if(e.eat(93)||e.eat(125)){e.raise("Lone quantifier brackets")}}if(e.maxBackReference>e.numCapturingParens){e.raise("Invalid escape")}for(var t=0,r=e.backReferenceNames;t=9){r=e.eat(60)}if(e.eat(61)||e.eat(33)){this.regexp_disjunction(e);if(!e.eat(41)){e.raise("Unterminated group")}e.lastAssertionIsQuantifiable=!r;return true}}e.pos=t;return false};se.regexp_eatQuantifier=function(e,t){if(t===void 0)t=false;if(this.regexp_eatQuantifierPrefix(e,t)){e.eat(63);return true}return false};se.regexp_eatQuantifierPrefix=function(e,t){return e.eat(42)||e.eat(43)||e.eat(63)||this.regexp_eatBracedQuantifier(e,t)};se.regexp_eatBracedQuantifier=function(e,t){var r=e.pos;if(e.eat(123)){var i=0,n=-1;if(this.regexp_eatDecimalDigits(e)){i=e.lastIntValue;if(e.eat(44)&&this.regexp_eatDecimalDigits(e)){n=e.lastIntValue}if(e.eat(125)){if(n!==-1&&n=9){this.regexp_groupSpecifier(e)}else if(e.current()===63){e.raise("Invalid group")}this.regexp_disjunction(e);if(e.eat(41)){e.numCapturingParens+=1;return true}e.raise("Unterminated group")}return false};se.regexp_eatExtendedAtom=function(e){return e.eat(46)||this.regexp_eatReverseSolidusAtomEscape(e)||this.regexp_eatCharacterClass(e)||this.regexp_eatUncapturingGroup(e)||this.regexp_eatCapturingGroup(e)||this.regexp_eatInvalidBracedQuantifier(e)||this.regexp_eatExtendedPatternCharacter(e)};se.regexp_eatInvalidBracedQuantifier=function(e){if(this.regexp_eatBracedQuantifier(e,true)){e.raise("Nothing to repeat")}return false};se.regexp_eatSyntaxCharacter=function(e){var t=e.current();if(ue(t)){e.lastIntValue=t;e.advance();return true}return false};function ue(e){return e===36||e>=40&&e<=43||e===46||e===63||e>=91&&e<=94||e>=123&&e<=125}se.regexp_eatPatternCharacters=function(e){var t=e.pos;var r=0;while((r=e.current())!==-1&&!ue(r)){e.advance()}return e.pos!==t};se.regexp_eatExtendedPatternCharacter=function(e){var t=e.current();if(t!==-1&&t!==36&&!(t>=40&&t<=43)&&t!==46&&t!==63&&t!==91&&t!==94&&t!==124){e.advance();return true}return false};se.regexp_groupSpecifier=function(e){if(e.eat(63)){if(this.regexp_eatGroupName(e)){if(e.groupNames.indexOf(e.lastStringValue)!==-1){e.raise("Duplicate capture group name")}e.groupNames.push(e.lastStringValue);return}e.raise("Invalid group")}};se.regexp_eatGroupName=function(e){e.lastStringValue="";if(e.eat(60)){if(this.regexp_eatRegExpIdentifierName(e)&&e.eat(62)){return true}e.raise("Invalid capture group name")}return false};se.regexp_eatRegExpIdentifierName=function(e){e.lastStringValue="";if(this.regexp_eatRegExpIdentifierStart(e)){e.lastStringValue+=le(e.lastIntValue);while(this.regexp_eatRegExpIdentifierPart(e)){e.lastStringValue+=le(e.lastIntValue)}return true}return false};se.regexp_eatRegExpIdentifierStart=function(e){var t=e.pos;var r=e.current();e.advance();if(r===92&&this.regexp_eatRegExpUnicodeEscapeSequence(e)){r=e.lastIntValue}if(ce(r)){e.lastIntValue=r;return true}e.pos=t;return false};function ce(e){return h(e,true)||e===36||e===95}se.regexp_eatRegExpIdentifierPart=function(e){var t=e.pos;var r=e.current();e.advance();if(r===92&&this.regexp_eatRegExpUnicodeEscapeSequence(e)){r=e.lastIntValue}if(fe(r)){e.lastIntValue=r;return true}e.pos=t;return false};function fe(e){return p(e,true)||e===36||e===95||e===8204||e===8205}se.regexp_eatAtomEscape=function(e){if(this.regexp_eatBackReference(e)||this.regexp_eatCharacterClassEscape(e)||this.regexp_eatCharacterEscape(e)||e.switchN&&this.regexp_eatKGroupName(e)){return true}if(e.switchU){if(e.current()===99){e.raise("Invalid unicode escape")}e.raise("Invalid escape")}return false};se.regexp_eatBackReference=function(e){var t=e.pos;if(this.regexp_eatDecimalEscape(e)){var r=e.lastIntValue;if(e.switchU){if(r>e.maxBackReference){e.maxBackReference=r}return true}if(r<=e.numCapturingParens){return true}e.pos=t}return false};se.regexp_eatKGroupName=function(e){if(e.eat(107)){if(this.regexp_eatGroupName(e)){e.backReferenceNames.push(e.lastStringValue);return true}e.raise("Invalid named reference")}return false};se.regexp_eatCharacterEscape=function(e){return this.regexp_eatControlEscape(e)||this.regexp_eatCControlLetter(e)||this.regexp_eatZero(e)||this.regexp_eatHexEscapeSequence(e)||this.regexp_eatRegExpUnicodeEscapeSequence(e)||!e.switchU&&this.regexp_eatLegacyOctalEscapeSequence(e)||this.regexp_eatIdentityEscape(e)};se.regexp_eatCControlLetter=function(e){var t=e.pos;if(e.eat(99)){if(this.regexp_eatControlLetter(e)){return true}e.pos=t}return false};se.regexp_eatZero=function(e){if(e.current()===48&&!ge(e.lookahead())){e.lastIntValue=0;e.advance();return true}return false};se.regexp_eatControlEscape=function(e){var t=e.current();if(t===116){e.lastIntValue=9;e.advance();return true}if(t===110){e.lastIntValue=10;e.advance();return true}if(t===118){e.lastIntValue=11;e.advance();return true}if(t===102){e.lastIntValue=12;e.advance();return true}if(t===114){e.lastIntValue=13;e.advance();return true}return false};se.regexp_eatControlLetter=function(e){var t=e.current();if(he(t)){e.lastIntValue=t%32;e.advance();return true}return false};function he(e){return e>=65&&e<=90||e>=97&&e<=122}se.regexp_eatRegExpUnicodeEscapeSequence=function(e){var t=e.pos;if(e.eat(117)){if(this.regexp_eatFixedHexDigits(e,4)){var r=e.lastIntValue;if(e.switchU&&r>=55296&&r<=56319){var i=e.pos;if(e.eat(92)&&e.eat(117)&&this.regexp_eatFixedHexDigits(e,4)){var n=e.lastIntValue;if(n>=56320&&n<=57343){e.lastIntValue=(r-55296)*1024+(n-56320)+65536;return true}}e.pos=i;e.lastIntValue=r}return true}if(e.switchU&&e.eat(123)&&this.regexp_eatHexDigits(e)&&e.eat(125)&&pe(e.lastIntValue)){return true}if(e.switchU){e.raise("Invalid unicode escape")}e.pos=t}return false};function pe(e){return e>=0&&e<=1114111}se.regexp_eatIdentityEscape=function(e){if(e.switchU){if(this.regexp_eatSyntaxCharacter(e)){return true}if(e.eat(47)){e.lastIntValue=47;return true}return false}var t=e.current();if(t!==99&&(!e.switchN||t!==107)){e.lastIntValue=t;e.advance();return true}return false};se.regexp_eatDecimalEscape=function(e){e.lastIntValue=0;var t=e.current();if(t>=49&&t<=57){do{e.lastIntValue=10*e.lastIntValue+(t-48);e.advance()}while((t=e.current())>=48&&t<=57);return true}return false};se.regexp_eatCharacterClassEscape=function(e){var t=e.current();if(de(t)){e.lastIntValue=-1;e.advance();return true}if(e.switchU&&this.options.ecmaVersion>=9&&(t===80||t===112)){e.lastIntValue=-1;e.advance();if(e.eat(123)&&this.regexp_eatUnicodePropertyValueExpression(e)&&e.eat(125)){return true}e.raise("Invalid property name")}return false};function de(e){return e===100||e===68||e===115||e===83||e===119||e===87}se.regexp_eatUnicodePropertyValueExpression=function(e){var t=e.pos;if(this.regexp_eatUnicodePropertyName(e)&&e.eat(61)){var r=e.lastStringValue;if(this.regexp_eatUnicodePropertyValue(e)){var i=e.lastStringValue;this.regexp_validateUnicodePropertyNameAndValue(e,r,i);return true}}e.pos=t;if(this.regexp_eatLoneUnicodePropertyNameOrValue(e)){var n=e.lastStringValue;this.regexp_validateUnicodePropertyNameOrValue(e,n);return true}return false};se.regexp_validateUnicodePropertyNameAndValue=function(e,t,r){if(!ae.hasOwnProperty(t)||ae[t].indexOf(r)===-1){e.raise("Invalid property name")}};se.regexp_validateUnicodePropertyNameOrValue=function(e,t){if(ae.$LONE.indexOf(t)===-1){e.raise("Invalid property name")}};se.regexp_eatUnicodePropertyName=function(e){var t=0;e.lastStringValue="";while(me(t=e.current())){e.lastStringValue+=le(t);e.advance()}return e.lastStringValue!==""};function me(e){return he(e)||e===95}se.regexp_eatUnicodePropertyValue=function(e){var t=0;e.lastStringValue="";while(ve(t=e.current())){e.lastStringValue+=le(t);e.advance()}return e.lastStringValue!==""};function ve(e){return me(e)||ge(e)}se.regexp_eatLoneUnicodePropertyNameOrValue=function(e){return this.regexp_eatUnicodePropertyValue(e)};se.regexp_eatCharacterClass=function(e){if(e.eat(91)){e.eat(94);this.regexp_classRanges(e);if(e.eat(93)){return true}e.raise("Unterminated character class")}return false};se.regexp_classRanges=function(e){var t=this;while(this.regexp_eatClassAtom(e)){var r=e.lastIntValue;if(e.eat(45)&&t.regexp_eatClassAtom(e)){var i=e.lastIntValue;if(e.switchU&&(r===-1||i===-1)){e.raise("Invalid character class")}if(r!==-1&&i!==-1&&r>i){e.raise("Range out of order in character class")}}}};se.regexp_eatClassAtom=function(e){var t=e.pos;if(e.eat(92)){if(this.regexp_eatClassEscape(e)){return true}if(e.switchU){var r=e.current();if(r===99||be(r)){e.raise("Invalid class escape")}e.raise("Invalid escape")}e.pos=t}var i=e.current();if(i!==93){e.lastIntValue=i;e.advance();return true}return false};se.regexp_eatClassEscape=function(e){var t=e.pos;if(e.eat(98)){e.lastIntValue=8;return true}if(e.switchU&&e.eat(45)){e.lastIntValue=45;return true}if(!e.switchU&&e.eat(99)){if(this.regexp_eatClassControlLetter(e)){return true}e.pos=t}return this.regexp_eatCharacterClassEscape(e)||this.regexp_eatCharacterEscape(e)};se.regexp_eatClassControlLetter=function(e){var t=e.current();if(ge(t)||t===95){e.lastIntValue=t%32;e.advance();return true}return false};se.regexp_eatHexEscapeSequence=function(e){var t=e.pos;if(e.eat(120)){if(this.regexp_eatFixedHexDigits(e,2)){return true}if(e.switchU){e.raise("Invalid escape")}e.pos=t}return false};se.regexp_eatDecimalDigits=function(e){var t=e.pos;var r=0;e.lastIntValue=0;while(ge(r=e.current())){e.lastIntValue=10*e.lastIntValue+(r-48);e.advance()}return e.pos!==t};function ge(e){return e>=48&&e<=57}se.regexp_eatHexDigits=function(e){var t=e.pos;var r=0;e.lastIntValue=0;while(ye(r=e.current())){e.lastIntValue=16*e.lastIntValue+xe(r);e.advance()}return e.pos!==t};function ye(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function xe(e){if(e>=65&&e<=70){return 10+(e-65)}if(e>=97&&e<=102){return 10+(e-97)}return e-48}se.regexp_eatLegacyOctalEscapeSequence=function(e){if(this.regexp_eatOctalDigit(e)){var t=e.lastIntValue;if(this.regexp_eatOctalDigit(e)){var r=e.lastIntValue;if(t<=3&&this.regexp_eatOctalDigit(e)){e.lastIntValue=t*64+r*8+e.lastIntValue}else{e.lastIntValue=t*8+r}}else{e.lastIntValue=t}return true}return false};se.regexp_eatOctalDigit=function(e){var t=e.current();if(be(t)){e.lastIntValue=t-48;e.advance();return true}e.lastIntValue=0;return false};function be(e){return e>=48&&e<=55}se.regexp_eatFixedHexDigits=function(e,t){var r=e.pos;e.lastIntValue=0;for(var i=0;i=this.input.length){return this.finishToken(b.eof)}if(e.override){return e.override(this)}else{this.readToken(this.fullCharCodeAtPos())}};ke.readToken=function(e){if(h(e,this.options.ecmaVersion>=6)||e===92){return this.readWord()}return this.getTokenFromCode(e)};ke.fullCharCodeAtPos=function(){var e=this.input.charCodeAt(this.pos);if(e<=55295||e>=57344){return e}var t=this.input.charCodeAt(this.pos+1);return(e<<10)+t-56613888};ke.skipBlockComment=function(){var e=this;var t=this.options.onComment&&this.curPosition();var r=this.pos,i=this.input.indexOf("*/",this.pos+=2);if(i===-1){this.raise(this.pos-2,"Unterminated comment")}this.pos=i+2;if(this.options.locations){k.lastIndex=r;var n;while((n=k.exec(this.input))&&n.index8&&t<14||t>=5760&&S.test(String.fromCharCode(t))){++e.pos}else{break e}}}};ke.finishToken=function(e,t){this.end=this.pos;if(this.options.locations){this.endLoc=this.curPosition()}var r=this.type;this.type=e;this.value=t;this.updateContext(r)};ke.readToken_dot=function(){var e=this.input.charCodeAt(this.pos+1);if(e>=48&&e<=57){return this.readNumber(true)}var t=this.input.charCodeAt(this.pos+2);if(this.options.ecmaVersion>=6&&e===46&&t===46){this.pos+=3;return this.finishToken(b.ellipsis)}else{++this.pos;return this.finishToken(b.dot)}};ke.readToken_slash=function(){var e=this.input.charCodeAt(this.pos+1);if(this.exprAllowed){++this.pos;return this.readRegexp()}if(e===61){return this.finishOp(b.assign,2)}return this.finishOp(b.slash,1)};ke.readToken_mult_modulo_exp=function(e){var t=this.input.charCodeAt(this.pos+1);var r=1;var i=e===42?b.star:b.modulo;if(this.options.ecmaVersion>=7&&e===42&&t===42){++r;i=b.starstar;t=this.input.charCodeAt(this.pos+2)}if(t===61){return this.finishOp(b.assign,r+1)}return this.finishOp(i,r)};ke.readToken_pipe_amp=function(e){var t=this.input.charCodeAt(this.pos+1);if(t===e){return this.finishOp(e===124?b.logicalOR:b.logicalAND,2)}if(t===61){return this.finishOp(b.assign,2)}return this.finishOp(e===124?b.bitwiseOR:b.bitwiseAND,1)};ke.readToken_caret=function(){var e=this.input.charCodeAt(this.pos+1);if(e===61){return this.finishOp(b.assign,2)}return this.finishOp(b.bitwiseXOR,1)};ke.readToken_plus_min=function(e){var t=this.input.charCodeAt(this.pos+1);if(t===e){if(t===45&&!this.inModule&&this.input.charCodeAt(this.pos+2)===62&&(this.lastTokEnd===0||w.test(this.input.slice(this.lastTokEnd,this.pos)))){this.skipLineComment(3);this.skipSpace();return this.nextToken()}return this.finishOp(b.incDec,2)}if(t===61){return this.finishOp(b.assign,2)}return this.finishOp(b.plusMin,1)};ke.readToken_lt_gt=function(e){var t=this.input.charCodeAt(this.pos+1);var r=1;if(t===e){r=e===62&&this.input.charCodeAt(this.pos+2)===62?3:2;if(this.input.charCodeAt(this.pos+r)===61){return this.finishOp(b.assign,r+1)}return this.finishOp(b.bitShift,r)}if(t===33&&e===60&&!this.inModule&&this.input.charCodeAt(this.pos+2)===45&&this.input.charCodeAt(this.pos+3)===45){this.skipLineComment(4);this.skipSpace();return this.nextToken()}if(t===61){r=2}return this.finishOp(b.relational,r)};ke.readToken_eq_excl=function(e){var t=this.input.charCodeAt(this.pos+1);if(t===61){return this.finishOp(b.equality,this.input.charCodeAt(this.pos+2)===61?3:2)}if(e===61&&t===62&&this.options.ecmaVersion>=6){this.pos+=2;return this.finishToken(b.arrow)}return this.finishOp(e===61?b.eq:b.prefix,1)};ke.getTokenFromCode=function(e){switch(e){case 46:return this.readToken_dot();case 40:++this.pos;return this.finishToken(b.parenL);case 41:++this.pos;return this.finishToken(b.parenR);case 59:++this.pos;return this.finishToken(b.semi);case 44:++this.pos;return this.finishToken(b.comma);case 91:++this.pos +;return this.finishToken(b.bracketL);case 93:++this.pos;return this.finishToken(b.bracketR);case 123:++this.pos;return this.finishToken(b.braceL);case 125:++this.pos;return this.finishToken(b.braceR);case 58:++this.pos;return this.finishToken(b.colon);case 63:++this.pos;return this.finishToken(b.question);case 96:if(this.options.ecmaVersion<6){break}++this.pos;return this.finishToken(b.backQuote);case 48:var t=this.input.charCodeAt(this.pos+1);if(t===120||t===88){return this.readRadixNumber(16)}if(this.options.ecmaVersion>=6){if(t===111||t===79){return this.readRadixNumber(8)}if(t===98||t===66){return this.readRadixNumber(2)}}case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return this.readNumber(false);case 34:case 39:return this.readString(e);case 47:return this.readToken_slash();case 37:case 42:return this.readToken_mult_modulo_exp(e);case 124:case 38:return this.readToken_pipe_amp(e);case 94:return this.readToken_caret();case 43:case 45:return this.readToken_plus_min(e);case 60:case 62:return this.readToken_lt_gt(e);case 61:case 33:return this.readToken_eq_excl(e);case 126:return this.finishOp(b.prefix,1)}this.raise(this.pos,"Unexpected character '"+Ce(e)+"'")};ke.finishOp=function(e,t){var r=this.input.slice(this.pos,this.pos+t);this.pos+=t;return this.finishToken(e,r)};ke.readRegexp=function(){var e=this;var t,r,i=this.pos;for(;;){if(e.pos>=e.input.length){e.raise(i,"Unterminated regular expression")}var n=e.input.charAt(e.pos);if(w.test(n)){e.raise(i,"Unterminated regular expression")}if(!t){if(n==="["){r=true}else if(n==="]"&&r){r=false}else if(n==="/"&&!r){break}t=n==="\\"}else{t=false}++e.pos}var a=this.input.slice(i,this.pos);++this.pos;var s=this.pos;var o=this.readWord1();if(this.containsEsc){this.unexpected(s)}var l=this.regexpState||(this.regexpState=new oe(this));l.reset(i,a,o);this.validateRegExpFlags(l);this.validateRegExpPattern(l);var u=null;try{u=new RegExp(a,o)}catch(e){}return this.finishToken(b.regexp,{pattern:a,flags:o,value:u})};ke.readInt=function(e,t){var r=this;var i=this.pos,n=0;for(var a=0,s=t==null?Infinity:t;a=97){l=o-97+10}else if(o>=65){l=o-65+10}else if(o>=48&&o<=57){l=o-48}else{l=Infinity}if(l>=e){break}++r.pos;n=n*e+l}if(this.pos===i||t!=null&&this.pos-i!==t){return null}return n};ke.readRadixNumber=function(e){this.pos+=2;var t=this.readInt(e);if(t==null){this.raise(this.start+2,"Expected number in radix "+e)}if(h(this.fullCharCodeAtPos())){this.raise(this.pos,"Identifier directly after number")}return this.finishToken(b.num,t)};ke.readNumber=function(e){var t=this.pos;if(!e&&this.readInt(10)===null){this.raise(t,"Invalid number")}var r=this.pos-t>=2&&this.input.charCodeAt(t)===48;if(r&&this.strict){this.raise(t,"Invalid number")}if(r&&/[89]/.test(this.input.slice(t,this.pos))){r=false}var i=this.input.charCodeAt(this.pos);if(i===46&&!r){++this.pos;this.readInt(10);i=this.input.charCodeAt(this.pos)}if((i===69||i===101)&&!r){i=this.input.charCodeAt(++this.pos);if(i===43||i===45){++this.pos}if(this.readInt(10)===null){this.raise(t,"Invalid number")}}if(h(this.fullCharCodeAtPos())){this.raise(this.pos,"Identifier directly after number")}var n=this.input.slice(t,this.pos);var a=r?parseInt(n,8):parseFloat(n);return this.finishToken(b.num,a)};ke.readCodePoint=function(){var e=this.input.charCodeAt(this.pos),t;if(e===123){if(this.options.ecmaVersion<6){this.unexpected()}var r=++this.pos;t=this.readHexChar(this.input.indexOf("}",this.pos)-this.pos);++this.pos;if(t>1114111){this.invalidStringToken(r,"Code point out of bounds")}}else{t=this.readHexChar(4)}return t};function Ce(e){if(e<=65535){return String.fromCharCode(e)}e-=65536;return String.fromCharCode((e>>10)+55296,(e&1023)+56320)}ke.readString=function(e){var t=this;var r="",i=++this.pos;for(;;){if(t.pos>=t.input.length){t.raise(t.start,"Unterminated string constant")}var n=t.input.charCodeAt(t.pos);if(n===e){break}if(n===92){r+=t.input.slice(i,t.pos);r+=t.readEscapedChar(false);i=t.pos}else{if(C(n,t.options.ecmaVersion>=10)){t.raise(t.start,"Unterminated string constant")}++t.pos}}r+=this.input.slice(i,this.pos++);return this.finishToken(b.string,r)};var Se={};ke.tryReadTemplateToken=function(){this.inTemplateElement=true;try{this.readTmplToken()}catch(e){if(e===Se){this.readInvalidTemplateToken()}else{throw e}}this.inTemplateElement=false};ke.invalidStringToken=function(e,t){if(this.inTemplateElement&&this.options.ecmaVersion>=9){throw Se}else{this.raise(e,t)}};ke.readTmplToken=function(){var e=this;var t="",r=this.pos;for(;;){if(e.pos>=e.input.length){e.raise(e.start,"Unterminated template")}var i=e.input.charCodeAt(e.pos);if(i===96||i===36&&e.input.charCodeAt(e.pos+1)===123){if(e.pos===e.start&&(e.type===b.template||e.type===b.invalidTemplate)){if(i===36){e.pos+=2;return e.finishToken(b.dollarBraceL)}else{++e.pos;return e.finishToken(b.backQuote)}}t+=e.input.slice(r,e.pos);return e.finishToken(b.template,t)}if(i===92){t+=e.input.slice(r,e.pos);t+=e.readEscapedChar(true);r=e.pos}else if(C(i)){t+=e.input.slice(r,e.pos);++e.pos;switch(i){case 13:if(e.input.charCodeAt(e.pos)===10){++e.pos}case 10:t+="\n";break;default:t+=String.fromCharCode(i);break}if(e.options.locations){++e.curLine;e.lineStart=e.pos}r=e.pos}else{++e.pos}}};ke.readInvalidTemplateToken=function(){var e=this;for(;this.pos=48&&t<=55){var r=this.input.substr(this.pos-1,3).match(/^[0-7]+/)[0];var i=parseInt(r,8);if(i>255){r=r.slice(0,-1);i=parseInt(r,8)}this.pos+=r.length-1;t=this.input.charCodeAt(this.pos);if((r!=="0"||t===56||t===57)&&(this.strict||e)){this.invalidStringToken(this.pos-1-r.length,e?"Octal literal in template string":"Octal literal in strict mode")}return String.fromCharCode(i)}return String.fromCharCode(t)}};ke.readHexChar=function(e){var t=this.pos;var r=this.readInt(16,e);if(r===null){this.invalidStringToken(t,"Bad character escape sequence")}return r};ke.readWord1=function(){var e=this;this.containsEsc=false;var t="",r=true,i=this.pos;var n=this.options.ecmaVersion>=6;while(this.pos=r)){s[u](n,o,e)}if((t==null||n.start===t)&&(r==null||n.end===r)&&i(u,n)){throw new a(n,o)}})(e,o)}catch(e){if(e instanceof a){return e}throw e}}function u(e,t,r,i,s){r=n(r);if(!i){i=v}try{(function e(n,s,o){var l=o||n.type;if(n.start>t||n.end=t&&r(l,n)){throw new a(n,s)}i[l](n,s,e)})(e,s)}catch(e){if(e instanceof a){return e}throw e}}function f(e,t,r,i,s){r=n(r);if(!i){i=v}var o;(function e(n,s,l){if(n.start>t){return}var u=l||n.type;if(n.end<=t&&(!o||o.node.end 0.1 || correlation < -0.1) {\n console.log(event + \":\", correlation);\n }\n}\n// → brushed teeth: -0.3805211953\n// → work: -0.1371988681\n// → reading: 0.1106828054\n", + "exercises": [ + { + "name": "The sum of a range", + "file": "code/solutions/04_1_the_sum_of_a_range.js", + "number": 1, + "type": "js", + "code": "// Your code here.\n\nconsole.log(range(1, 10));\n// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\nconsole.log(range(5, 2, -1));\n// → [5, 4, 3, 2]\nconsole.log(sum(range(1, 10)));\n// → 55", + "solution": "function range(start, end, step = start < end ? 1 : -1) {\n let array = [];\n\n if (step > 0) {\n for (let i = start; i <= end; i += step) array.push(i);\n } else {\n for (let i = start; i >= end; i += step) array.push(i);\n }\n return array;\n}\n\nfunction sum(array) {\n let total = 0;\n for (let value of array) {\n total += value;\n }\n return total;\n}\n\nconsole.log(range(1, 10))\n// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\nconsole.log(range(5, 2, -1));\n// → [5, 4, 3, 2]\nconsole.log(sum(range(1, 10)));\n// → 55" + }, + { + "name": "Reversing an array", + "file": "code/solutions/04_2_reversing_an_array.js", + "number": 2, + "type": "js", + "code": "// Your code here.\n\nconsole.log(reverseArray([\"A\", \"B\", \"C\"]));\n// → [\"C\", \"B\", \"A\"];\nlet arrayValue = [1, 2, 3, 4, 5];\nreverseArrayInPlace(arrayValue);\nconsole.log(arrayValue);\n// → [5, 4, 3, 2, 1]", + "solution": "function reverseArray(array) {\n let output = [];\n for (let i = array.length - 1; i >= 0; i--) {\n output.push(array[i]);\n }\n return output;\n}\n\nfunction reverseArrayInPlace(array) {\n for (let i = 0; i < Math.floor(array.length / 2); i++) {\n let old = array[i];\n array[i] = array[array.length - 1 - i];\n array[array.length - 1 - i] = old;\n }\n return array;\n}\n\nconsole.log(reverseArray([\"A\", \"B\", \"C\"]));\n// → [\"C\", \"B\", \"A\"];\nlet arrayValue = [1, 2, 3, 4, 5];\nreverseArrayInPlace(arrayValue);\nconsole.log(arrayValue);\n// → [5, 4, 3, 2, 1]" + }, + { + "name": "A list", + "file": "code/solutions/04_3_a_list.js", + "number": 3, + "type": "js", + "code": "// Your code here.\n\nconsole.log(arrayToList([10, 20]));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(listToArray(arrayToList([10, 20, 30])));\n// → [10, 20, 30]\nconsole.log(prepend(10, prepend(20, null)));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(nth(arrayToList([10, 20, 30]), 1));\n// → 20", + "solution": "function arrayToList(array) {\n let list = null;\n for (let i = array.length - 1; i >= 0; i--) {\n list = {value: array[i], rest: list};\n }\n return list;\n}\n\nfunction listToArray(list) {\n let array = [];\n for (let node = list; node; node = node.rest) {\n array.push(node.value);\n }\n return array;\n}\n\nfunction prepend(value, list) {\n return {value, rest: list};\n}\n\nfunction nth(list, n) {\n if (!list) return undefined;\n else if (n == 0) return list.value;\n else return nth(list.rest, n - 1);\n}\n\nconsole.log(arrayToList([10, 20]));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(listToArray(arrayToList([10, 20, 30])));\n// → [10, 20, 30]\nconsole.log(prepend(10, prepend(20, null)));\n// → {value: 10, rest: {value: 20, rest: null}}\nconsole.log(nth(arrayToList([10, 20, 30]), 1));\n// → 20" + }, + { + "name": "Deep comparison", + "file": "code/solutions/04_4_deep_comparison.js", + "number": 4, + "type": "js", + "code": "// Your code here.\n\nlet obj = {here: {is: \"an\"}, object: 2};\nconsole.log(deepEqual(obj, obj));\n// → true\nconsole.log(deepEqual(obj, {here: 1, object: 2}));\n// → false\nconsole.log(deepEqual(obj, {here: {is: \"an\"}, object: 2}));\n// → true", + "solution": "function deepEqual(a, b) {\n if (a === b) return true;\n \n if (a == null || typeof a != \"object\" ||\n b == null || typeof b != \"object\") return false;\n\n let keysA = Object.keys(a), keysB = Object.keys(b);\n\n if (keysA.length != keysB.length) return false;\n\n for (let key of keysA) {\n if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;\n }\n\n return true;\n}\n\nlet obj = {here: {is: \"an\"}, object: 2};\nconsole.log(deepEqual(obj, obj));\n// → true\nconsole.log(deepEqual(obj, {here: 1, object: 2}));\n// → false\nconsole.log(deepEqual(obj, {here: {is: \"an\"}, object: 2}));\n// → true" + } + ], + "include": [ + "code/journal.js", + "code/chapter/04_data.js" + ] + }, + { + "number": 5, + "id": "05_higher_order", + "title": "Higher-Order Functions", + "start_code": "function textScripts(text) {\n let scripts = countBy(text, char => {\n let script = characterScript(char.codePointAt(0));\n return script ? script.name : \"none\";\n }).filter(({name}) => name != \"none\");\n\n let total = scripts.reduce((n, {count}) => n + count, 0);\n if (total == 0) return \"No scripts found\";\n\n return scripts.map(({name, count}) => {\n return `${Math.round(count * 100 / total)}% ${name}`;\n }).join(\", \");\n}\n\nconsole.log(textScripts('英国的狗说\"woof\", 俄罗斯的狗说\"тяв\"'));\n", + "exercises": [ + { + "name": "Flattening", + "file": "code/solutions/05_1_flattening.js", + "number": 1, + "type": "js", + "code": "let arrays = [[1, 2, 3], [4, 5], [6]];\n// Your code here.\n// → [1, 2, 3, 4, 5, 6]", + "solution": "let arrays = [[1, 2, 3], [4, 5], [6]];\n\nconsole.log(arrays.reduce((flat, current) => flat.concat(current), []));\n// → [1, 2, 3, 4, 5, 6]" + }, + { + "name": "Your own loop", + "file": "code/solutions/05_2_your_own_loop.js", + "number": 2, + "type": "js", + "code": "// Your code here.\n\nloop(3, n => n > 0, n => n - 1, console.log);\n// → 3\n// → 2\n// → 1", + "solution": "function loop(start, test, update, body) {\n for (let value = start; test(value); value = update(value)) {\n body(value);\n }\n}\n\nloop(3, n => n > 0, n => n - 1, console.log);\n// → 3\n// → 2\n// → 1" + }, + { + "name": "Everything", + "file": "code/solutions/05_3_everything.js", + "number": 3, + "type": "js", + "code": "function every(array, test) {\n // Your code here.\n}\n\nconsole.log(every([1, 3, 5], n => n < 10));\n// → true\nconsole.log(every([2, 4, 16], n => n < 10));\n// → false\nconsole.log(every([], n => n < 10));\n// → true", + "solution": "function every(array, predicate) {\n for (let element of array) {\n if (!predicate(element)) return false;\n }\n return true;\n}\n\nfunction every2(array, predicate) {\n return !array.some(element => !predicate(element));\n}\n\nconsole.log(every([1, 3, 5], n => n < 10));\n// → true\nconsole.log(every([2, 4, 16], n => n < 10));\n// → false\nconsole.log(every([], n => n < 10));\n// → true" + }, + { + "name": "Dominant writing direction", + "file": "code/solutions/05_4_dominant_writing_direction.js", + "number": 4, + "type": "js", + "code": "function dominantDirection(text) {\n // Your code here.\n}\n\nconsole.log(dominantDirection(\"Hello!\"));\n// → ltr\nconsole.log(dominantDirection(\"Hey, مساء الخير\"));\n// → rtl", + "solution": "function dominantDirection(text) {\n let counted = countBy(text, char => {\n let script = characterScript(char.codePointAt(0));\n return script ? script.direction : \"none\";\n }).filter(({name}) => name != \"none\");\n\n if (counted.length == 0) return \"ltr\";\n\n return counted.reduce((a, b) => a.count > b.count ? a : b).name;\n}\n\nconsole.log(dominantDirection(\"Hello!\"));\n// → ltr\nconsole.log(dominantDirection(\"Hey, مساء الخير\"));\n// → rtl" + } + ], + "include": [ + "code/scripts.js", + "code/chapter/05_higher_order.js", + "code/intro.js" + ] + }, + { + "number": 6, + "id": "06_object", + "title": "The Secret Life of Objects", + "start_code": "class Temperature {\n constructor(celsius) {\n this.celsius = celsius;\n }\n get fahrenheit() {\n return this.celsius * 1.8 + 32;\n }\n set fahrenheit(value) {\n this.celsius = (value - 32) / 1.8;\n }\n\n static fromFahrenheit(value) {\n return new Temperature((value - 32) / 1.8);\n }\n}\n\nlet temp = new Temperature(22);\nconsole.log(temp.fahrenheit);\ntemp.fahrenheit = 86;\nconsole.log(temp.celsius);\n", + "exercises": [ + { + "name": "A vector type", + "file": "code/solutions/06_1_a_vector_type.js", + "number": 1, + "type": "js", + "code": "// Your code here.\n\nconsole.log(new Vec(1, 2).plus(new Vec(2, 3)));\n// → Vec{x: 3, y: 5}\nconsole.log(new Vec(1, 2).minus(new Vec(2, 3)));\n// → Vec{x: -1, y: -1}\nconsole.log(new Vec(3, 4).length);\n// → 5", + "solution": "class Vec {\n constructor(x, y) {\n this.x = x;\n this.y = y;\n }\n\n plus(other) {\n return new Vec(this.x + other.x, this.y + other.y);\n }\n\n minus(other) {\n return new Vec(this.x - other.x, this.y - other.y);\n }\n\n get length() {\n return Math.sqrt(this.x * this.x + this.y * this.y);\n }\n}\n\nconsole.log(new Vec(1, 2).plus(new Vec(2, 3)));\n// → Vec{x: 3, y: 5}\nconsole.log(new Vec(1, 2).minus(new Vec(2, 3)));\n// → Vec{x: -1, y: -1}\nconsole.log(new Vec(3, 4).length);\n// → 5" + }, + { + "name": "Groups", + "file": "code/solutions/06_2_groups.js", + "number": 2, + "type": "js", + "code": "class Group {\n // Your code here.\n}\n\nlet group = Group.from([10, 20]);\nconsole.log(group.has(10));\n// → true\nconsole.log(group.has(30));\n// → false\ngroup.add(10);\ngroup.delete(10);\nconsole.log(group.has(10));\n// → false", + "solution": "class Group {\n constructor() {\n this.members = [];\n }\n\n add(value) {\n if (!this.has(value)) {\n this.members.push(value);\n }\n }\n\n delete(value) {\n this.members = this.members.filter(v => v !== value);\n }\n\n has(value) {\n return this.members.includes(value);\n }\n\n static from(collection) {\n let group = new Group;\n for (let value of collection) {\n group.add(value);\n }\n return group;\n }\n}\n\nlet group = Group.from([10, 20]);\nconsole.log(group.has(10));\n// → true\nconsole.log(group.has(30));\n// → false\ngroup.add(10);\ngroup.delete(10);\nconsole.log(group.has(10));" + }, + { + "name": "Iterable groups", + "file": "code/solutions/06_3_iterable_groups.js", + "number": 3, + "type": "js", + "code": "// Your code here (and the code from the previous exercise)\n\nfor (let value of Group.from([\"a\", \"b\", \"c\"])) {\n console.log(value);\n}\n// → a\n// → b\n// → c", + "solution": "class Group {\n constructor() {\n this.members = [];\n }\n\n add(value) {\n if (!this.has(value)) {\n this.members.push(value);\n }\n }\n\n delete(value) {\n this.members = this.members.filter(v => v !== value);\n }\n\n has(value) {\n return this.members.includes(value);\n }\n\n static from(collection) {\n let group = new Group;\n for (let value of collection) {\n group.add(value);\n }\n return group;\n }\n\n [Symbol.iterator]() {\n return new GroupIterator(this);\n }\n}\n\nclass GroupIterator {\n constructor(group) {\n this.group = group;\n this.position = 0;\n }\n\n next() {\n if (this.position >= this.group.members.length) {\n return {done: true};\n } else {\n let result = {value: this.group.members[this.position],\n done: false};\n this.position++;\n return result;\n }\n }\n}\n\nfor (let value of Group.from([\"a\", \"b\", \"c\"])) {\n console.log(value);\n}\n// → a\n// → b\n// → c" + }, + { + "name": "Borrowing a method", + "file": "code/solutions/06_4_borrowing_a_method.js", + "number": 4, + "type": "js", + "code": "let map = {one: true, two: true, hasOwnProperty: true};\n\n// Fix this call\nconsole.log(map.hasOwnProperty(\"one\"));\n// → true", + "solution": "let map = {one: true, two: true, hasOwnProperty: true};\n\nconsole.log(Object.prototype.hasOwnProperty.call(map, \"one\"));\n// → true" + } + ], + "include": [ + "code/chapter/06_object.js" + ] + }, + { + "number": 7, + "id": "07_robot", + "title": "Project: A Robot", + "start_code": "runRobotAnimation(VillageState.random(),\n goalOrientedRobot, []);\n", + "exercises": [ + { + "name": "Measuring a robot", + "file": "code/solutions/07_1_measuring_a_robot.js", + "number": 1, + "type": "js", + "code": "function compareRobots(robot1, memory1, robot2, memory2) {\n // Your code here\n}\n\ncompareRobots(routeRobot, [], goalOrientedRobot, []);", + "solution": "function countSteps(state, robot, memory) {\n for (let steps = 0;; steps++) {\n if (state.parcels.length == 0) return steps;\n let action = robot(state, memory);\n state = state.move(action.direction);\n memory = action.memory;\n }\n}\n\nfunction compareRobots(robot1, memory1, robot2, memory2) {\n let total1 = 0, total2 = 0;\n for (let i = 0; i < 100; i++) {\n let state = VillageState.random();\n total1 += countSteps(state, robot1, memory1);\n total2 += countSteps(state, robot2, memory2);\n }\n console.log(`Robot 1 needed ${total1 / 100} steps per task`)\n console.log(`Robot 2 needed ${total2 / 100}`)\n}\n\ncompareRobots(routeRobot, [], goalOrientedRobot, []);" + }, + { + "name": "Robot efficiency", + "file": "code/solutions/07_2_robot_efficiency.js", + "number": 2, + "type": "js", + "code": "// Your code here\n\nrunRobotAnimation(VillageState.random(), yourRobot, memory);", + "solution": "function lazyRobot({place, parcels}, route) {\n if (route.length == 0) {\n // Describe a route for every parcel\n let routes = parcels.map(parcel => {\n if (parcel.place != place) {\n return {route: findRoute(roadGraph, place, parcel.place),\n pickUp: true};\n } else {\n return {route: findRoute(roadGraph, place, parcel.address),\n pickUp: false};\n }\n });\n\n // This determines the precedence a route gets when choosing.\n // Route length counts negatively, routes that pick up a package\n // get a small bonus.\n function score({route, pickUp}) {\n return (pickUp ? 0.5 : 0) - route.length;\n }\n route = routes.reduce((a, b) => score(a) > score(b) ? a : b).route;\n }\n\n return {direction: route[0], memory: route.slice(1)};\n}\n\nrunRobotAnimation(VillageState.random(), lazyRobot, []);" + }, + { + "name": "Persistent group", + "file": "code/solutions/07_3_persistent_group.js", + "number": 3, + "type": "js", + "code": "class PGroup {\n // Your code here\n}\n\nlet a = PGroup.empty.add(\"a\");\nlet ab = a.add(\"b\");\nlet b = ab.delete(\"a\");\n\nconsole.log(b.has(\"b\"));\n// → true\nconsole.log(a.has(\"b\"));\n// → false\nconsole.log(b.has(\"a\"));\n// → false", + "solution": "class PGroup {\n constructor(members) {\n this.members = members;\n }\n\n add(value) {\n if (this.has(value)) return this;\n return new PGroup(this.members.concat([value]));\n }\n\n delete(value) {\n if (!this.has(value)) return this;\n return new PGroup(this.members.filter(m => m !== value));\n }\n\n has(value) {\n return this.members.includes(value);\n }\n}\n\nPGroup.empty = new PGroup([]);\n\nlet a = PGroup.empty.add(\"a\");\nlet ab = a.add(\"b\");\nlet b = ab.delete(\"a\");\n\nconsole.log(b.has(\"b\"));\n// → true\nconsole.log(a.has(\"b\"));\n// → false\nconsole.log(b.has(\"a\"));\n// → false" + } + ], + "include": [ + "code/chapter/07_robot.js", + "code/animatevillage.js" + ] + }, + { + "number": 8, + "id": "08_error", + "title": "Bugs and Errors", + "start_code": "", + "exercises": [ + { + "name": "Retry", + "file": "code/solutions/08_1_retry.js", + "number": 1, + "type": "js", + "code": "class MultiplicatorUnitFailure extends Error {}\n\nfunction primitiveMultiply(a, b) {\n if (Math.random() < 0.2) {\n return a * b;\n } else {\n throw new MultiplicatorUnitFailure(\"Klunk\");\n }\n}\n\nfunction reliableMultiply(a, b) {\n // Your code here.\n}\n\nconsole.log(reliableMultiply(8, 8));\n// → 64", + "solution": "class MultiplicatorUnitFailure extends Error {}\n\nfunction primitiveMultiply(a, b) {\n if (Math.random() < 0.2) {\n return a * b;\n } else {\n throw new MultiplicatorUnitFailure(\"Klunk\");\n }\n}\n\nfunction reliableMultiply(a, b) {\n for (;;) {\n try {\n return primitiveMultiply(a, b);\n } catch (e) {\n if (!(e instanceof MultiplicatorUnitFailure))\n throw e;\n }\n }\n}\n\nconsole.log(reliableMultiply(8, 8));\n// → 64" + }, + { + "name": "The locked box", + "file": "code/solutions/08_2_the_locked_box.js", + "number": 2, + "type": "js", + "code": "const box = {\n locked: true,\n unlock() { this.locked = false; },\n lock() { this.locked = true; },\n _content: [],\n get content() {\n if (this.locked) throw new Error(\"Locked!\");\n return this._content;\n }\n};\n\nfunction withBoxUnlocked(body) {\n // Your code here.\n}\n\nwithBoxUnlocked(function() {\n box.content.push(\"gold piece\");\n});\n\ntry {\n withBoxUnlocked(function() {\n throw new Error(\"Pirates on the horizon! Abort!\");\n });\n} catch (e) {\n console.log(\"Error raised: \" + e);\n}\nconsole.log(box.locked);\n// → true", + "solution": "const box = {\n locked: true,\n unlock() { this.locked = false; },\n lock() { this.locked = true; },\n _content: [],\n get content() {\n if (this.locked) throw new Error(\"Locked!\");\n return this._content;\n }\n};\n\nfunction withBoxUnlocked(body) {\n let locked = box.locked;\n if (!locked) {\n return body();\n }\n\n box.unlock();\n try {\n return body();\n } finally {\n box.lock();\n }\n}\n\nwithBoxUnlocked(function() {\n box.content.push(\"gold piece\");\n});\n\ntry {\n withBoxUnlocked(function() {\n throw new Error(\"Pirates on the horizon! Abort!\");\n });\n} catch (e) {\n console.log(\"Error raised:\", e);\n}\n\nconsole.log(box.locked);\n// → true" + } + ], + "include": [ + "code/chapter/08_error.js" + ] + }, + { + "number": 9, + "id": "09_regexp", + "title": "Regular Expressions", + "start_code": "function parseINI(string) {\n // Start with an object to hold the top-level fields\n let result = {};\n let section = result;\n string.split(/\\r?\\n/).forEach(line => {\n let match;\n if (match = line.match(/^(\\w+)=(.*)$/)) {\n section[match[1]] = match[2];\n } else if (match = line.match(/^\\[(.*)\\]$/)) {\n section = result[match[1]] = {};\n } else if (!/^\\s*(;.*)?$/.test(line)) {\n throw new Error(\"Line '\" + line + \"' is not valid.\");\n }\n });\n return result;\n}\n\nconsole.log(parseINI(`\nname=Vasilis\n[address]\ncity=Tessaloniki`));\n", + "exercises": [ + { + "name": "Regexp golf", + "file": "code/solutions/09_1_regexp_golf.js", + "number": 1, + "type": "js", + "code": "// Fill in the regular expressions\n\nverify(/.../,\n [\"my car\", \"bad cats\"],\n [\"camper\", \"high art\"]);\n\nverify(/.../,\n [\"pop culture\", \"mad props\"],\n [\"plop\", \"prrrop\"]);\n\nverify(/.../,\n [\"ferret\", \"ferry\", \"ferrari\"],\n [\"ferrum\", \"transfer A\"]);\n\nverify(/.../,\n [\"how delicious\", \"spacious room\"],\n [\"ruinous\", \"consciousness\"]);\n\nverify(/.../,\n [\"bad punctuation .\"],\n [\"escape the period\"]);\n\nverify(/.../,\n [\"hottentottententen\"],\n [\"no\", \"hotten totten tenten\"]);\n\nverify(/.../,\n [\"red platypus\", \"wobbling nest\"],\n [\"earth bed\", \"learning ape\", \"BEET\"]);\n\n\nfunction verify(regexp, yes, no) {\n // Ignore unfinished exercises\n if (regexp.source == \"...\") return;\n for (let str of yes) if (!regexp.test(str)) {\n console.log(`Failure to match '${str}'`);\n }\n for (let str of no) if (regexp.test(str)) {\n console.log(`Unexpected match for '${str}'`);\n }\n}", + "solution": "// Fill in the regular expressions\n\nverify(/ca[rt]/,\n [\"my car\", \"bad cats\"],\n [\"camper\", \"high art\"]);\n\nverify(/pr?op/,\n [\"pop culture\", \"mad props\"],\n [\"plop\", \"prrrop\"]);\n\nverify(/ferr(et|y|ari)/,\n [\"ferret\", \"ferry\", \"ferrari\"],\n [\"ferrum\", \"transfer A\"]);\n\nverify(/ious\\b/,\n [\"how delicious\", \"spacious room\"],\n [\"ruinous\", \"consciousness\"]);\n\nverify(/\\s[.,:;]/,\n [\"bad punctuation .\"],\n [\"escape the dot\"]);\n\nverify(/\\w{7}/,\n [\"hottentottententen\"],\n [\"no\", \"hotten totten tenten\"]);\n\nverify(/\\b[^\\We]+\\b/i,\n [\"red platypus\", \"wobbling nest\"],\n [\"earth bed\", \"learning ape\", \"BEET\"]);\n\n\nfunction verify(regexp, yes, no) {\n // Ignore unfinished exercises\n if (regexp.source == \"...\") return;\n for (let str of yes) if (!regexp.test(str)) {\n console.log(`Failure to match '${str}'`);\n }\n for (let str of no) if (regexp.test(str)) {\n console.log(`Unexpected match for '${str}'`);\n }\n}" + }, + { + "name": "Quoting style", + "file": "code/solutions/09_2_quoting_style.js", + "number": 2, + "type": "js", + "code": "let text = \"'I'm the cook,' he said, 'it's my job.'\";\n// Change this call.\nconsole.log(text.replace(/A/g, \"B\"));\n// → \"I'm the cook,\" he said, \"it's my job.\"", + "solution": "let text = \"'I'm the cook,' he said, 'it's my job.'\";\n\nconsole.log(text.replace(/(^|\\W)'|'(\\W|$)/g, '$1\"$2'));\n// → \"I'm the cook,\" he said, \"it's my job.\"" + }, + { + "name": "Numbers again", + "file": "code/solutions/09_3_numbers_again.js", + "number": 3, + "type": "js", + "code": "// Fill in this regular expression.\nlet number = /^...$/;\n\n// Tests:\nfor (let str of [\"1\", \"-1\", \"+15\", \"1.55\", \".5\", \"5.\",\n \"1.3e2\", \"1E-4\", \"1e+12\"]) {\n if (!number.test(str)) {\n console.log(`Failed to match '${str}'`);\n }\n}\nfor (let str of [\"1a\", \"+-1\", \"1.2.3\", \"1+1\", \"1e4.5\",\n \".5.\", \"1f5\", \".\"]) {\n if (number.test(str)) {\n console.log(`Incorrectly accepted '${str}'`);\n }\n}", + "solution": "// Fill in this regular expression.\nlet number = /^[+\\-]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][+\\-]?\\d+)?$/;\n\n// Tests:\nfor (let str of [\"1\", \"-1\", \"+15\", \"1.55\", \".5\", \"5.\",\n \"1.3e2\", \"1E-4\", \"1e+12\"]) {\n if (!number.test(str)) {\n console.log(`Failed to match '${str}'`);\n }\n}\nfor (let str of [\"1a\", \"+-1\", \"1.2.3\", \"1+1\", \"1e4.5\",\n \".5.\", \"1f5\", \".\"]) {\n if (number.test(str)) {\n console.log(`Incorrectly accepted '${str}'`);\n }\n}" + } + ], + "include": null + }, + { + "number": 10, + "id": "10_modules", + "title": "Modules", + "start_code": "", + "exercises": [ + { + "name": "Roads module", + "file": "code/solutions/10_2_roads_module.js", + "number": 2, + "type": "js", + "code": "// Add dependencies and exports\n\nconst roads = [\n \"Alice's House-Bob's House\", \"Alice's House-Cabin\",\n \"Alice's House-Post Office\", \"Bob's House-Town Hall\",\n \"Daria's House-Ernie's House\", \"Daria's House-Town Hall\",\n \"Ernie's House-Grete's House\", \"Grete's House-Farm\",\n \"Grete's House-Shop\", \"Marketplace-Farm\",\n \"Marketplace-Post Office\", \"Marketplace-Shop\",\n \"Marketplace-Town Hall\", \"Shop-Town Hall\"\n];", + "solution": "const {buildGraph} = require(\"./graph\");\n\nconst roads = [\n \"Alice's House-Bob's House\", \"Alice's House-Cabin\",\n \"Alice's House-Post Office\", \"Bob's House-Town Hall\",\n \"Daria's House-Ernie's House\", \"Daria's House-Town Hall\",\n \"Ernie's House-Grete's House\", \"Grete's House-Farm\",\n \"Grete's House-Shop\", \"Marketplace-Farm\",\n \"Marketplace-Post Office\", \"Marketplace-Shop\",\n \"Marketplace-Town Hall\", \"Shop-Town Hall\"\n];\n\nexports.roadGraph = buildGraph(roads.map(r => r.split(\"-\")));" + } + ], + "include": [ + "code/packages_chapter_10.js", + "code/chapter/07_robot.js" + ] + }, + { + "number": 11, + "id": "11_async", + "title": "Asynchronous Programming", + "start_code": "findInStorage(bigOak, \"events on 2017-12-21\")\n .then(console.log);\n", + "exercises": [ + { + "name": "Tracking the scalpel", + "file": "code/solutions/11_1_tracking_the_scalpel.js", + "number": 1, + "type": "js", + "code": "async function locateScalpel(nest) {\n // Your code here.\n}\n\nfunction locateScalpel2(nest) {\n // Your code here.\n}\n\nlocateScalpel(bigOak).then(console.log);\n// → Butcher Shop", + "solution": "async function locateScalpel(nest) {\n let current = nest.name;\n for (;;) {\n let next = await anyStorage(nest, current, \"scalpel\");\n if (next == current) return current;\n current = next;\n }\n}\n\nfunction locateScalpel2(nest) {\n function loop(current) {\n return anyStorage(nest, current, \"scalpel\").then(next => {\n if (next == current) return current;\n else return loop(next);\n });\n }\n return loop(nest.name);\n}\n\nlocateScalpel(bigOak).then(console.log);\n// → Butcher's Shop\nlocateScalpel2(bigOak).then(console.log);\n// → Butcher's Shop" + }, + { + "name": "Building Promise.all", + "file": "code/solutions/11_2_building_promiseall.js", + "number": 2, + "type": "js", + "code": "function Promise_all(promises) {\n return new Promise((resolve, reject) => {\n // Your code here.\n });\n}\n\n// Test code.\nPromise_all([]).then(array => {\n console.log(\"This should be []:\", array);\n});\nfunction soon(val) {\n return new Promise(resolve => {\n setTimeout(() => resolve(val), Math.random() * 500);\n });\n}\nPromise_all([soon(1), soon(2), soon(3)]).then(array => {\n console.log(\"This should be [1, 2, 3]:\", array);\n});\nPromise_all([soon(1), Promise.reject(\"X\"), soon(3)])\n .then(array => {\n console.log(\"We should not get here\");\n })\n .catch(error => {\n if (error != \"X\") {\n console.log(\"Unexpected failure:\", error);\n }\n });", + "solution": "function Promise_all(promises) {\n return new Promise((resolve, reject) => {\n let results = [];\n let pending = promises.length;\n for (let i = 0; i < promises.length; i++) {\n promises[i].then(result => {\n results[i] = result;\n pending--;\n if (pending == 0) resolve(results);\n }).catch(reject);\n }\n if (promises.length == 0) resolve(results);\n });\n}\n\n// Test code.\nPromise_all([]).then(array => {\n console.log(\"This should be []:\", array);\n});\nfunction soon(val) {\n return new Promise(resolve => {\n setTimeout(() => resolve(val), Math.random() * 500);\n });\n}\nPromise_all([soon(1), soon(2), soon(3)]).then(array => {\n console.log(\"This should be [1, 2, 3]:\", array);\n});\nPromise_all([soon(1), Promise.reject(\"X\"), soon(3)]).then(array => {\n console.log(\"We should not get here\");\n}).catch(error => {\n if (error != \"X\") {\n console.log(\"Unexpected failure:\", error);\n }\n});" + } + ], + "include": [ + "code/crow-tech.js", + "code/chapter/11_async.js" + ] + }, + { + "number": 12, + "id": "12_language", + "title": "Project: A Programming Language", + "start_code": "run(`\ndo(define(plusOne, fun(a, +(a, 1))),\n print(plusOne(10)))\n`);\n\nrun(`\ndo(define(pow, fun(base, exp,\n if(==(exp, 0),\n 1,\n *(base, pow(base, -(exp, 1)))))),\n print(pow(2, 10)))\n`);\n", + "exercises": [ + { + "name": "Arrays", + "file": "code/solutions/12_1_arrays.js", + "number": 1, + "type": "js", + "code": "// Modify these definitions...\n\ntopScope.array = \"...\";\n\ntopScope.length = \"...\";\n\ntopScope.element = \"...\";\n\nrun(`\ndo(define(sum, fun(array,\n do(define(i, 0),\n define(sum, 0),\n while(<(i, length(array)),\n do(define(sum, +(sum, element(array, i))),\n define(i, +(i, 1)))),\n sum))),\n print(sum(array(1, 2, 3))))\n`);\n// → 6", + "solution": "topScope.array = (...values) => values;\n\ntopScope.length = array => array.length;\n\ntopScope.element = (array, i) => array[i];\n\nrun(`\ndo(define(sum, fun(array,\n do(define(i, 0),\n define(sum, 0),\n while(<(i, length(array)),\n do(define(sum, +(sum, element(array, i))),\n define(i, +(i, 1)))),\n sum))),\n print(sum(array(1, 2, 3))))\n`);\n// → 6" + }, + { + "name": "Comments", + "file": "code/solutions/12_3_comments.js", + "number": 3, + "type": "js", + "code": "// This is the old skipSpace. Modify it...\nfunction skipSpace(string) {\n let first = string.search(/\\S/);\n if (first == -1) return \"\";\n return string.slice(first);\n}\n\nconsole.log(parse(\"# hello\\nx\"));\n// → {type: \"word\", name: \"x\"}\n\nconsole.log(parse(\"a # one\\n # two\\n()\"));\n// → {type: \"apply\",\n// operator: {type: \"word\", name: \"a\"},\n// args: []}", + "solution": "function skipSpace(string) {\n let skippable = string.match(/^(\\s|#.*)*/);\n return string.slice(skippable[0].length);\n}\n\nconsole.log(parse(\"# hello\\nx\"));\n// → {type: \"word\", name: \"x\"}\n\nconsole.log(parse(\"a # one\\n # two\\n()\"));\n// → {type: \"apply\",\n// operator: {type: \"word\", name: \"a\"},\n// args: []}" + }, + { + "name": "Fixing scope", + "file": "code/solutions/12_4_fixing_scope.js", + "number": 4, + "type": "js", + "code": "specialForms.set = (args, scope) => {\n // Your code here.\n};\n\nrun(`\ndo(define(x, 4),\n define(setx, fun(val, set(x, val))),\n setx(50),\n print(x))\n`);\n// → 50\nrun(`set(quux, true)`);\n// → Some kind of ReferenceError", + "solution": "specialForms.set = (args, env) => {\n if (args.length != 2 || args[0].type != \"word\") {\n throw new SyntaxError(\"Bad use of set\");\n }\n let varName = args[0].name;\n let value = evaluate(args[1], env);\n\n for (let scope = env; scope; scope = Object.getPrototypeOf(scope)) {\n if (Object.prototype.hasOwnProperty.call(scope, varName)) {\n scope[varName] = value;\n return value;\n }\n }\n throw new ReferenceError(`Setting undefined variable ${varName}`);\n};\n\nrun(`\ndo(define(x, 4),\n define(setx, fun(val, set(x, val))),\n setx(50),\n print(x))\n`);\n// → 50\nrun(`set(quux, true)`);\n// → Some kind of ReferenceError" + } + ], + "include": [ + "code/chapter/12_language.js" + ] + }, + { + "number": 13, + "id": "13_browser", + "title": "JavaScript and the Browser", + "start_code": "", + "exercises": [], + "include": null + }, + { + "number": 14, + "id": "14_dom", + "title": "The Document Object Model", + "start_code": "\n\n

        \n \n

        \n\n", + "exercises": [ + { + "name": "Build a table", + "file": "code/solutions/14_1_build_a_table.html", + "number": 1, + "type": "html", + "code": "\n\n

        Mountains

        \n\n
        \n\n", + "solution": "\n\n\n\n

        Mountains

        \n\n
        \n\n" + }, + { + "name": "Elements by tag name", + "file": "code/solutions/14_2_elements_by_tag_name.html", + "number": 2, + "type": "html", + "code": "\n\n

        Heading with a span element.

        \n

        A paragraph with one, two\n spans.

        \n\n", + "solution": "\n\n

        Heading with a span element.

        \n

        A paragraph with one, two\n spans.

        \n\n" + }, + { + "name": "The cat's hat", + "file": "code/solutions/14_3_the_cats_hat.html", + "number": 3, + "type": "html", + "code": "\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n\n\n\n\n" + } + ], + "include": null + }, + { + "number": 15, + "id": "15_event", + "title": "Handling Events", + "start_code": "\n\n

        Drag the bar to change its width:

        \n
        \n
        \n\n", + "exercises": [ + { + "name": "Balloon", + "file": "code/solutions/15_1_balloon.html", + "number": 1, + "type": "html", + "code": "\n\n

        🎈

        \n\n", + "solution": "\n\n

        🎈

        \n\n" + }, + { + "name": "Mouse trail", + "file": "code/solutions/15_2_mouse_trail.html", + "number": 2, + "type": "html", + "code": "\n\n\n\n", + "solution": "\n\n\n\n\n\n" + }, + { + "name": "Tabs", + "file": "code/solutions/15_3_tabs.html", + "number": 3, + "type": "html", + "code": "\n\n\n
        Tab one
        \n
        Tab two
        \n
        Tab three
        \n
        \n", + "solution": "\n\n\n
        Tab one
        \n
        Tab two
        \n
        Tab three
        \n
        \n" + } + ], + "include": null + }, + { + "number": 16, + "id": "16_game", + "title": "Project: A Platform Game", + "start_code": "\n\n\n\n\n\n\n \n\n", + "exercises": [ + { + "name": "Game over", + "file": "code/solutions/16_1_game_over.html", + "number": 1, + "type": "html", + "code": "\n\n\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n\n\n" + }, + { + "name": "Pausing the game", + "file": "code/solutions/16_2_pausing_the_game.html", + "number": 2, + "type": "html", + "code": "\n\n\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n\n\n" + }, + { + "name": "A monster", + "file": "code/solutions/16_3_a_monster.html", + "number": 3, + "type": "html", + "code": "\n\n\n\n\n\n\n\n \n", + "solution": "\n\n\n\n\n\n\n\n\n \n" + } + ], + "include": [ + "code/chapter/16_game.js", + "code/levels.js" + ] + }, + { + "number": 17, + "id": "17_canvas", + "title": "Drawing on Canvas", + "start_code": "\n\n\n\n\n\n \n\n", + "exercises": [ + { + "name": "Shapes", + "file": "code/solutions/17_1_shapes.html", + "number": 1, + "type": "html", + "code": "\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n" + }, + { + "name": "The pie chart", + "file": "code/solutions/17_2_the_pie_chart.html", + "number": 2, + "type": "html", + "code": "\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n" + }, + { + "name": "A bouncing ball", + "file": "code/solutions/17_3_a_bouncing_ball.html", + "number": 3, + "type": "html", + "code": "\n\n\n\n\n\n", + "solution": "\n\n\n\n\n\n" + } + ], + "include": [ + "code/chapter/16_game.js", + "code/levels.js", + "code/chapter/17_canvas.js" + ] + }, + { + "number": 18, + "id": "18_http", + "title": "HTTP and Forms", + "start_code": "\n\n\nNotes:
        \n\n\n\n", + "exercises": [ + { + "name": "Content negotiation", + "file": "code/solutions/18_1_content_negotiation.js", + "number": 1, + "type": "js", + "code": "// Your code here.", + "solution": "const url = \"https://eloquentjavascript.net/author\";\nconst types = [\"text/plain\",\n \"text/html\",\n \"application/json\",\n \"application/rainbows+unicorns\"];\n\nasync function showTypes() {\n for (let type of types) {\n let resp = await fetch(url, {headers: {accept: type}});\n console.log(`${type}: ${await resp.text()}\\n`);\n }\n}\n\nshowTypes();" + }, + { + "name": "A JavaScript workbench", + "file": "code/solutions/18_2_a_javascript_workbench.html", + "number": 2, + "type": "html", + "code": "\n\n\n\n\n
        \n\n",
        +        "solution": "\n\n\n\n\n
        \n\n"
        +      },
        +      {
        +        "name": "Conway's Game of Life",
        +        "file": "code/solutions/18_3_conways_game_of_life.html",
        +        "number": 3,
        +        "type": "html",
        +        "code": "\n\n\n
        \n\n\n", + "solution": "\n\n\n
        \n\n\n\n" + } + ], + "include": [ + "code/chapter/18_http.js" + ] + }, + { + "number": 19, + "id": "19_paint", + "title": "Project: A Pixel Art Editor", + "start_code": "\n\n\n
        \n\n", + "exercises": [ + { + "name": "Keyboard bindings", + "file": "code/solutions/19_1_keyboard_bindings.html", + "number": 1, + "type": "html", + "code": "\n\n\n
        \n", + "solution": "\n\n\n
        \n" + }, + { + "name": "Efficient drawing", + "file": "code/solutions/19_2_efficient_drawing.html", + "number": 2, + "type": "html", + "code": "\n\n\n
        \n", + "solution": "\n\n\n
        \n" + }, + { + "name": "Circles", + "file": "code/solutions/19_3_circles.html", + "number": 3, + "type": "html", + "code": "\n\n\n
        \n", + "solution": "\n\n\n
        \n" + }, + { + "name": "Proper lines", + "file": "code/solutions/19_4_proper_lines.html", + "number": 4, + "type": "html", + "code": "\n\n\n
        \n", + "solution": "\n\n\n
        \n" + } + ], + "include": [ + "code/chapter/19_paint.js" + ] + }, + { + "number": 20, + "id": "20_node", + "title": "Node.js", + "start_code": "", + "exercises": [ + { + "name": "Search tool", + "file": "code/solutions/20_1_search_tool.js", + "number": 1, + "type": "js", + "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", + "solution": "const {statSync, readdirSync, readFileSync} = require(\"fs\");\n\nlet searchTerm = new RegExp(process.argv[2]);\n\nfor (let arg of process.argv.slice(3)) {\n search(arg);\n}\n\nfunction search(file) {\n let stats = statSync(file);\n if (stats.isDirectory()) {\n for (let f of readdirSync(file)) {\n search(file + \"/\" + f);\n }\n } else if (searchTerm.test(readFileSync(file, \"utf8\"))) {\n console.log(file);\n }\n}\n" + }, + { + "name": "Directory creation", + "file": "code/solutions/20_2_directory_creation.js", + "number": 2, + "type": "js", + "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", + "solution": "// This code won't work on its own, but is also included in the\n// code/file_server.js file, which defines the whole system.\n\nconst {mkdir} = require(\"fs\").promises;\n\nmethods.MKCOL = async function(request) {\n let path = urlPath(request.url);\n let stats;\n try {\n stats = await stat(path);\n } catch (error) {\n if (error.code != \"ENOENT\") throw error;\n await mkdir(path);\n return {status: 204};\n }\n if (stats.isDirectory()) return {status: 204};\n else return {status: 400, body: \"Not a directory\"};\n};\n" + }, + { + "name": "A public space on the web", + "file": "code/solutions/20_3_a_public_space_on_the_web.zip", + "number": 3, + "type": "js", + "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", + "solution": "// This solutions consists of multiple files. Download it\n// though the link below.\n" + } + ], + "include": null + }, + { + "number": 21, + "id": "21_skillsharing", + "title": "Project: Skill-Sharing Website", + "start_code": "", + "exercises": [ + { + "name": "Disk persistence", + "file": "code/solutions/21_1_disk_persistence.js", + "number": 1, + "type": "js", + "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", + "solution": "// This isn't a stand-alone file, only a redefinition of a few\n// fragments from skillsharing/skillsharing_server.js\n\nconst {readFileSync, writeFile} = require(\"fs\");\n\nconst fileName = \"./talks.json\";\n\nfunction loadTalks() {\n let json;\n try {\n json = JSON.parse(readFileSync(fileName, \"utf8\"));\n } catch (e) {\n json = {};\n }\n return Object.assign(Object.create(null), json);\n}\n\nSkillShareServer.prototype.updated = function() {\n this.version++;\n let response = this.talkResponse();\n this.waiting.forEach(resolve => resolve(response));\n this.waiting = [];\n\n writeFile(fileName, JSON.stringify(this.talks), e => {\n if (e) throw e;\n });\n};\n\n// The line that starts the server must be changed to\nnew SkillShareServer(loadTalks()).start(8000);\n" + }, + { + "name": "Comment field resets", + "file": "code/solutions/21_2_comment_field_resets.js", + "number": 2, + "type": "js", + "code": "// Node exercises can not be ran in the browser,\n// but you can look at their solution here.\n", + "solution": "// This isn't a stand-alone file, only a redefinition of the main\n// component from skillsharing/public/skillsharing_client.js\n\nclass Talk {\n constructor(talk, dispatch) {\n this.comments = elt(\"div\");\n this.dom = elt(\n \"section\", {className: \"talk\"},\n elt(\"h2\", null, talk.title, \" \", elt(\"button\", {\n type: \"button\",\n onclick: () => dispatch({type: \"deleteTalk\",\n talk: talk.title})\n }, \"Delete\")),\n elt(\"div\", null, \"by \",\n elt(\"strong\", null, talk.presenter)),\n elt(\"p\", null, talk.summary),\n this.comments,\n elt(\"form\", {\n onsubmit(event) {\n event.preventDefault();\n let form = event.target;\n dispatch({type: \"newComment\",\n talk: talk.title,\n message: form.elements.comment.value});\n form.reset();\n }\n }, elt(\"input\", {type: \"text\", name: \"comment\"}), \" \",\n elt(\"button\", {type: \"submit\"}, \"Add comment\")));\n this.syncState(talk);\n }\n\n syncState(talk) {\n this.talk = talk;\n this.comments.textContent = \"\";\n for (let comment of talk.comments) {\n this.comments.appendChild(renderComment(comment));\n }\n }\n}\n\nclass SkillShareApp {\n constructor(state, dispatch) {\n this.dispatch = dispatch;\n this.talkDOM = elt(\"div\", {className: \"talks\"});\n this.talkMap = Object.create(null);\n this.dom = elt(\"div\", null,\n renderUserField(state.user, dispatch),\n this.talkDOM,\n renderTalkForm(dispatch));\n this.syncState(state);\n }\n\n syncState(state) {\n if (state.talks == this.talks) return;\n this.talks = state.talks;\n\n for (let talk of state.talks) {\n let cmp = this.talkMap[talk.title];\n if (cmp && cmp.talk.author == talk.author &&\n cmp.talk.summary == talk.summary) {\n cmp.syncState(talk);\n } else {\n if (cmp) cmp.dom.remove();\n cmp = new Talk(talk, this.dispatch);\n this.talkMap[talk.title] = cmp;\n this.talkDOM.appendChild(cmp.dom);\n }\n }\n for (let title of Object.keys(this.talkMap)) {\n if (!state.talks.some(talk => talk.title == title)) {\n this.talkMap[title].dom.remove();\n delete this.talkMap[title];\n }\n }\n }\n}\n" + } + ], + "include": null + }, + { + "title": "JavaScript and Performance", + "number": 22, + "start_code": "\n\n\n", + "include": [ + "code/draw_layout.js", + "code/chapter/22_fast.js" + ], + "exercises": [ + { + "name": "Pathfinding", + "file": "code/solutions/22_1_pathfinding.js", + "number": 1, + "type": "js", + "code": "function findPath(a, b) {\n // Your code here...\n}\n\nlet graph = treeGraph(4, 4);\nlet root = graph[0], leaf = graph[graph.length - 1];\nconsole.log(findPath(root, leaf).length);\n// → 4\n\nleaf.connect(root);\nconsole.log(findPath(root, leaf).length);\n// → 2\n", + "solution": "function findPath(a, b) {\n let work = [[a]];\n for (let path of work) {\n let end = path[path.length - 1];\n if (end == b) return path;\n for (let next of end.edges) {\n if (!work.some(path => path[path.length - 1] == next)) {\n work.push(path.concat([next]));\n }\n }\n }\n}\n\nlet graph = treeGraph(4, 4);\nlet root = graph[0], leaf = graph[graph.length - 1];\nconsole.log(findPath(root, leaf).length);\n// → 4\n\nleaf.connect(root);\nconsole.log(findPath(root, leaf).length);\n// → 2\n" + }, + { + "name": "Timing", + "file": "code/solutions/22_2_timing.js", + "number": 2, + "type": "js", + "code": "", + "solution": "function findPath(a, b) {\n let work = [[a]];\n for (let path of work) {\n let end = path[path.length - 1];\n if (end == b) return path;\n for (let next of end.edges) {\n if (!work.some(path => path[path.length - 1] == next)) {\n work.push(path.concat([next]));\n }\n }\n }\n}\n\nfunction time(findPath) {\n let graph = treeGraph(6, 6);\n let startTime = Date.now();\n let result = findPath(graph[0], graph[graph.length - 1]);\n console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`);\n}\ntime(findPath);\n" + }, + { + "name": "Optimizing", + "file": "code/solutions/22_3_optimizing.js", + "number": 3, + "type": "js", + "code": "", + "solution": "function time(findPath) {\n let graph = treeGraph(6, 6);\n let startTime = Date.now();\n let result = findPath(graph[0], graph[graph.length - 1]);\n console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`);\n}\n\nfunction findPath_set(a, b) {\n let work = [[a]];\n let reached = new Set([a]);\n for (let path of work) {\n let end = path[path.length - 1];\n if (end == b) return path;\n for (let next of end.edges) {\n if (!reached.has(next)) {\n reached.add(next);\n work.push(path.concat([next]));\n }\n }\n }\n}\n\ntime(findPath_set);\n\nfunction pathToArray(path) {\n let result = [];\n for (; path; path = path.via) result.unshift(path.at);\n return result;\n}\n\nfunction findPath_list(a, b) {\n let work = [{at: a, via: null}];\n let reached = new Set([a]);\n for (let path of work) {\n if (path.at == b) return pathToArray(path);\n for (let next of path.at.edges) {\n if (!reached.has(next)) {\n reached.add(next);\n work.push({at: next, via: path});\n }\n }\n }\n}\n\ntime(findPath_list);\n" + } + ] + } +]; diff --git a/docs/js/code.js b/docs/js/code.js new file mode 100644 index 000000000..93eb6a6be --- /dev/null +++ b/docs/js/code.js @@ -0,0 +1,217 @@ +addEventListener("load", () => { + let editor = CodeMirror.fromTextArea(document.querySelector("#editor"), { + mode: "javascript", + extraKeys: { + "Ctrl-Enter": runCode, + "Cmd-Enter": runCode + }, + matchBrackets: true, + lineNumbers: true + }) + function guessType(code) { + return /^[\s\w\n:]* { + clearTimeout(reGuess) + reGuess = setTimeout(() => { + if (context.type == null) { + let mode = guessType(editor.getValue()) == "html" ? "text/html" : "javascript" + if (mode != editor.getOption("mode")) + editor.setOption("mode", mode) + } + }, 500) + }) + + function hasIncludes(code, include) { + if (!include) return code + + let re = /(?:\s|)* + + + + + + +
        +

        RPM changes mode

        + +
        + + +

        MIME types defined: text/x-rpm-changes.

        +
        diff --git a/docs/js/node_modules/codemirror/mode/rpm/rpm.js b/docs/js/node_modules/codemirror/mode/rpm/rpm.js new file mode 100644 index 000000000..2dece2eab --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/rpm/rpm.js @@ -0,0 +1,109 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("rpm-changes", function() { + var headerSeperator = /^-+$/; + var headerLine = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /; + var simpleEmail = /^[\w+.-]+@[\w.-]+/; + + return { + token: function(stream) { + if (stream.sol()) { + if (stream.match(headerSeperator)) { return 'tag'; } + if (stream.match(headerLine)) { return 'tag'; } + } + if (stream.match(simpleEmail)) { return 'string'; } + stream.next(); + return null; + } + }; +}); + +CodeMirror.defineMIME("text/x-rpm-changes", "rpm-changes"); + +// Quick and dirty spec file highlighting + +CodeMirror.defineMode("rpm-spec", function() { + var arch = /^(i386|i586|i686|x86_64|ppc64le|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/; + + var preamble = /^[a-zA-Z0-9()]+:/; + var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pretrans|posttrans|pre|post|triggerin|triggerun|verifyscript|check|triggerpostun|triggerprein|trigger)/; + var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros + var control_flow_simple = /^%(else|endif)/; // rpm control flow macros + var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros + + return { + startState: function () { + return { + controlFlow: false, + macroParameters: false, + section: false + }; + }, + token: function (stream, state) { + var ch = stream.peek(); + if (ch == "#") { stream.skipToEnd(); return "comment"; } + + if (stream.sol()) { + if (stream.match(preamble)) { return "header"; } + if (stream.match(section)) { return "atom"; } + } + + if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT' + if (stream.match(/^\$\{\w+\}/)) { return "def"; } // Variables like '${RPM_BUILD_ROOT}' + + if (stream.match(control_flow_simple)) { return "keyword"; } + if (stream.match(control_flow_complex)) { + state.controlFlow = true; + return "keyword"; + } + if (state.controlFlow) { + if (stream.match(operators)) { return "operator"; } + if (stream.match(/^(\d+)/)) { return "number"; } + if (stream.eol()) { state.controlFlow = false; } + } + + if (stream.match(arch)) { + if (stream.eol()) { state.controlFlow = false; } + return "number"; + } + + // Macros like '%make_install' or '%attr(0775,root,root)' + if (stream.match(/^%[\w]+/)) { + if (stream.match(/^\(/)) { state.macroParameters = true; } + return "keyword"; + } + if (state.macroParameters) { + if (stream.match(/^\d+/)) { return "number";} + if (stream.match(/^\)/)) { + state.macroParameters = false; + return "keyword"; + } + } + + // Macros like '%{defined fedora}' + if (stream.match(/^%\{\??[\w \-\:\!]+\}/)) { + if (stream.eol()) { state.controlFlow = false; } + return "def"; + } + + //TODO: Include bash script sub-parser (CodeMirror supports that) + stream.next(); + return null; + } + }; +}); + +CodeMirror.defineMIME("text/x-rpm-spec", "rpm-spec"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/rst/rst.js b/docs/js/node_modules/codemirror/mode/rst/rst.js new file mode 100644 index 000000000..f14eb270f --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/rst/rst.js @@ -0,0 +1,557 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../python/python"), require("../stex/stex"), require("../../addon/mode/overlay")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../python/python", "../stex/stex", "../../addon/mode/overlay"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode('rst', function (config, options) { + + var rx_strong = /^\*\*[^\*\s](?:[^\*]*[^\*\s])?\*\*/; + var rx_emphasis = /^\*[^\*\s](?:[^\*]*[^\*\s])?\*/; + var rx_literal = /^``[^`\s](?:[^`]*[^`\s])``/; + + var rx_number = /^(?:[\d]+(?:[\.,]\d+)*)/; + var rx_positive = /^(?:\s\+[\d]+(?:[\.,]\d+)*)/; + var rx_negative = /^(?:\s\-[\d]+(?:[\.,]\d+)*)/; + + var rx_uri_protocol = "[Hh][Tt][Tt][Pp][Ss]?://"; + var rx_uri_domain = "(?:[\\d\\w.-]+)\\.(?:\\w{2,6})"; + var rx_uri_path = "(?:/[\\d\\w\\#\\%\\&\\-\\.\\,\\/\\:\\=\\?\\~]+)*"; + var rx_uri = new RegExp("^" + rx_uri_protocol + rx_uri_domain + rx_uri_path); + + var overlay = { + token: function (stream) { + + if (stream.match(rx_strong) && stream.match (/\W+|$/, false)) + return 'strong'; + if (stream.match(rx_emphasis) && stream.match (/\W+|$/, false)) + return 'em'; + if (stream.match(rx_literal) && stream.match (/\W+|$/, false)) + return 'string-2'; + if (stream.match(rx_number)) + return 'number'; + if (stream.match(rx_positive)) + return 'positive'; + if (stream.match(rx_negative)) + return 'negative'; + if (stream.match(rx_uri)) + return 'link'; + + while (stream.next() != null) { + if (stream.match(rx_strong, false)) break; + if (stream.match(rx_emphasis, false)) break; + if (stream.match(rx_literal, false)) break; + if (stream.match(rx_number, false)) break; + if (stream.match(rx_positive, false)) break; + if (stream.match(rx_negative, false)) break; + if (stream.match(rx_uri, false)) break; + } + + return null; + } + }; + + var mode = CodeMirror.getMode( + config, options.backdrop || 'rst-base' + ); + + return CodeMirror.overlayMode(mode, overlay, true); // combine +}, 'python', 'stex'); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +CodeMirror.defineMode('rst-base', function (config) { + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + function format(string) { + var args = Array.prototype.slice.call(arguments, 1); + return string.replace(/{(\d+)}/g, function (match, n) { + return typeof args[n] != 'undefined' ? args[n] : match; + }); + } + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + var mode_python = CodeMirror.getMode(config, 'python'); + var mode_stex = CodeMirror.getMode(config, 'stex'); + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + var SEPA = "\\s+"; + var TAIL = "(?:\\s*|\\W|$)", + rx_TAIL = new RegExp(format('^{0}', TAIL)); + + var NAME = + "(?:[^\\W\\d_](?:[\\w!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)", + rx_NAME = new RegExp(format('^{0}', NAME)); + var NAME_WWS = + "(?:[^\\W\\d_](?:[\\w\\s!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)"; + var REF_NAME = format('(?:{0}|`{1}`)', NAME, NAME_WWS); + + var TEXT1 = "(?:[^\\s\\|](?:[^\\|]*[^\\s\\|])?)"; + var TEXT2 = "(?:[^\\`]+)", + rx_TEXT2 = new RegExp(format('^{0}', TEXT2)); + + var rx_section = new RegExp( + "^([!'#$%&\"()*+,-./:;<=>?@\\[\\\\\\]^_`{|}~])\\1{3,}\\s*$"); + var rx_explicit = new RegExp( + format('^\\.\\.{0}', SEPA)); + var rx_link = new RegExp( + format('^_{0}:{1}|^__:{1}', REF_NAME, TAIL)); + var rx_directive = new RegExp( + format('^{0}::{1}', REF_NAME, TAIL)); + var rx_substitution = new RegExp( + format('^\\|{0}\\|{1}{2}::{3}', TEXT1, SEPA, REF_NAME, TAIL)); + var rx_footnote = new RegExp( + format('^\\[(?:\\d+|#{0}?|\\*)]{1}', REF_NAME, TAIL)); + var rx_citation = new RegExp( + format('^\\[{0}\\]{1}', REF_NAME, TAIL)); + + var rx_substitution_ref = new RegExp( + format('^\\|{0}\\|', TEXT1)); + var rx_footnote_ref = new RegExp( + format('^\\[(?:\\d+|#{0}?|\\*)]_', REF_NAME)); + var rx_citation_ref = new RegExp( + format('^\\[{0}\\]_', REF_NAME)); + var rx_link_ref1 = new RegExp( + format('^{0}__?', REF_NAME)); + var rx_link_ref2 = new RegExp( + format('^`{0}`_', TEXT2)); + + var rx_role_pre = new RegExp( + format('^:{0}:`{1}`{2}', NAME, TEXT2, TAIL)); + var rx_role_suf = new RegExp( + format('^`{1}`:{0}:{2}', NAME, TEXT2, TAIL)); + var rx_role = new RegExp( + format('^:{0}:{1}', NAME, TAIL)); + + var rx_directive_name = new RegExp(format('^{0}', REF_NAME)); + var rx_directive_tail = new RegExp(format('^::{0}', TAIL)); + var rx_substitution_text = new RegExp(format('^\\|{0}\\|', TEXT1)); + var rx_substitution_sepa = new RegExp(format('^{0}', SEPA)); + var rx_substitution_name = new RegExp(format('^{0}', REF_NAME)); + var rx_substitution_tail = new RegExp(format('^::{0}', TAIL)); + var rx_link_head = new RegExp("^_"); + var rx_link_name = new RegExp(format('^{0}|_', REF_NAME)); + var rx_link_tail = new RegExp(format('^:{0}', TAIL)); + + var rx_verbatim = new RegExp('^::\\s*$'); + var rx_examples = new RegExp('^\\s+(?:>>>|In \\[\\d+\\]:)\\s'); + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + function to_normal(stream, state) { + var token = null; + + if (stream.sol() && stream.match(rx_examples, false)) { + change(state, to_mode, { + mode: mode_python, local: CodeMirror.startState(mode_python) + }); + } else if (stream.sol() && stream.match(rx_explicit)) { + change(state, to_explicit); + token = 'meta'; + } else if (stream.sol() && stream.match(rx_section)) { + change(state, to_normal); + token = 'header'; + } else if (phase(state) == rx_role_pre || + stream.match(rx_role_pre, false)) { + + switch (stage(state)) { + case 0: + change(state, to_normal, context(rx_role_pre, 1)); + stream.match(/^:/); + token = 'meta'; + break; + case 1: + change(state, to_normal, context(rx_role_pre, 2)); + stream.match(rx_NAME); + token = 'keyword'; + + if (stream.current().match(/^(?:math|latex)/)) { + state.tmp_stex = true; + } + break; + case 2: + change(state, to_normal, context(rx_role_pre, 3)); + stream.match(/^:`/); + token = 'meta'; + break; + case 3: + if (state.tmp_stex) { + state.tmp_stex = undefined; state.tmp = { + mode: mode_stex, local: CodeMirror.startState(mode_stex) + }; + } + + if (state.tmp) { + if (stream.peek() == '`') { + change(state, to_normal, context(rx_role_pre, 4)); + state.tmp = undefined; + break; + } + + token = state.tmp.mode.token(stream, state.tmp.local); + break; + } + + change(state, to_normal, context(rx_role_pre, 4)); + stream.match(rx_TEXT2); + token = 'string'; + break; + case 4: + change(state, to_normal, context(rx_role_pre, 5)); + stream.match(/^`/); + token = 'meta'; + break; + case 5: + change(state, to_normal, context(rx_role_pre, 6)); + stream.match(rx_TAIL); + break; + default: + change(state, to_normal); + } + } else if (phase(state) == rx_role_suf || + stream.match(rx_role_suf, false)) { + + switch (stage(state)) { + case 0: + change(state, to_normal, context(rx_role_suf, 1)); + stream.match(/^`/); + token = 'meta'; + break; + case 1: + change(state, to_normal, context(rx_role_suf, 2)); + stream.match(rx_TEXT2); + token = 'string'; + break; + case 2: + change(state, to_normal, context(rx_role_suf, 3)); + stream.match(/^`:/); + token = 'meta'; + break; + case 3: + change(state, to_normal, context(rx_role_suf, 4)); + stream.match(rx_NAME); + token = 'keyword'; + break; + case 4: + change(state, to_normal, context(rx_role_suf, 5)); + stream.match(/^:/); + token = 'meta'; + break; + case 5: + change(state, to_normal, context(rx_role_suf, 6)); + stream.match(rx_TAIL); + break; + default: + change(state, to_normal); + } + } else if (phase(state) == rx_role || stream.match(rx_role, false)) { + + switch (stage(state)) { + case 0: + change(state, to_normal, context(rx_role, 1)); + stream.match(/^:/); + token = 'meta'; + break; + case 1: + change(state, to_normal, context(rx_role, 2)); + stream.match(rx_NAME); + token = 'keyword'; + break; + case 2: + change(state, to_normal, context(rx_role, 3)); + stream.match(/^:/); + token = 'meta'; + break; + case 3: + change(state, to_normal, context(rx_role, 4)); + stream.match(rx_TAIL); + break; + default: + change(state, to_normal); + } + } else if (phase(state) == rx_substitution_ref || + stream.match(rx_substitution_ref, false)) { + + switch (stage(state)) { + case 0: + change(state, to_normal, context(rx_substitution_ref, 1)); + stream.match(rx_substitution_text); + token = 'variable-2'; + break; + case 1: + change(state, to_normal, context(rx_substitution_ref, 2)); + if (stream.match(/^_?_?/)) token = 'link'; + break; + default: + change(state, to_normal); + } + } else if (stream.match(rx_footnote_ref)) { + change(state, to_normal); + token = 'quote'; + } else if (stream.match(rx_citation_ref)) { + change(state, to_normal); + token = 'quote'; + } else if (stream.match(rx_link_ref1)) { + change(state, to_normal); + if (!stream.peek() || stream.peek().match(/^\W$/)) { + token = 'link'; + } + } else if (phase(state) == rx_link_ref2 || + stream.match(rx_link_ref2, false)) { + + switch (stage(state)) { + case 0: + if (!stream.peek() || stream.peek().match(/^\W$/)) { + change(state, to_normal, context(rx_link_ref2, 1)); + } else { + stream.match(rx_link_ref2); + } + break; + case 1: + change(state, to_normal, context(rx_link_ref2, 2)); + stream.match(/^`/); + token = 'link'; + break; + case 2: + change(state, to_normal, context(rx_link_ref2, 3)); + stream.match(rx_TEXT2); + break; + case 3: + change(state, to_normal, context(rx_link_ref2, 4)); + stream.match(/^`_/); + token = 'link'; + break; + default: + change(state, to_normal); + } + } else if (stream.match(rx_verbatim)) { + change(state, to_verbatim); + } + + else { + if (stream.next()) change(state, to_normal); + } + + return token; + } + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + function to_explicit(stream, state) { + var token = null; + + if (phase(state) == rx_substitution || + stream.match(rx_substitution, false)) { + + switch (stage(state)) { + case 0: + change(state, to_explicit, context(rx_substitution, 1)); + stream.match(rx_substitution_text); + token = 'variable-2'; + break; + case 1: + change(state, to_explicit, context(rx_substitution, 2)); + stream.match(rx_substitution_sepa); + break; + case 2: + change(state, to_explicit, context(rx_substitution, 3)); + stream.match(rx_substitution_name); + token = 'keyword'; + break; + case 3: + change(state, to_explicit, context(rx_substitution, 4)); + stream.match(rx_substitution_tail); + token = 'meta'; + break; + default: + change(state, to_normal); + } + } else if (phase(state) == rx_directive || + stream.match(rx_directive, false)) { + + switch (stage(state)) { + case 0: + change(state, to_explicit, context(rx_directive, 1)); + stream.match(rx_directive_name); + token = 'keyword'; + + if (stream.current().match(/^(?:math|latex)/)) + state.tmp_stex = true; + else if (stream.current().match(/^python/)) + state.tmp_py = true; + break; + case 1: + change(state, to_explicit, context(rx_directive, 2)); + stream.match(rx_directive_tail); + token = 'meta'; + + if (stream.match(/^latex\s*$/) || state.tmp_stex) { + state.tmp_stex = undefined; change(state, to_mode, { + mode: mode_stex, local: CodeMirror.startState(mode_stex) + }); + } + break; + case 2: + change(state, to_explicit, context(rx_directive, 3)); + if (stream.match(/^python\s*$/) || state.tmp_py) { + state.tmp_py = undefined; change(state, to_mode, { + mode: mode_python, local: CodeMirror.startState(mode_python) + }); + } + break; + default: + change(state, to_normal); + } + } else if (phase(state) == rx_link || stream.match(rx_link, false)) { + + switch (stage(state)) { + case 0: + change(state, to_explicit, context(rx_link, 1)); + stream.match(rx_link_head); + stream.match(rx_link_name); + token = 'link'; + break; + case 1: + change(state, to_explicit, context(rx_link, 2)); + stream.match(rx_link_tail); + token = 'meta'; + break; + default: + change(state, to_normal); + } + } else if (stream.match(rx_footnote)) { + change(state, to_normal); + token = 'quote'; + } else if (stream.match(rx_citation)) { + change(state, to_normal); + token = 'quote'; + } + + else { + stream.eatSpace(); + if (stream.eol()) { + change(state, to_normal); + } else { + stream.skipToEnd(); + change(state, to_comment); + token = 'comment'; + } + } + + return token; + } + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + function to_comment(stream, state) { + return as_block(stream, state, 'comment'); + } + + function to_verbatim(stream, state) { + return as_block(stream, state, 'meta'); + } + + function as_block(stream, state, token) { + if (stream.eol() || stream.eatSpace()) { + stream.skipToEnd(); + return token; + } else { + change(state, to_normal); + return null; + } + } + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + function to_mode(stream, state) { + + if (state.ctx.mode && state.ctx.local) { + + if (stream.sol()) { + if (!stream.eatSpace()) change(state, to_normal); + return null; + } + + return state.ctx.mode.token(stream, state.ctx.local); + } + + change(state, to_normal); + return null; + } + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + function context(phase, stage, mode, local) { + return {phase: phase, stage: stage, mode: mode, local: local}; + } + + function change(state, tok, ctx) { + state.tok = tok; + state.ctx = ctx || {}; + } + + function stage(state) { + return state.ctx.stage || 0; + } + + function phase(state) { + return state.ctx.phase; + } + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + return { + startState: function () { + return {tok: to_normal, ctx: context(undefined, 0)}; + }, + + copyState: function (state) { + var ctx = state.ctx, tmp = state.tmp; + if (ctx.local) + ctx = {mode: ctx.mode, local: CodeMirror.copyState(ctx.mode, ctx.local)}; + if (tmp) + tmp = {mode: tmp.mode, local: CodeMirror.copyState(tmp.mode, tmp.local)}; + return {tok: state.tok, ctx: ctx, tmp: tmp}; + }, + + innerMode: function (state) { + return state.tmp ? {state: state.tmp.local, mode: state.tmp.mode} + : state.ctx.mode ? {state: state.ctx.local, mode: state.ctx.mode} + : null; + }, + + token: function (stream, state) { + return state.tok(stream, state); + } + }; +}, 'python', 'stex'); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +CodeMirror.defineMIME('text/x-rst', 'rst'); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +}); diff --git a/docs/js/node_modules/codemirror/mode/ruby/ruby.js b/docs/js/node_modules/codemirror/mode/ruby/ruby.js new file mode 100644 index 000000000..dd0e603e5 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/ruby/ruby.js @@ -0,0 +1,298 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("ruby", function(config) { + function wordObj(words) { + var o = {}; + for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true; + return o; + } + var keywords = wordObj([ + "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", + "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or", + "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", + "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc", + "caller", "lambda", "proc", "public", "protected", "private", "require", "load", + "require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__" + ]); + var indentWords = wordObj(["def", "class", "case", "for", "while", "until", "module", "then", + "catch", "loop", "proc", "begin"]); + var dedentWords = wordObj(["end", "until"]); + var opening = {"[": "]", "{": "}", "(": ")"}; + var closing = {"]": "[", "}": "{", ")": "("}; + var curPunc; + + function chain(newtok, stream, state) { + state.tokenize.push(newtok); + return newtok(stream, state); + } + + function tokenBase(stream, state) { + if (stream.sol() && stream.match("=begin") && stream.eol()) { + state.tokenize.push(readBlockComment); + return "comment"; + } + if (stream.eatSpace()) return null; + var ch = stream.next(), m; + if (ch == "`" || ch == "'" || ch == '"') { + return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state); + } else if (ch == "/") { + if (regexpAhead(stream)) + return chain(readQuoted(ch, "string-2", true), stream, state); + else + return "operator"; + } else if (ch == "%") { + var style = "string", embed = true; + if (stream.eat("s")) style = "atom"; + else if (stream.eat(/[WQ]/)) style = "string"; + else if (stream.eat(/[r]/)) style = "string-2"; + else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; } + var delim = stream.eat(/[^\w\s=]/); + if (!delim) return "operator"; + if (opening.propertyIsEnumerable(delim)) delim = opening[delim]; + return chain(readQuoted(delim, style, embed, true), stream, state); + } else if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } else if (ch == "<" && (m = stream.match(/^<([-~])[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) { + return chain(readHereDoc(m[2], m[1]), stream, state); + } else if (ch == "0") { + if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/); + else if (stream.eat("b")) stream.eatWhile(/[01]/); + else stream.eatWhile(/[0-7]/); + return "number"; + } else if (/\d/.test(ch)) { + stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/); + return "number"; + } else if (ch == "?") { + while (stream.match(/^\\[CM]-/)) {} + if (stream.eat("\\")) stream.eatWhile(/\w/); + else stream.next(); + return "string"; + } else if (ch == ":") { + if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state); + if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state); + + // :> :>> :< :<< are valid symbols + if (stream.eat(/[\<\>]/)) { + stream.eat(/[\<\>]/); + return "atom"; + } + + // :+ :- :/ :* :| :& :! are valid symbols + if (stream.eat(/[\+\-\*\/\&\|\:\!]/)) { + return "atom"; + } + + // Symbols can't start by a digit + if (stream.eat(/[a-zA-Z$@_\xa1-\uffff]/)) { + stream.eatWhile(/[\w$\xa1-\uffff]/); + // Only one ? ! = is allowed and only as the last character + stream.eat(/[\?\!\=]/); + return "atom"; + } + return "operator"; + } else if (ch == "@" && stream.match(/^@?[a-zA-Z_\xa1-\uffff]/)) { + stream.eat("@"); + stream.eatWhile(/[\w\xa1-\uffff]/); + return "variable-2"; + } else if (ch == "$") { + if (stream.eat(/[a-zA-Z_]/)) { + stream.eatWhile(/[\w]/); + } else if (stream.eat(/\d/)) { + stream.eat(/\d/); + } else { + stream.next(); // Must be a special global like $: or $! + } + return "variable-3"; + } else if (/[a-zA-Z_\xa1-\uffff]/.test(ch)) { + stream.eatWhile(/[\w\xa1-\uffff]/); + stream.eat(/[\?\!]/); + if (stream.eat(":")) return "atom"; + return "ident"; + } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) { + curPunc = "|"; + return null; + } else if (/[\(\)\[\]{}\\;]/.test(ch)) { + curPunc = ch; + return null; + } else if (ch == "-" && stream.eat(">")) { + return "arrow"; + } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) { + var more = stream.eatWhile(/[=+\-\/*:\.^%<>~|]/); + if (ch == "." && !more) curPunc = "."; + return "operator"; + } else { + return null; + } + } + + function regexpAhead(stream) { + var start = stream.pos, depth = 0, next, found = false, escaped = false + while ((next = stream.next()) != null) { + if (!escaped) { + if ("[{(".indexOf(next) > -1) { + depth++ + } else if ("]})".indexOf(next) > -1) { + depth-- + if (depth < 0) break + } else if (next == "/" && depth == 0) { + found = true + break + } + escaped = next == "\\" + } else { + escaped = false + } + } + stream.backUp(stream.pos - start) + return found + } + + function tokenBaseUntilBrace(depth) { + if (!depth) depth = 1; + return function(stream, state) { + if (stream.peek() == "}") { + if (depth == 1) { + state.tokenize.pop(); + return state.tokenize[state.tokenize.length-1](stream, state); + } else { + state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth - 1); + } + } else if (stream.peek() == "{") { + state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth + 1); + } + return tokenBase(stream, state); + }; + } + function tokenBaseOnce() { + var alreadyCalled = false; + return function(stream, state) { + if (alreadyCalled) { + state.tokenize.pop(); + return state.tokenize[state.tokenize.length-1](stream, state); + } + alreadyCalled = true; + return tokenBase(stream, state); + }; + } + function readQuoted(quote, style, embed, unescaped) { + return function(stream, state) { + var escaped = false, ch; + + if (state.context.type === 'read-quoted-paused') { + state.context = state.context.prev; + stream.eat("}"); + } + + while ((ch = stream.next()) != null) { + if (ch == quote && (unescaped || !escaped)) { + state.tokenize.pop(); + break; + } + if (embed && ch == "#" && !escaped) { + if (stream.eat("{")) { + if (quote == "}") { + state.context = {prev: state.context, type: 'read-quoted-paused'}; + } + state.tokenize.push(tokenBaseUntilBrace()); + break; + } else if (/[@\$]/.test(stream.peek())) { + state.tokenize.push(tokenBaseOnce()); + break; + } + } + escaped = !escaped && ch == "\\"; + } + return style; + }; + } + function readHereDoc(phrase, mayIndent) { + return function(stream, state) { + if (mayIndent) stream.eatSpace() + if (stream.match(phrase)) state.tokenize.pop(); + else stream.skipToEnd(); + return "string"; + }; + } + function readBlockComment(stream, state) { + if (stream.sol() && stream.match("=end") && stream.eol()) + state.tokenize.pop(); + stream.skipToEnd(); + return "comment"; + } + + return { + startState: function() { + return {tokenize: [tokenBase], + indented: 0, + context: {type: "top", indented: -config.indentUnit}, + continuedLine: false, + lastTok: null, + varList: false}; + }, + + token: function(stream, state) { + curPunc = null; + if (stream.sol()) state.indented = stream.indentation(); + var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype; + var thisTok = curPunc; + if (style == "ident") { + var word = stream.current(); + style = state.lastTok == "." ? "property" + : keywords.propertyIsEnumerable(stream.current()) ? "keyword" + : /^[A-Z]/.test(word) ? "tag" + : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def" + : "variable"; + if (style == "keyword") { + thisTok = word; + if (indentWords.propertyIsEnumerable(word)) kwtype = "indent"; + else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent"; + else if ((word == "if" || word == "unless") && stream.column() == stream.indentation()) + kwtype = "indent"; + else if (word == "do" && state.context.indented < state.indented) + kwtype = "indent"; + } + } + if (curPunc || (style && style != "comment")) state.lastTok = thisTok; + if (curPunc == "|") state.varList = !state.varList; + + if (kwtype == "indent" || /[\(\[\{]/.test(curPunc)) + state.context = {prev: state.context, type: curPunc || style, indented: state.indented}; + else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev) + state.context = state.context.prev; + + if (stream.eol()) + state.continuedLine = (curPunc == "\\" || style == "operator"); + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize[state.tokenize.length-1] != tokenBase) return CodeMirror.Pass; + var firstChar = textAfter && textAfter.charAt(0); + var ct = state.context; + var closed = ct.type == closing[firstChar] || + ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter); + return ct.indented + (closed ? 0 : config.indentUnit) + + (state.continuedLine ? config.indentUnit : 0); + }, + + electricInput: /^\s*(?:end|rescue|elsif|else|\})$/, + lineComment: "#", + fold: "indent" + }; +}); + +CodeMirror.defineMIME("text/x-ruby", "ruby"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/rust/rust.js b/docs/js/node_modules/codemirror/mode/rust/rust.js new file mode 100644 index 000000000..6bcfbc444 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/rust/rust.js @@ -0,0 +1,72 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../../addon/mode/simple"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineSimpleMode("rust",{ + start: [ + // string and byte string + {regex: /b?"/, token: "string", next: "string"}, + // raw string and raw byte string + {regex: /b?r"/, token: "string", next: "string_raw"}, + {regex: /b?r#+"/, token: "string", next: "string_raw_hash"}, + // character + {regex: /'(?:[^'\\]|\\(?:[nrt0'"]|x[\da-fA-F]{2}|u\{[\da-fA-F]{6}\}))'/, token: "string-2"}, + // byte + {regex: /b'(?:[^']|\\(?:['\\nrt0]|x[\da-fA-F]{2}))'/, token: "string-2"}, + + {regex: /(?:(?:[0-9][0-9_]*)(?:(?:[Ee][+-]?[0-9_]+)|\.[0-9_]+(?:[Ee][+-]?[0-9_]+)?)(?:f32|f64)?)|(?:0(?:b[01_]+|(?:o[0-7_]+)|(?:x[0-9a-fA-F_]+))|(?:[0-9][0-9_]*))(?:u8|u16|u32|u64|i8|i16|i32|i64|isize|usize)?/, + token: "number"}, + {regex: /(let(?:\s+mut)?|fn|enum|mod|struct|type)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/, token: ["keyword", null, "def"]}, + {regex: /(?:abstract|alignof|as|box|break|continue|const|crate|do|else|enum|extern|fn|for|final|if|impl|in|loop|macro|match|mod|move|offsetof|override|priv|proc|pub|pure|ref|return|self|sizeof|static|struct|super|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/, token: "keyword"}, + {regex: /\b(?:Self|isize|usize|char|bool|u8|u16|u32|u64|f16|f32|f64|i8|i16|i32|i64|str|Option)\b/, token: "atom"}, + {regex: /\b(?:true|false|Some|None|Ok|Err)\b/, token: "builtin"}, + {regex: /\b(fn)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/, + token: ["keyword", null ,"def"]}, + {regex: /#!?\[.*\]/, token: "meta"}, + {regex: /\/\/.*/, token: "comment"}, + {regex: /\/\*/, token: "comment", next: "comment"}, + {regex: /[-+\/*=<>!]+/, token: "operator"}, + {regex: /[a-zA-Z_]\w*!/,token: "variable-3"}, + {regex: /[a-zA-Z_]\w*/, token: "variable"}, + {regex: /[\{\[\(]/, indent: true}, + {regex: /[\}\]\)]/, dedent: true} + ], + string: [ + {regex: /"/, token: "string", next: "start"}, + {regex: /(?:[^\\"]|\\(?:.|$))*/, token: "string"} + ], + string_raw: [ + {regex: /"/, token: "string", next: "start"}, + {regex: /[^"]*/, token: "string"} + ], + string_raw_hash: [ + {regex: /"#+/, token: "string", next: "start"}, + {regex: /(?:[^"]|"(?!#))*/, token: "string"} + ], + comment: [ + {regex: /.*?\*\//, token: "comment", next: "start"}, + {regex: /.*/, token: "comment"} + ], + meta: { + dontIndentStates: ["comment"], + electricInput: /^\s*\}$/, + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//", + fold: "brace" + } +}); + + +CodeMirror.defineMIME("text/x-rustsrc", "rust"); +CodeMirror.defineMIME("text/rust", "rust"); +}); diff --git a/docs/js/node_modules/codemirror/mode/sas/sas.js b/docs/js/node_modules/codemirror/mode/sas/sas.js new file mode 100755 index 000000000..c6f528e0a --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/sas/sas.js @@ -0,0 +1,303 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + + +// SAS mode copyright (c) 2016 Jared Dean, SAS Institute +// Created by Jared Dean + +// TODO +// indent and de-indent +// identify macro variables + + +//Definitions +// comment -- text within * ; or /* */ +// keyword -- SAS language variable +// variable -- macro variables starts with '&' or variable formats +// variable-2 -- DATA Step, proc, or macro names +// string -- text within ' ' or " " +// operator -- numeric operator + / - * ** le eq ge ... and so on +// builtin -- proc %macro data run mend +// atom +// def + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("sas", function () { + var words = {}; + var isDoubleOperatorSym = { + eq: 'operator', + lt: 'operator', + le: 'operator', + gt: 'operator', + ge: 'operator', + "in": 'operator', + ne: 'operator', + or: 'operator' + }; + var isDoubleOperatorChar = /(<=|>=|!=|<>)/; + var isSingleOperatorChar = /[=\(:\),{}.*<>+\-\/^\[\]]/; + + // Takes a string of words separated by spaces and adds them as + // keys with the value of the first argument 'style' + function define(style, string, context) { + if (context) { + var split = string.split(' '); + for (var i = 0; i < split.length; i++) { + words[split[i]] = {style: style, state: context}; + } + } + } + //datastep + define('def', 'stack pgm view source debug nesting nolist', ['inDataStep']); + define('def', 'if while until for do do; end end; then else cancel', ['inDataStep']); + define('def', 'label format _n_ _error_', ['inDataStep']); + define('def', 'ALTER BUFNO BUFSIZE CNTLLEV COMPRESS DLDMGACTION ENCRYPT ENCRYPTKEY EXTENDOBSCOUNTER GENMAX GENNUM INDEX LABEL OBSBUF OUTREP PW PWREQ READ REPEMPTY REPLACE REUSE ROLE SORTEDBY SPILL TOBSNO TYPE WRITE FILECLOSE FIRSTOBS IN OBS POINTOBS WHERE WHEREUP IDXNAME IDXWHERE DROP KEEP RENAME', ['inDataStep']); + define('def', 'filevar finfo finv fipname fipnamel fipstate first firstobs floor', ['inDataStep']); + define('def', 'varfmt varinfmt varlabel varlen varname varnum varray varrayx vartype verify vformat vformatd vformatdx vformatn vformatnx vformatw vformatwx vformatx vinarray vinarrayx vinformat vinformatd vinformatdx vinformatn vinformatnx vinformatw vinformatwx vinformatx vlabel vlabelx vlength vlengthx vname vnamex vnferr vtype vtypex weekday', ['inDataStep']); + define('def', 'zipfips zipname zipnamel zipstate', ['inDataStep']); + define('def', 'put putc putn', ['inDataStep']); + define('builtin', 'data run', ['inDataStep']); + + + //proc + define('def', 'data', ['inProc']); + + // flow control for macros + define('def', '%if %end %end; %else %else; %do %do; %then', ['inMacro']); + + //everywhere + define('builtin', 'proc run; quit; libname filename %macro %mend option options', ['ALL']); + + define('def', 'footnote title libname ods', ['ALL']); + define('def', '%let %put %global %sysfunc %eval ', ['ALL']); + // automatic macro variables http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/viewer.htm#a003167023.htm + define('variable', '&sysbuffr &syscc &syscharwidth &syscmd &sysdate &sysdate9 &sysday &sysdevic &sysdmg &sysdsn &sysencoding &sysenv &syserr &syserrortext &sysfilrc &syshostname &sysindex &sysinfo &sysjobid &syslast &syslckrc &syslibrc &syslogapplname &sysmacroname &sysmenv &sysmsg &sysncpu &sysodspath &sysparm &syspbuff &sysprocessid &sysprocessname &sysprocname &sysrc &sysscp &sysscpl &sysscpl &syssite &sysstartid &sysstartname &systcpiphostname &systime &sysuserid &sysver &sysvlong &sysvlong4 &syswarningtext', ['ALL']); + + //footnote[1-9]? title[1-9]? + + //options statement + define('def', 'source2 nosource2 page pageno pagesize', ['ALL']); + + //proc and datastep + define('def', '_all_ _character_ _cmd_ _freq_ _i_ _infile_ _last_ _msg_ _null_ _numeric_ _temporary_ _type_ abort abs addr adjrsq airy alpha alter altlog altprint and arcos array arsin as atan attrc attrib attrn authserver autoexec awscontrol awsdef awsmenu awsmenumerge awstitle backward band base betainv between blocksize blshift bnot bor brshift bufno bufsize bxor by byerr byline byte calculated call cards cards4 catcache cbufno cdf ceil center cexist change chisq cinv class cleanup close cnonct cntllev coalesce codegen col collate collin column comamid comaux1 comaux2 comdef compbl compound compress config continue convert cos cosh cpuid create cross crosstab css curobs cv daccdb daccdbsl daccsl daccsyd dacctab dairy datalines datalines4 datejul datepart datetime day dbcslang dbcstype dclose ddm delete delimiter depdb depdbsl depsl depsyd deptab dequote descending descript design= device dflang dhms dif digamma dim dinfo display distinct dkricond dkrocond dlm dnum do dopen doptname doptnum dread drop dropnote dsname dsnferr echo else emaildlg emailid emailpw emailserver emailsys encrypt end endsas engine eof eov erf erfc error errorcheck errors exist exp fappend fclose fcol fdelete feedback fetch fetchobs fexist fget file fileclose fileexist filefmt filename fileref fmterr fmtsearch fnonct fnote font fontalias fopen foptname foptnum force formatted formchar formdelim formdlim forward fpoint fpos fput fread frewind frlen from fsep fuzz fwrite gaminv gamma getoption getvarc getvarn go goto group gwindow hbar hbound helpenv helploc hms honorappearance hosthelp hostprint hour hpct html hvar ibessel ibr id if index indexc indexw initcmd initstmt inner input inputc inputn inr insert int intck intnx into intrr invaliddata irr is jbessel join juldate keep kentb kurtosis label lag last lbound leave left length levels lgamma lib library libref line linesize link list log log10 log2 logpdf logpmf logsdf lostcard lowcase lrecl ls macro macrogen maps mautosource max maxdec maxr mdy mean measures median memtype merge merror min minute missing missover mlogic mod mode model modify month mopen mort mprint mrecall msglevel msymtabmax mvarsize myy n nest netpv new news nmiss no nobatch nobs nocaps nocardimage nocenter nocharcode nocmdmac nocol nocum nodate nodbcs nodetails nodmr nodms nodmsbatch nodup nodupkey noduplicates noechoauto noequals noerrorabend noexitwindows nofullstimer noicon noimplmac noint nolist noloadlist nomiss nomlogic nomprint nomrecall nomsgcase nomstored nomultenvappl nonotes nonumber noobs noovp nopad nopercent noprint noprintinit normal norow norsasuser nosetinit nosplash nosymbolgen note notes notitle notitles notsorted noverbose noxsync noxwait npv null number numkeys nummousekeys nway obs on open order ordinal otherwise out outer outp= output over ovp p(1 5 10 25 50 75 90 95 99) pad pad2 paired parm parmcards path pathdll pathname pdf peek peekc pfkey pmf point poisson poke position printer probbeta probbnml probchi probf probgam probhypr probit probnegb probnorm probsig probt procleave prt ps pw pwreq qtr quote r ranbin rancau ranexp rangam range ranks rannor ranpoi rantbl rantri ranuni read recfm register regr remote remove rename repeat replace resolve retain return reuse reverse rewind right round rsquare rtf rtrace rtraceloc s s2 samploc sasautos sascontrol sasfrscr sasmsg sasmstore sasscript sasuser saving scan sdf second select selection separated seq serror set setcomm setot sign simple sin sinh siteinfo skewness skip sle sls sortedby sortpgm sortseq sortsize soundex spedis splashlocation split spool sqrt start std stderr stdin stfips stimer stname stnamel stop stopover subgroup subpopn substr sum sumwgt symbol symbolgen symget symput sysget sysin sysleave sysmsg sysparm sysprint sysprintfont sysprod sysrc system t table tables tan tanh tapeclose tbufsize terminal test then timepart tinv tnonct to today tol tooldef totper transformout translate trantab tranwrd trigamma trim trimn trunc truncover type unformatted uniform union until upcase update user usericon uss validate value var weight when where while wincharset window work workinit workterm write wsum xsync xwait yearcutoff yes yyq min max', ['inDataStep', 'inProc']); + define('operator', 'and not ', ['inDataStep', 'inProc']); + + // Main function + function tokenize(stream, state) { + // Finally advance the stream + var ch = stream.next(); + + // BLOCKCOMMENT + if (ch === '/' && stream.eat('*')) { + state.continueComment = true; + return "comment"; + } else if (state.continueComment === true) { // in comment block + //comment ends at the beginning of the line + if (ch === '*' && stream.peek() === '/') { + stream.next(); + state.continueComment = false; + } else if (stream.skipTo('*')) { //comment is potentially later in line + stream.skipTo('*'); + stream.next(); + if (stream.eat('/')) + state.continueComment = false; + } else { + stream.skipToEnd(); + } + return "comment"; + } + + if (ch == "*" && stream.column() == stream.indentation()) { + stream.skipToEnd() + return "comment" + } + + // DoubleOperator match + var doubleOperator = ch + stream.peek(); + + if ((ch === '"' || ch === "'") && !state.continueString) { + state.continueString = ch + return "string" + } else if (state.continueString) { + if (state.continueString == ch) { + state.continueString = null; + } else if (stream.skipTo(state.continueString)) { + // quote found on this line + stream.next(); + state.continueString = null; + } else { + stream.skipToEnd(); + } + return "string"; + } else if (state.continueString !== null && stream.eol()) { + stream.skipTo(state.continueString) || stream.skipToEnd(); + return "string"; + } else if (/[\d\.]/.test(ch)) { //find numbers + if (ch === ".") + stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); + else if (ch === "0") + stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); + else + stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); + return "number"; + } else if (isDoubleOperatorChar.test(ch + stream.peek())) { // TWO SYMBOL TOKENS + stream.next(); + return "operator"; + } else if (isDoubleOperatorSym.hasOwnProperty(doubleOperator)) { + stream.next(); + if (stream.peek() === ' ') + return isDoubleOperatorSym[doubleOperator.toLowerCase()]; + } else if (isSingleOperatorChar.test(ch)) { // SINGLE SYMBOL TOKENS + return "operator"; + } + + // Matches one whole word -- even if the word is a character + var word; + if (stream.match(/[%&;\w]+/, false) != null) { + word = ch + stream.match(/[%&;\w]+/, true); + if (/&/.test(word)) return 'variable' + } else { + word = ch; + } + // the word after DATA PROC or MACRO + if (state.nextword) { + stream.match(/[\w]+/); + // match memname.libname + if (stream.peek() === '.') stream.skipTo(' '); + state.nextword = false; + return 'variable-2'; + } + + word = word.toLowerCase() + // Are we in a DATA Step? + if (state.inDataStep) { + if (word === 'run;' || stream.match(/run\s;/)) { + state.inDataStep = false; + return 'builtin'; + } + // variable formats + if ((word) && stream.next() === '.') { + //either a format or libname.memname + if (/\w/.test(stream.peek())) return 'variable-2'; + else return 'variable'; + } + // do we have a DATA Step keyword + if (word && words.hasOwnProperty(word) && + (words[word].state.indexOf("inDataStep") !== -1 || + words[word].state.indexOf("ALL") !== -1)) { + //backup to the start of the word + if (stream.start < stream.pos) + stream.backUp(stream.pos - stream.start); + //advance the length of the word and return + for (var i = 0; i < word.length; ++i) stream.next(); + return words[word].style; + } + } + // Are we in an Proc statement? + if (state.inProc) { + if (word === 'run;' || word === 'quit;') { + state.inProc = false; + return 'builtin'; + } + // do we have a proc keyword + if (word && words.hasOwnProperty(word) && + (words[word].state.indexOf("inProc") !== -1 || + words[word].state.indexOf("ALL") !== -1)) { + stream.match(/[\w]+/); + return words[word].style; + } + } + // Are we in a Macro statement? + if (state.inMacro) { + if (word === '%mend') { + if (stream.peek() === ';') stream.next(); + state.inMacro = false; + return 'builtin'; + } + if (word && words.hasOwnProperty(word) && + (words[word].state.indexOf("inMacro") !== -1 || + words[word].state.indexOf("ALL") !== -1)) { + stream.match(/[\w]+/); + return words[word].style; + } + + return 'atom'; + } + // Do we have Keywords specific words? + if (word && words.hasOwnProperty(word)) { + // Negates the initial next() + stream.backUp(1); + // Actually move the stream + stream.match(/[\w]+/); + if (word === 'data' && /=/.test(stream.peek()) === false) { + state.inDataStep = true; + state.nextword = true; + return 'builtin'; + } + if (word === 'proc') { + state.inProc = true; + state.nextword = true; + return 'builtin'; + } + if (word === '%macro') { + state.inMacro = true; + state.nextword = true; + return 'builtin'; + } + if (/title[1-9]/.test(word)) return 'def'; + + if (word === 'footnote') { + stream.eat(/[1-9]/); + return 'def'; + } + + // Returns their value as state in the prior define methods + if (state.inDataStep === true && words[word].state.indexOf("inDataStep") !== -1) + return words[word].style; + if (state.inProc === true && words[word].state.indexOf("inProc") !== -1) + return words[word].style; + if (state.inMacro === true && words[word].state.indexOf("inMacro") !== -1) + return words[word].style; + if (words[word].state.indexOf("ALL") !== -1) + return words[word].style; + return null; + } + // Unrecognized syntax + return null; + } + + return { + startState: function () { + return { + inDataStep: false, + inProc: false, + inMacro: false, + nextword: false, + continueString: null, + continueComment: false + }; + }, + token: function (stream, state) { + // Strip the spaces, but regex will account for them either way + if (stream.eatSpace()) return null; + // Go through the main process + return tokenize(stream, state); + }, + + blockCommentStart: "/*", + blockCommentEnd: "*/" + }; + + }); + + CodeMirror.defineMIME("text/x-sas", "sas"); +}); diff --git a/docs/js/node_modules/codemirror/mode/sass/sass.js b/docs/js/node_modules/codemirror/mode/sass/sass.js new file mode 100644 index 000000000..c37ab0b28 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/sass/sass.js @@ -0,0 +1,454 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../css/css")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../css/css"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("sass", function(config) { + var cssMode = CodeMirror.mimeModes["text/css"]; + var propertyKeywords = cssMode.propertyKeywords || {}, + colorKeywords = cssMode.colorKeywords || {}, + valueKeywords = cssMode.valueKeywords || {}, + fontProperties = cssMode.fontProperties || {}; + + function tokenRegexp(words) { + return new RegExp("^" + words.join("|")); + } + + var keywords = ["true", "false", "null", "auto"]; + var keywordsRegexp = new RegExp("^" + keywords.join("|")); + + var operators = ["\\(", "\\)", "=", ">", "<", "==", ">=", "<=", "\\+", "-", + "\\!=", "/", "\\*", "%", "and", "or", "not", ";","\\{","\\}",":"]; + var opRegexp = tokenRegexp(operators); + + var pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/; + + var word; + + function isEndLine(stream) { + return !stream.peek() || stream.match(/\s+$/, false); + } + + function urlTokens(stream, state) { + var ch = stream.peek(); + + if (ch === ")") { + stream.next(); + state.tokenizer = tokenBase; + return "operator"; + } else if (ch === "(") { + stream.next(); + stream.eatSpace(); + + return "operator"; + } else if (ch === "'" || ch === '"') { + state.tokenizer = buildStringTokenizer(stream.next()); + return "string"; + } else { + state.tokenizer = buildStringTokenizer(")", false); + return "string"; + } + } + function comment(indentation, multiLine) { + return function(stream, state) { + if (stream.sol() && stream.indentation() <= indentation) { + state.tokenizer = tokenBase; + return tokenBase(stream, state); + } + + if (multiLine && stream.skipTo("*/")) { + stream.next(); + stream.next(); + state.tokenizer = tokenBase; + } else { + stream.skipToEnd(); + } + + return "comment"; + }; + } + + function buildStringTokenizer(quote, greedy) { + if (greedy == null) { greedy = true; } + + function stringTokenizer(stream, state) { + var nextChar = stream.next(); + var peekChar = stream.peek(); + var previousChar = stream.string.charAt(stream.pos-2); + + var endingString = ((nextChar !== "\\" && peekChar === quote) || (nextChar === quote && previousChar !== "\\")); + + if (endingString) { + if (nextChar !== quote && greedy) { stream.next(); } + if (isEndLine(stream)) { + state.cursorHalf = 0; + } + state.tokenizer = tokenBase; + return "string"; + } else if (nextChar === "#" && peekChar === "{") { + state.tokenizer = buildInterpolationTokenizer(stringTokenizer); + stream.next(); + return "operator"; + } else { + return "string"; + } + } + + return stringTokenizer; + } + + function buildInterpolationTokenizer(currentTokenizer) { + return function(stream, state) { + if (stream.peek() === "}") { + stream.next(); + state.tokenizer = currentTokenizer; + return "operator"; + } else { + return tokenBase(stream, state); + } + }; + } + + function indent(state) { + if (state.indentCount == 0) { + state.indentCount++; + var lastScopeOffset = state.scopes[0].offset; + var currentOffset = lastScopeOffset + config.indentUnit; + state.scopes.unshift({ offset:currentOffset }); + } + } + + function dedent(state) { + if (state.scopes.length == 1) return; + + state.scopes.shift(); + } + + function tokenBase(stream, state) { + var ch = stream.peek(); + + // Comment + if (stream.match("/*")) { + state.tokenizer = comment(stream.indentation(), true); + return state.tokenizer(stream, state); + } + if (stream.match("//")) { + state.tokenizer = comment(stream.indentation(), false); + return state.tokenizer(stream, state); + } + + // Interpolation + if (stream.match("#{")) { + state.tokenizer = buildInterpolationTokenizer(tokenBase); + return "operator"; + } + + // Strings + if (ch === '"' || ch === "'") { + stream.next(); + state.tokenizer = buildStringTokenizer(ch); + return "string"; + } + + if(!state.cursorHalf){// state.cursorHalf === 0 + // first half i.e. before : for key-value pairs + // including selectors + + if (ch === "-") { + if (stream.match(/^-\w+-/)) { + return "meta"; + } + } + + if (ch === ".") { + stream.next(); + if (stream.match(/^[\w-]+/)) { + indent(state); + return "qualifier"; + } else if (stream.peek() === "#") { + indent(state); + return "tag"; + } + } + + if (ch === "#") { + stream.next(); + // ID selectors + if (stream.match(/^[\w-]+/)) { + indent(state); + return "builtin"; + } + if (stream.peek() === "#") { + indent(state); + return "tag"; + } + } + + // Variables + if (ch === "$") { + stream.next(); + stream.eatWhile(/[\w-]/); + return "variable-2"; + } + + // Numbers + if (stream.match(/^-?[0-9\.]+/)) + return "number"; + + // Units + if (stream.match(/^(px|em|in)\b/)) + return "unit"; + + if (stream.match(keywordsRegexp)) + return "keyword"; + + if (stream.match(/^url/) && stream.peek() === "(") { + state.tokenizer = urlTokens; + return "atom"; + } + + if (ch === "=") { + // Match shortcut mixin definition + if (stream.match(/^=[\w-]+/)) { + indent(state); + return "meta"; + } + } + + if (ch === "+") { + // Match shortcut mixin definition + if (stream.match(/^\+[\w-]+/)){ + return "variable-3"; + } + } + + if(ch === "@"){ + if(stream.match(/@extend/)){ + if(!stream.match(/\s*[\w]/)) + dedent(state); + } + } + + + // Indent Directives + if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) { + indent(state); + return "def"; + } + + // Other Directives + if (ch === "@") { + stream.next(); + stream.eatWhile(/[\w-]/); + return "def"; + } + + if (stream.eatWhile(/[\w-]/)){ + if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){ + word = stream.current().toLowerCase(); + var prop = state.prevProp + "-" + word; + if (propertyKeywords.hasOwnProperty(prop)) { + return "property"; + } else if (propertyKeywords.hasOwnProperty(word)) { + state.prevProp = word; + return "property"; + } else if (fontProperties.hasOwnProperty(word)) { + return "property"; + } + return "tag"; + } + else if(stream.match(/ *:/,false)){ + indent(state); + state.cursorHalf = 1; + state.prevProp = stream.current().toLowerCase(); + return "property"; + } + else if(stream.match(/ *,/,false)){ + return "tag"; + } + else{ + indent(state); + return "tag"; + } + } + + if(ch === ":"){ + if (stream.match(pseudoElementsRegexp)){ // could be a pseudo-element + return "variable-3"; + } + stream.next(); + state.cursorHalf=1; + return "operator"; + } + + } // cursorHalf===0 ends here + else{ + + if (ch === "#") { + stream.next(); + // Hex numbers + if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){ + if (isEndLine(stream)) { + state.cursorHalf = 0; + } + return "number"; + } + } + + // Numbers + if (stream.match(/^-?[0-9\.]+/)){ + if (isEndLine(stream)) { + state.cursorHalf = 0; + } + return "number"; + } + + // Units + if (stream.match(/^(px|em|in)\b/)){ + if (isEndLine(stream)) { + state.cursorHalf = 0; + } + return "unit"; + } + + if (stream.match(keywordsRegexp)){ + if (isEndLine(stream)) { + state.cursorHalf = 0; + } + return "keyword"; + } + + if (stream.match(/^url/) && stream.peek() === "(") { + state.tokenizer = urlTokens; + if (isEndLine(stream)) { + state.cursorHalf = 0; + } + return "atom"; + } + + // Variables + if (ch === "$") { + stream.next(); + stream.eatWhile(/[\w-]/); + if (isEndLine(stream)) { + state.cursorHalf = 0; + } + return "variable-2"; + } + + // bang character for !important, !default, etc. + if (ch === "!") { + stream.next(); + state.cursorHalf = 0; + return stream.match(/^[\w]+/) ? "keyword": "operator"; + } + + if (stream.match(opRegexp)){ + if (isEndLine(stream)) { + state.cursorHalf = 0; + } + return "operator"; + } + + // attributes + if (stream.eatWhile(/[\w-]/)) { + if (isEndLine(stream)) { + state.cursorHalf = 0; + } + word = stream.current().toLowerCase(); + if (valueKeywords.hasOwnProperty(word)) { + return "atom"; + } else if (colorKeywords.hasOwnProperty(word)) { + return "keyword"; + } else if (propertyKeywords.hasOwnProperty(word)) { + state.prevProp = stream.current().toLowerCase(); + return "property"; + } else { + return "tag"; + } + } + + //stream.eatSpace(); + if (isEndLine(stream)) { + state.cursorHalf = 0; + return null; + } + + } // else ends here + + if (stream.match(opRegexp)) + return "operator"; + + // If we haven't returned by now, we move 1 character + // and return an error + stream.next(); + return null; + } + + function tokenLexer(stream, state) { + if (stream.sol()) state.indentCount = 0; + var style = state.tokenizer(stream, state); + var current = stream.current(); + + if (current === "@return" || current === "}"){ + dedent(state); + } + + if (style !== null) { + var startOfToken = stream.pos - current.length; + + var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount); + + var newScopes = []; + + for (var i = 0; i < state.scopes.length; i++) { + var scope = state.scopes[i]; + + if (scope.offset <= withCurrentIndent) + newScopes.push(scope); + } + + state.scopes = newScopes; + } + + + return style; + } + + return { + startState: function() { + return { + tokenizer: tokenBase, + scopes: [{offset: 0, type: "sass"}], + indentCount: 0, + cursorHalf: 0, // cursor half tells us if cursor lies after (1) + // or before (0) colon (well... more or less) + definedVars: [], + definedMixins: [] + }; + }, + token: function(stream, state) { + var style = tokenLexer(stream, state); + + state.lastToken = { style: style, content: stream.current() }; + + return style; + }, + + indent: function(state) { + return state.scopes[0].offset; + } + }; +}, "css"); + +CodeMirror.defineMIME("text/x-sass", "sass"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/scheme/scheme.js b/docs/js/node_modules/codemirror/mode/scheme/scheme.js new file mode 100644 index 000000000..56e4e332e --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/scheme/scheme.js @@ -0,0 +1,265 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +/** + * Author: Koh Zi Han, based on implementation by Koh Zi Chun + */ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("scheme", function () { + var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", + ATOM = "atom", NUMBER = "number", BRACKET = "bracket"; + var INDENT_WORD_SKIP = 2; + + function makeKeywords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var keywords = makeKeywords("λ case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt #f floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string=? string>? string? substring symbol->string symbol? #t tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"); + var indentKeys = makeKeywords("define let letrec let* lambda"); + + function stateStack(indent, type, prev) { // represents a state stack object + this.indent = indent; + this.type = type; + this.prev = prev; + } + + function pushStack(state, indent, type) { + state.indentStack = new stateStack(indent, type, state.indentStack); + } + + function popStack(state) { + state.indentStack = state.indentStack.prev; + } + + var binaryMatcher = new RegExp(/^(?:[-+]i|[-+][01]+#*(?:\/[01]+#*)?i|[-+]?[01]+#*(?:\/[01]+#*)?@[-+]?[01]+#*(?:\/[01]+#*)?|[-+]?[01]+#*(?:\/[01]+#*)?[-+](?:[01]+#*(?:\/[01]+#*)?)?i|[-+]?[01]+#*(?:\/[01]+#*)?)(?=[()\s;"]|$)/i); + var octalMatcher = new RegExp(/^(?:[-+]i|[-+][0-7]+#*(?:\/[0-7]+#*)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?@[-+]?[0-7]+#*(?:\/[0-7]+#*)?|[-+]?[0-7]+#*(?:\/[0-7]+#*)?[-+](?:[0-7]+#*(?:\/[0-7]+#*)?)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?)(?=[()\s;"]|$)/i); + var hexMatcher = new RegExp(/^(?:[-+]i|[-+][\da-f]+#*(?:\/[\da-f]+#*)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?@[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?[-+](?:[\da-f]+#*(?:\/[\da-f]+#*)?)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?)(?=[()\s;"]|$)/i); + var decimalMatcher = new RegExp(/^(?:[-+]i|[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)i|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)@[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)?i|(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*))(?=[()\s;"]|$)/i); + + function isBinaryNumber (stream) { + return stream.match(binaryMatcher); + } + + function isOctalNumber (stream) { + return stream.match(octalMatcher); + } + + function isDecimalNumber (stream, backup) { + if (backup === true) { + stream.backUp(1); + } + return stream.match(decimalMatcher); + } + + function isHexNumber (stream) { + return stream.match(hexMatcher); + } + + return { + startState: function () { + return { + indentStack: null, + indentation: 0, + mode: false, + sExprComment: false, + sExprQuote: false + }; + }, + + token: function (stream, state) { + if (state.indentStack == null && stream.sol()) { + // update indentation, but only if indentStack is empty + state.indentation = stream.indentation(); + } + + // skip spaces + if (stream.eatSpace()) { + return null; + } + var returnType = null; + + switch(state.mode){ + case "string": // multi-line string parsing mode + var next, escaped = false; + while ((next = stream.next()) != null) { + if (next == "\"" && !escaped) { + + state.mode = false; + break; + } + escaped = !escaped && next == "\\"; + } + returnType = STRING; // continue on in scheme-string mode + break; + case "comment": // comment parsing mode + var next, maybeEnd = false; + while ((next = stream.next()) != null) { + if (next == "#" && maybeEnd) { + + state.mode = false; + break; + } + maybeEnd = (next == "|"); + } + returnType = COMMENT; + break; + case "s-expr-comment": // s-expr commenting mode + state.mode = false; + if(stream.peek() == "(" || stream.peek() == "["){ + // actually start scheme s-expr commenting mode + state.sExprComment = 0; + }else{ + // if not we just comment the entire of the next token + stream.eatWhile(/[^\s\(\)\[\]]/); // eat symbol atom + returnType = COMMENT; + break; + } + default: // default parsing mode + var ch = stream.next(); + + if (ch == "\"") { + state.mode = "string"; + returnType = STRING; + + } else if (ch == "'") { + if (stream.peek() == "(" || stream.peek() == "["){ + if (typeof state.sExprQuote != "number") { + state.sExprQuote = 0; + } // else already in a quoted expression + returnType = ATOM; + } else { + stream.eatWhile(/[\w_\-!$%&*+\.\/:<=>?@\^~]/); + returnType = ATOM; + } + } else if (ch == '#') { + if (stream.eat("|")) { // Multi-line comment + state.mode = "comment"; // toggle to comment mode + returnType = COMMENT; + } else if (stream.eat(/[tf]/i)) { // #t/#f (atom) + returnType = ATOM; + } else if (stream.eat(';')) { // S-Expr comment + state.mode = "s-expr-comment"; + returnType = COMMENT; + } else { + var numTest = null, hasExactness = false, hasRadix = true; + if (stream.eat(/[ei]/i)) { + hasExactness = true; + } else { + stream.backUp(1); // must be radix specifier + } + if (stream.match(/^#b/i)) { + numTest = isBinaryNumber; + } else if (stream.match(/^#o/i)) { + numTest = isOctalNumber; + } else if (stream.match(/^#x/i)) { + numTest = isHexNumber; + } else if (stream.match(/^#d/i)) { + numTest = isDecimalNumber; + } else if (stream.match(/^[-+0-9.]/, false)) { + hasRadix = false; + numTest = isDecimalNumber; + // re-consume the intial # if all matches failed + } else if (!hasExactness) { + stream.eat('#'); + } + if (numTest != null) { + if (hasRadix && !hasExactness) { + // consume optional exactness after radix + stream.match(/^#[ei]/i); + } + if (numTest(stream)) + returnType = NUMBER; + } + } + } else if (/^[-+0-9.]/.test(ch) && isDecimalNumber(stream, true)) { // match non-prefixed number, must be decimal + returnType = NUMBER; + } else if (ch == ";") { // comment + stream.skipToEnd(); // rest of the line is a comment + returnType = COMMENT; + } else if (ch == "(" || ch == "[") { + var keyWord = ''; var indentTemp = stream.column(), letter; + /** + Either + (indent-word .. + (non-indent-word .. + (;something else, bracket, etc. + */ + + while ((letter = stream.eat(/[^\s\(\[\;\)\]]/)) != null) { + keyWord += letter; + } + + if (keyWord.length > 0 && indentKeys.propertyIsEnumerable(keyWord)) { // indent-word + + pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); + } else { // non-indent word + // we continue eating the spaces + stream.eatSpace(); + if (stream.eol() || stream.peek() == ";") { + // nothing significant after + // we restart indentation 1 space after + pushStack(state, indentTemp + 1, ch); + } else { + pushStack(state, indentTemp + stream.current().length, ch); // else we match + } + } + stream.backUp(stream.current().length - 1); // undo all the eating + + if(typeof state.sExprComment == "number") state.sExprComment++; + if(typeof state.sExprQuote == "number") state.sExprQuote++; + + returnType = BRACKET; + } else if (ch == ")" || ch == "]") { + returnType = BRACKET; + if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) { + popStack(state); + + if(typeof state.sExprComment == "number"){ + if(--state.sExprComment == 0){ + returnType = COMMENT; // final closing bracket + state.sExprComment = false; // turn off s-expr commenting mode + } + } + if(typeof state.sExprQuote == "number"){ + if(--state.sExprQuote == 0){ + returnType = ATOM; // final closing bracket + state.sExprQuote = false; // turn off s-expr quote mode + } + } + } + } else { + stream.eatWhile(/[\w_\-!$%&*+\.\/:<=>?@\^~]/); + + if (keywords && keywords.propertyIsEnumerable(stream.current())) { + returnType = BUILTIN; + } else returnType = "variable"; + } + } + return (typeof state.sExprComment == "number") ? COMMENT : ((typeof state.sExprQuote == "number") ? ATOM : returnType); + }, + + indent: function (state) { + if (state.indentStack == null) return state.indentation; + return state.indentStack.indent; + }, + + closeBrackets: {pairs: "()[]{}\"\""}, + lineComment: ";;" + }; +}); + +CodeMirror.defineMIME("text/x-scheme", "scheme"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/shell/shell.js b/docs/js/node_modules/codemirror/mode/shell/shell.js new file mode 100644 index 000000000..5af12413b --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/shell/shell.js @@ -0,0 +1,152 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode('shell', function() { + + var words = {}; + function define(style, dict) { + for(var i = 0; i < dict.length; i++) { + words[dict[i]] = style; + } + }; + + var commonAtoms = ["true", "false"]; + var commonKeywords = ["if", "then", "do", "else", "elif", "while", "until", "for", "in", "esac", "fi", + "fin", "fil", "done", "exit", "set", "unset", "export", "function"]; + var commonCommands = ["ab", "awk", "bash", "beep", "cat", "cc", "cd", "chown", "chmod", "chroot", "clear", + "cp", "curl", "cut", "diff", "echo", "find", "gawk", "gcc", "get", "git", "grep", "hg", "kill", "killall", + "ln", "ls", "make", "mkdir", "openssl", "mv", "nc", "nl", "node", "npm", "ping", "ps", "restart", "rm", + "rmdir", "sed", "service", "sh", "shopt", "shred", "source", "sort", "sleep", "ssh", "start", "stop", + "su", "sudo", "svn", "tee", "telnet", "top", "touch", "vi", "vim", "wall", "wc", "wget", "who", "write", + "yes", "zsh"]; + + CodeMirror.registerHelper("hintWords", "shell", commonAtoms.concat(commonKeywords, commonCommands)); + + define('atom', commonAtoms); + define('keyword', commonKeywords); + define('builtin', commonCommands); + + function tokenBase(stream, state) { + if (stream.eatSpace()) return null; + + var sol = stream.sol(); + var ch = stream.next(); + + if (ch === '\\') { + stream.next(); + return null; + } + if (ch === '\'' || ch === '"' || ch === '`') { + state.tokens.unshift(tokenString(ch, ch === "`" ? "quote" : "string")); + return tokenize(stream, state); + } + if (ch === '#') { + if (sol && stream.eat('!')) { + stream.skipToEnd(); + return 'meta'; // 'comment'? + } + stream.skipToEnd(); + return 'comment'; + } + if (ch === '$') { + state.tokens.unshift(tokenDollar); + return tokenize(stream, state); + } + if (ch === '+' || ch === '=') { + return 'operator'; + } + if (ch === '-') { + stream.eat('-'); + stream.eatWhile(/\w/); + return 'attribute'; + } + if (/\d/.test(ch)) { + stream.eatWhile(/\d/); + if(stream.eol() || !/\w/.test(stream.peek())) { + return 'number'; + } + } + stream.eatWhile(/[\w-]/); + var cur = stream.current(); + if (stream.peek() === '=' && /\w+/.test(cur)) return 'def'; + return words.hasOwnProperty(cur) ? words[cur] : null; + } + + function tokenString(quote, style) { + var close = quote == "(" ? ")" : quote == "{" ? "}" : quote + return function(stream, state) { + var next, escaped = false; + while ((next = stream.next()) != null) { + if (next === close && !escaped) { + state.tokens.shift(); + break; + } else if (next === '$' && !escaped && quote !== "'" && stream.peek() != close) { + escaped = true; + stream.backUp(1); + state.tokens.unshift(tokenDollar); + break; + } else if (!escaped && quote !== close && next === quote) { + state.tokens.unshift(tokenString(quote, style)) + return tokenize(stream, state) + } else if (!escaped && /['"]/.test(next) && !/['"]/.test(quote)) { + state.tokens.unshift(tokenStringStart(next, "string")); + stream.backUp(1); + break; + } + escaped = !escaped && next === '\\'; + } + return style; + }; + }; + + function tokenStringStart(quote, style) { + return function(stream, state) { + state.tokens[0] = tokenString(quote, style) + stream.next() + return tokenize(stream, state) + } + } + + var tokenDollar = function(stream, state) { + if (state.tokens.length > 1) stream.eat('$'); + var ch = stream.next() + if (/['"({]/.test(ch)) { + state.tokens[0] = tokenString(ch, ch == "(" ? "quote" : ch == "{" ? "def" : "string"); + return tokenize(stream, state); + } + if (!/\d/.test(ch)) stream.eatWhile(/\w/); + state.tokens.shift(); + return 'def'; + }; + + function tokenize(stream, state) { + return (state.tokens[0] || tokenBase) (stream, state); + }; + + return { + startState: function() {return {tokens:[]};}, + token: function(stream, state) { + return tokenize(stream, state); + }, + closeBrackets: "()[]{}''\"\"``", + lineComment: '#', + fold: "brace" + }; +}); + +CodeMirror.defineMIME('text/x-sh', 'shell'); +// Apache uses a slightly different Media Type for Shell scripts +// http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types +CodeMirror.defineMIME('application/x-sh', 'shell'); + +}); diff --git a/docs/js/node_modules/codemirror/mode/sieve/sieve.js b/docs/js/node_modules/codemirror/mode/sieve/sieve.js new file mode 100644 index 000000000..f02a867e7 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/sieve/sieve.js @@ -0,0 +1,193 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("sieve", function(config) { + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var keywords = words("if elsif else stop require"); + var atoms = words("true false not"); + var indentUnit = config.indentUnit; + + function tokenBase(stream, state) { + + var ch = stream.next(); + if (ch == "/" && stream.eat("*")) { + state.tokenize = tokenCComment; + return tokenCComment(stream, state); + } + + if (ch === '#') { + stream.skipToEnd(); + return "comment"; + } + + if (ch == "\"") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + + if (ch == "(") { + state._indent.push("("); + // add virtual angel wings so that editor behaves... + // ...more sane incase of broken brackets + state._indent.push("{"); + return null; + } + + if (ch === "{") { + state._indent.push("{"); + return null; + } + + if (ch == ")") { + state._indent.pop(); + state._indent.pop(); + } + + if (ch === "}") { + state._indent.pop(); + return null; + } + + if (ch == ",") + return null; + + if (ch == ";") + return null; + + + if (/[{}\(\),;]/.test(ch)) + return null; + + // 1*DIGIT "K" / "M" / "G" + if (/\d/.test(ch)) { + stream.eatWhile(/[\d]/); + stream.eat(/[KkMmGg]/); + return "number"; + } + + // ":" (ALPHA / "_") *(ALPHA / DIGIT / "_") + if (ch == ":") { + stream.eatWhile(/[a-zA-Z_]/); + stream.eatWhile(/[a-zA-Z0-9_]/); + + return "operator"; + } + + stream.eatWhile(/\w/); + var cur = stream.current(); + + // "text:" *(SP / HTAB) (hash-comment / CRLF) + // *(multiline-literal / multiline-dotstart) + // "." CRLF + if ((cur == "text") && stream.eat(":")) + { + state.tokenize = tokenMultiLineString; + return "string"; + } + + if (keywords.propertyIsEnumerable(cur)) + return "keyword"; + + if (atoms.propertyIsEnumerable(cur)) + return "atom"; + + return null; + } + + function tokenMultiLineString(stream, state) + { + state._multiLineString = true; + // the first line is special it may contain a comment + if (!stream.sol()) { + stream.eatSpace(); + + if (stream.peek() == "#") { + stream.skipToEnd(); + return "comment"; + } + + stream.skipToEnd(); + return "string"; + } + + if ((stream.next() == ".") && (stream.eol())) + { + state._multiLineString = false; + state.tokenize = tokenBase; + } + + return "string"; + } + + function tokenCComment(stream, state) { + var maybeEnd = false, ch; + while ((ch = stream.next()) != null) { + if (maybeEnd && ch == "/") { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) + break; + escaped = !escaped && ch == "\\"; + } + if (!escaped) state.tokenize = tokenBase; + return "string"; + }; + } + + return { + startState: function(base) { + return {tokenize: tokenBase, + baseIndent: base || 0, + _indent: []}; + }, + + token: function(stream, state) { + if (stream.eatSpace()) + return null; + + return (state.tokenize || tokenBase)(stream, state); + }, + + indent: function(state, _textAfter) { + var length = state._indent.length; + if (_textAfter && (_textAfter[0] == "}")) + length--; + + if (length <0) + length = 0; + + return length * indentUnit; + }, + + electricChars: "}" + }; +}); + +CodeMirror.defineMIME("application/sieve", "sieve"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/slim/slim.js b/docs/js/node_modules/codemirror/mode/slim/slim.js new file mode 100644 index 000000000..b8ccb1381 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/slim/slim.js @@ -0,0 +1,575 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../ruby/ruby")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../ruby/ruby"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + + CodeMirror.defineMode("slim", function(config) { + var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"}); + var rubyMode = CodeMirror.getMode(config, "ruby"); + var modes = { html: htmlMode, ruby: rubyMode }; + var embedded = { + ruby: "ruby", + javascript: "javascript", + css: "text/css", + sass: "text/x-sass", + scss: "text/x-scss", + less: "text/x-less", + styl: "text/x-styl", // no highlighting so far + coffee: "coffeescript", + asciidoc: "text/x-asciidoc", + markdown: "text/x-markdown", + textile: "text/x-textile", // no highlighting so far + creole: "text/x-creole", // no highlighting so far + wiki: "text/x-wiki", // no highlighting so far + mediawiki: "text/x-mediawiki", // no highlighting so far + rdoc: "text/x-rdoc", // no highlighting so far + builder: "text/x-builder", // no highlighting so far + nokogiri: "text/x-nokogiri", // no highlighting so far + erb: "application/x-erb" + }; + var embeddedRegexp = function(map){ + var arr = []; + for(var key in map) arr.push(key); + return new RegExp("^("+arr.join('|')+"):"); + }(embedded); + + var styleMap = { + "commentLine": "comment", + "slimSwitch": "operator special", + "slimTag": "tag", + "slimId": "attribute def", + "slimClass": "attribute qualifier", + "slimAttribute": "attribute", + "slimSubmode": "keyword special", + "closeAttributeTag": null, + "slimDoctype": null, + "lineContinuation": null + }; + var closing = { + "{": "}", + "[": "]", + "(": ")" + }; + + var nameStartChar = "_a-zA-Z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD"; + var nameChar = nameStartChar + "\\-0-9\xB7\u0300-\u036F\u203F-\u2040"; + var nameRegexp = new RegExp("^[:"+nameStartChar+"](?::["+nameChar+"]|["+nameChar+"]*)"); + var attributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*(?=\\s*=)"); + var wrappedAttributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*"); + var classNameRegexp = /^\.-?[_a-zA-Z]+[\w\-]*/; + var classIdRegexp = /^#[_a-zA-Z]+[\w\-]*/; + + function backup(pos, tokenize, style) { + var restore = function(stream, state) { + state.tokenize = tokenize; + if (stream.pos < pos) { + stream.pos = pos; + return style; + } + return state.tokenize(stream, state); + }; + return function(stream, state) { + state.tokenize = restore; + return tokenize(stream, state); + }; + } + + function maybeBackup(stream, state, pat, offset, style) { + var cur = stream.current(); + var idx = cur.search(pat); + if (idx > -1) { + state.tokenize = backup(stream.pos, state.tokenize, style); + stream.backUp(cur.length - idx - offset); + } + return style; + } + + function continueLine(state, column) { + state.stack = { + parent: state.stack, + style: "continuation", + indented: column, + tokenize: state.line + }; + state.line = state.tokenize; + } + function finishContinue(state) { + if (state.line == state.tokenize) { + state.line = state.stack.tokenize; + state.stack = state.stack.parent; + } + } + + function lineContinuable(column, tokenize) { + return function(stream, state) { + finishContinue(state); + if (stream.match(/^\\$/)) { + continueLine(state, column); + return "lineContinuation"; + } + var style = tokenize(stream, state); + if (stream.eol() && stream.current().match(/(?:^|[^\\])(?:\\\\)*\\$/)) { + stream.backUp(1); + } + return style; + }; + } + function commaContinuable(column, tokenize) { + return function(stream, state) { + finishContinue(state); + var style = tokenize(stream, state); + if (stream.eol() && stream.current().match(/,$/)) { + continueLine(state, column); + } + return style; + }; + } + + function rubyInQuote(endQuote, tokenize) { + // TODO: add multi line support + return function(stream, state) { + var ch = stream.peek(); + if (ch == endQuote && state.rubyState.tokenize.length == 1) { + // step out of ruby context as it seems to complete processing all the braces + stream.next(); + state.tokenize = tokenize; + return "closeAttributeTag"; + } else { + return ruby(stream, state); + } + }; + } + function startRubySplat(tokenize) { + var rubyState; + var runSplat = function(stream, state) { + if (state.rubyState.tokenize.length == 1 && !state.rubyState.context.prev) { + stream.backUp(1); + if (stream.eatSpace()) { + state.rubyState = rubyState; + state.tokenize = tokenize; + return tokenize(stream, state); + } + stream.next(); + } + return ruby(stream, state); + }; + return function(stream, state) { + rubyState = state.rubyState; + state.rubyState = CodeMirror.startState(rubyMode); + state.tokenize = runSplat; + return ruby(stream, state); + }; + } + + function ruby(stream, state) { + return rubyMode.token(stream, state.rubyState); + } + + function htmlLine(stream, state) { + if (stream.match(/^\\$/)) { + return "lineContinuation"; + } + return html(stream, state); + } + function html(stream, state) { + if (stream.match(/^#\{/)) { + state.tokenize = rubyInQuote("}", state.tokenize); + return null; + } + return maybeBackup(stream, state, /[^\\]#\{/, 1, htmlMode.token(stream, state.htmlState)); + } + + function startHtmlLine(lastTokenize) { + return function(stream, state) { + var style = htmlLine(stream, state); + if (stream.eol()) state.tokenize = lastTokenize; + return style; + }; + } + + function startHtmlMode(stream, state, offset) { + state.stack = { + parent: state.stack, + style: "html", + indented: stream.column() + offset, // pipe + space + tokenize: state.line + }; + state.line = state.tokenize = html; + return null; + } + + function comment(stream, state) { + stream.skipToEnd(); + return state.stack.style; + } + + function commentMode(stream, state) { + state.stack = { + parent: state.stack, + style: "comment", + indented: state.indented + 1, + tokenize: state.line + }; + state.line = comment; + return comment(stream, state); + } + + function attributeWrapper(stream, state) { + if (stream.eat(state.stack.endQuote)) { + state.line = state.stack.line; + state.tokenize = state.stack.tokenize; + state.stack = state.stack.parent; + return null; + } + if (stream.match(wrappedAttributeNameRegexp)) { + state.tokenize = attributeWrapperAssign; + return "slimAttribute"; + } + stream.next(); + return null; + } + function attributeWrapperAssign(stream, state) { + if (stream.match(/^==?/)) { + state.tokenize = attributeWrapperValue; + return null; + } + return attributeWrapper(stream, state); + } + function attributeWrapperValue(stream, state) { + var ch = stream.peek(); + if (ch == '"' || ch == "\'") { + state.tokenize = readQuoted(ch, "string", true, false, attributeWrapper); + stream.next(); + return state.tokenize(stream, state); + } + if (ch == '[') { + return startRubySplat(attributeWrapper)(stream, state); + } + if (stream.match(/^(true|false|nil)\b/)) { + state.tokenize = attributeWrapper; + return "keyword"; + } + return startRubySplat(attributeWrapper)(stream, state); + } + + function startAttributeWrapperMode(state, endQuote, tokenize) { + state.stack = { + parent: state.stack, + style: "wrapper", + indented: state.indented + 1, + tokenize: tokenize, + line: state.line, + endQuote: endQuote + }; + state.line = state.tokenize = attributeWrapper; + return null; + } + + function sub(stream, state) { + if (stream.match(/^#\{/)) { + state.tokenize = rubyInQuote("}", state.tokenize); + return null; + } + var subStream = new CodeMirror.StringStream(stream.string.slice(state.stack.indented), stream.tabSize); + subStream.pos = stream.pos - state.stack.indented; + subStream.start = stream.start - state.stack.indented; + subStream.lastColumnPos = stream.lastColumnPos - state.stack.indented; + subStream.lastColumnValue = stream.lastColumnValue - state.stack.indented; + var style = state.subMode.token(subStream, state.subState); + stream.pos = subStream.pos + state.stack.indented; + return style; + } + function firstSub(stream, state) { + state.stack.indented = stream.column(); + state.line = state.tokenize = sub; + return state.tokenize(stream, state); + } + + function createMode(mode) { + var query = embedded[mode]; + var spec = CodeMirror.mimeModes[query]; + if (spec) { + return CodeMirror.getMode(config, spec); + } + var factory = CodeMirror.modes[query]; + if (factory) { + return factory(config, {name: query}); + } + return CodeMirror.getMode(config, "null"); + } + + function getMode(mode) { + if (!modes.hasOwnProperty(mode)) { + return modes[mode] = createMode(mode); + } + return modes[mode]; + } + + function startSubMode(mode, state) { + var subMode = getMode(mode); + var subState = CodeMirror.startState(subMode); + + state.subMode = subMode; + state.subState = subState; + + state.stack = { + parent: state.stack, + style: "sub", + indented: state.indented + 1, + tokenize: state.line + }; + state.line = state.tokenize = firstSub; + return "slimSubmode"; + } + + function doctypeLine(stream, _state) { + stream.skipToEnd(); + return "slimDoctype"; + } + + function startLine(stream, state) { + var ch = stream.peek(); + if (ch == '<') { + return (state.tokenize = startHtmlLine(state.tokenize))(stream, state); + } + if (stream.match(/^[|']/)) { + return startHtmlMode(stream, state, 1); + } + if (stream.match(/^\/(!|\[\w+])?/)) { + return commentMode(stream, state); + } + if (stream.match(/^(-|==?[<>]?)/)) { + state.tokenize = lineContinuable(stream.column(), commaContinuable(stream.column(), ruby)); + return "slimSwitch"; + } + if (stream.match(/^doctype\b/)) { + state.tokenize = doctypeLine; + return "keyword"; + } + + var m = stream.match(embeddedRegexp); + if (m) { + return startSubMode(m[1], state); + } + + return slimTag(stream, state); + } + + function slim(stream, state) { + if (state.startOfLine) { + return startLine(stream, state); + } + return slimTag(stream, state); + } + + function slimTag(stream, state) { + if (stream.eat('*')) { + state.tokenize = startRubySplat(slimTagExtras); + return null; + } + if (stream.match(nameRegexp)) { + state.tokenize = slimTagExtras; + return "slimTag"; + } + return slimClass(stream, state); + } + function slimTagExtras(stream, state) { + if (stream.match(/^(<>?|> state.indented && state.last != "slimSubmode") { + state.line = state.tokenize = state.stack.tokenize; + state.stack = state.stack.parent; + state.subMode = null; + state.subState = null; + } + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + state.startOfLine = false; + if (style) state.last = style; + return styleMap.hasOwnProperty(style) ? styleMap[style] : style; + }, + + blankLine: function(state) { + if (state.subMode && state.subMode.blankLine) { + return state.subMode.blankLine(state.subState); + } + }, + + innerMode: function(state) { + if (state.subMode) return {state: state.subState, mode: state.subMode}; + return {state: state, mode: mode}; + } + + //indent: function(state) { + // return state.indented; + //} + }; + return mode; + }, "htmlmixed", "ruby"); + + CodeMirror.defineMIME("text/x-slim", "slim"); + CodeMirror.defineMIME("application/x-slim", "slim"); +}); diff --git a/docs/js/node_modules/codemirror/mode/smalltalk/smalltalk.js b/docs/js/node_modules/codemirror/mode/smalltalk/smalltalk.js new file mode 100644 index 000000000..5039fe2d1 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/smalltalk/smalltalk.js @@ -0,0 +1,168 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode('smalltalk', function(config) { + + var specialChars = /[+\-\/\\*~<>=@%|&?!.,:;^]/; + var keywords = /true|false|nil|self|super|thisContext/; + + var Context = function(tokenizer, parent) { + this.next = tokenizer; + this.parent = parent; + }; + + var Token = function(name, context, eos) { + this.name = name; + this.context = context; + this.eos = eos; + }; + + var State = function() { + this.context = new Context(next, null); + this.expectVariable = true; + this.indentation = 0; + this.userIndentationDelta = 0; + }; + + State.prototype.userIndent = function(indentation) { + this.userIndentationDelta = indentation > 0 ? (indentation / config.indentUnit - this.indentation) : 0; + }; + + var next = function(stream, context, state) { + var token = new Token(null, context, false); + var aChar = stream.next(); + + if (aChar === '"') { + token = nextComment(stream, new Context(nextComment, context)); + + } else if (aChar === '\'') { + token = nextString(stream, new Context(nextString, context)); + + } else if (aChar === '#') { + if (stream.peek() === '\'') { + stream.next(); + token = nextSymbol(stream, new Context(nextSymbol, context)); + } else { + if (stream.eatWhile(/[^\s.{}\[\]()]/)) + token.name = 'string-2'; + else + token.name = 'meta'; + } + + } else if (aChar === '$') { + if (stream.next() === '<') { + stream.eatWhile(/[^\s>]/); + stream.next(); + } + token.name = 'string-2'; + + } else if (aChar === '|' && state.expectVariable) { + token.context = new Context(nextTemporaries, context); + + } else if (/[\[\]{}()]/.test(aChar)) { + token.name = 'bracket'; + token.eos = /[\[{(]/.test(aChar); + + if (aChar === '[') { + state.indentation++; + } else if (aChar === ']') { + state.indentation = Math.max(0, state.indentation - 1); + } + + } else if (specialChars.test(aChar)) { + stream.eatWhile(specialChars); + token.name = 'operator'; + token.eos = aChar !== ';'; // ; cascaded message expression + + } else if (/\d/.test(aChar)) { + stream.eatWhile(/[\w\d]/); + token.name = 'number'; + + } else if (/[\w_]/.test(aChar)) { + stream.eatWhile(/[\w\d_]/); + token.name = state.expectVariable ? (keywords.test(stream.current()) ? 'keyword' : 'variable') : null; + + } else { + token.eos = state.expectVariable; + } + + return token; + }; + + var nextComment = function(stream, context) { + stream.eatWhile(/[^"]/); + return new Token('comment', stream.eat('"') ? context.parent : context, true); + }; + + var nextString = function(stream, context) { + stream.eatWhile(/[^']/); + return new Token('string', stream.eat('\'') ? context.parent : context, false); + }; + + var nextSymbol = function(stream, context) { + stream.eatWhile(/[^']/); + return new Token('string-2', stream.eat('\'') ? context.parent : context, false); + }; + + var nextTemporaries = function(stream, context) { + var token = new Token(null, context, false); + var aChar = stream.next(); + + if (aChar === '|') { + token.context = context.parent; + token.eos = true; + + } else { + stream.eatWhile(/[^|]/); + token.name = 'variable'; + } + + return token; + }; + + return { + startState: function() { + return new State; + }, + + token: function(stream, state) { + state.userIndent(stream.indentation()); + + if (stream.eatSpace()) { + return null; + } + + var token = state.context.next(stream, state.context, state); + state.context = token.context; + state.expectVariable = token.eos; + + return token.name; + }, + + blankLine: function(state) { + state.userIndent(0); + }, + + indent: function(state, textAfter) { + var i = state.context.next === next && textAfter && textAfter.charAt(0) === ']' ? -1 : state.userIndentationDelta; + return (state.indentation + i) * config.indentUnit; + }, + + electricChars: ']' + }; + +}); + +CodeMirror.defineMIME('text/x-stsrc', {name: 'smalltalk'}); + +}); diff --git a/docs/js/node_modules/codemirror/mode/smarty/smarty.js b/docs/js/node_modules/codemirror/mode/smarty/smarty.js new file mode 100644 index 000000000..57852feb0 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/smarty/smarty.js @@ -0,0 +1,225 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +/** + * Smarty 2 and 3 mode. + */ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("smarty", function(config, parserConf) { + var rightDelimiter = parserConf.rightDelimiter || "}"; + var leftDelimiter = parserConf.leftDelimiter || "{"; + var version = parserConf.version || 2; + var baseMode = CodeMirror.getMode(config, parserConf.baseMode || "null"); + + var keyFunctions = ["debug", "extends", "function", "include", "literal"]; + var regs = { + operatorChars: /[+\-*&%=<>!?]/, + validIdentifier: /[a-zA-Z0-9_]/, + stringChar: /['"]/ + }; + + var last; + function cont(style, lastType) { + last = lastType; + return style; + } + + function chain(stream, state, parser) { + state.tokenize = parser; + return parser(stream, state); + } + + // Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode + function doesNotCount(stream, pos) { + if (pos == null) pos = stream.pos; + return version === 3 && leftDelimiter == "{" && + (pos == stream.string.length || /\s/.test(stream.string.charAt(pos))); + } + + function tokenTop(stream, state) { + var string = stream.string; + for (var scan = stream.pos;;) { + var nextMatch = string.indexOf(leftDelimiter, scan); + scan = nextMatch + leftDelimiter.length; + if (nextMatch == -1 || !doesNotCount(stream, nextMatch + leftDelimiter.length)) break; + } + if (nextMatch == stream.pos) { + stream.match(leftDelimiter); + if (stream.eat("*")) { + return chain(stream, state, tokenBlock("comment", "*" + rightDelimiter)); + } else { + state.depth++; + state.tokenize = tokenSmarty; + last = "startTag"; + return "tag"; + } + } + + if (nextMatch > -1) stream.string = string.slice(0, nextMatch); + var token = baseMode.token(stream, state.base); + if (nextMatch > -1) stream.string = string; + return token; + } + + // parsing Smarty content + function tokenSmarty(stream, state) { + if (stream.match(rightDelimiter, true)) { + if (version === 3) { + state.depth--; + if (state.depth <= 0) { + state.tokenize = tokenTop; + } + } else { + state.tokenize = tokenTop; + } + return cont("tag", null); + } + + if (stream.match(leftDelimiter, true)) { + state.depth++; + return cont("tag", "startTag"); + } + + var ch = stream.next(); + if (ch == "$") { + stream.eatWhile(regs.validIdentifier); + return cont("variable-2", "variable"); + } else if (ch == "|") { + return cont("operator", "pipe"); + } else if (ch == ".") { + return cont("operator", "property"); + } else if (regs.stringChar.test(ch)) { + state.tokenize = tokenAttribute(ch); + return cont("string", "string"); + } else if (regs.operatorChars.test(ch)) { + stream.eatWhile(regs.operatorChars); + return cont("operator", "operator"); + } else if (ch == "[" || ch == "]") { + return cont("bracket", "bracket"); + } else if (ch == "(" || ch == ")") { + return cont("bracket", "operator"); + } else if (/\d/.test(ch)) { + stream.eatWhile(/\d/); + return cont("number", "number"); + } else { + + if (state.last == "variable") { + if (ch == "@") { + stream.eatWhile(regs.validIdentifier); + return cont("property", "property"); + } else if (ch == "|") { + stream.eatWhile(regs.validIdentifier); + return cont("qualifier", "modifier"); + } + } else if (state.last == "pipe") { + stream.eatWhile(regs.validIdentifier); + return cont("qualifier", "modifier"); + } else if (state.last == "whitespace") { + stream.eatWhile(regs.validIdentifier); + return cont("attribute", "modifier"); + } if (state.last == "property") { + stream.eatWhile(regs.validIdentifier); + return cont("property", null); + } else if (/\s/.test(ch)) { + last = "whitespace"; + return null; + } + + var str = ""; + if (ch != "/") { + str += ch; + } + var c = null; + while (c = stream.eat(regs.validIdentifier)) { + str += c; + } + for (var i=0, j=keyFunctions.length; i]=?)/)) { + // Tokenize filter, binary, null propagator, and equality operators. + return "operator"; + } + if (match = stream.match(/^\$([\w]+)/)) { + return ref(state.variables, match[1]); + } + if (match = stream.match(/^\w+/)) { + return /^(?:as|and|or|not|in)$/.test(match[0]) ? "keyword" : null; + } + stream.next(); + return null; + + case "literal": + if (stream.match(/^(?=\{\/literal})/)) { + state.indent -= config.indentUnit; + state.soyState.pop(); + return this.token(stream, state); + } + return tokenUntil(stream, state, /\{\/literal}/); + } + + if (stream.match(/^\{literal}/)) { + state.indent += config.indentUnit; + state.soyState.push("literal"); + state.context = new Context(state.context, "literal", state.variables); + return "keyword"; + + // A tag-keyword must be followed by whitespace, comment or a closing tag. + } else if (match = stream.match(/^\{([/@\\]?\w+\??)(?=$|[\s}]|\/[/*])/)) { + var prevTag = state.tag; + state.tag = match[1]; + var endTag = state.tag[0] == "/"; + var indentingTag = !!tags[state.tag]; + var tagName = endTag ? state.tag.substring(1) : state.tag; + var tag = tags[tagName]; + if (state.tag != "/switch") + state.indent += ((endTag || tag && tag.reduceIndent) && prevTag != "switch" ? 1 : 2) * config.indentUnit; + + state.soyState.push("tag"); + var tagError = false; + if (tag) { + if (!endTag) { + if (tag.soyState) state.soyState.push(tag.soyState); + } + // If a new tag, open a new context. + if (!tag.noEndTag && (indentingTag || !endTag)) { + state.context = new Context(state.context, state.tag, tag.variableScope ? state.variables : null); + // Otherwise close the current context. + } else if (endTag) { + if (!state.context || state.context.tag != tagName) { + tagError = true; + } else if (state.context) { + if (state.context.kind) { + state.localStates.pop(); + var localState = last(state.localStates); + if (localState.mode.indent) { + state.indent -= localState.mode.indent(localState.state, "", ""); + } + } + popcontext(state); + } + } + } else if (endTag) { + // Assume all tags with a closing tag are defined in the config. + tagError = true; + } + return (tagError ? "error " : "") + "keyword"; + + // Not a tag-keyword; it's an implicit print tag. + } else if (stream.eat('{')) { + state.tag = "print"; + state.indent += 2 * config.indentUnit; + state.soyState.push("tag"); + return "keyword"; + } + + return tokenUntil(stream, state, /\{|\s+\/\/|\/\*/); + }, + + indent: function(state, textAfter, line) { + var indent = state.indent, top = last(state.soyState); + if (top == "comment") return CodeMirror.Pass; + + if (top == "literal") { + if (/^\{\/literal}/.test(textAfter)) indent -= config.indentUnit; + } else { + if (/^\s*\{\/(template|deltemplate)\b/.test(textAfter)) return 0; + if (/^\{(\/|(fallbackmsg|elseif|else|ifempty)\b)/.test(textAfter)) indent -= config.indentUnit; + if (state.tag != "switch" && /^\{(case|default)\b/.test(textAfter)) indent -= config.indentUnit; + if (/^\{\/switch\b/.test(textAfter)) indent -= config.indentUnit; + } + var localState = last(state.localStates); + if (indent && localState.mode.indent) { + indent += localState.mode.indent(localState.state, textAfter, line); + } + return indent; + }, + + innerMode: function(state) { + if (state.soyState.length && last(state.soyState) != "literal") return null; + else return last(state.localStates); + }, + + electricInput: /^\s*\{(\/|\/template|\/deltemplate|\/switch|fallbackmsg|elseif|else|case|default|ifempty|\/literal\})$/, + lineComment: "//", + blockCommentStart: "/*", + blockCommentEnd: "*/", + blockCommentContinue: " * ", + useInnerComments: false, + fold: "indent" + }; + }, "htmlmixed"); + + CodeMirror.registerHelper("wordChars", "soy", /[\w$]/); + + CodeMirror.registerHelper("hintWords", "soy", Object.keys(tags).concat( + ["css", "debugger"])); + + CodeMirror.defineMIME("text/x-soy", "soy"); +}); diff --git a/docs/js/node_modules/codemirror/mode/sparql/sparql.js b/docs/js/node_modules/codemirror/mode/sparql/sparql.js new file mode 100644 index 000000000..bb79abff7 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/sparql/sparql.js @@ -0,0 +1,180 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("sparql", function(config) { + var indentUnit = config.indentUnit; + var curPunc; + + function wordRegexp(words) { + return new RegExp("^(?:" + words.join("|") + ")$", "i"); + } + var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", + "iri", "uri", "bnode", "count", "sum", "min", "max", "avg", "sample", + "group_concat", "rand", "abs", "ceil", "floor", "round", "concat", "substr", "strlen", + "replace", "ucase", "lcase", "encode_for_uri", "contains", "strstarts", "strends", + "strbefore", "strafter", "year", "month", "day", "hours", "minutes", "seconds", + "timezone", "tz", "now", "uuid", "struuid", "md5", "sha1", "sha256", "sha384", + "sha512", "coalesce", "if", "strlang", "strdt", "isnumeric", "regex", "exists", + "isblank", "isliteral", "a", "bind"]); + var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", + "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", + "graph", "by", "asc", "desc", "as", "having", "undef", "values", "group", + "minus", "in", "not", "service", "silent", "using", "insert", "delete", "union", + "true", "false", "with", + "data", "copy", "to", "move", "add", "create", "drop", "clear", "load"]); + var operatorChars = /[*+\-<>=&|\^\/!\?]/; + + function tokenBase(stream, state) { + var ch = stream.next(); + curPunc = null; + if (ch == "$" || ch == "?") { + if(ch == "?" && stream.match(/\s/, false)){ + return "operator"; + } + stream.match(/^[A-Za-z0-9_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][A-Za-z0-9_\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]*/); + return "variable-2"; + } + else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { + stream.match(/^[^\s\u00a0>]*>?/); + return "atom"; + } + else if (ch == "\"" || ch == "'") { + state.tokenize = tokenLiteral(ch); + return state.tokenize(stream, state); + } + else if (/[{}\(\),\.;\[\]]/.test(ch)) { + curPunc = ch; + return "bracket"; + } + else if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } + else if (operatorChars.test(ch)) { + stream.eatWhile(operatorChars); + return "operator"; + } + else if (ch == ":") { + stream.eatWhile(/[\w\d\._\-]/); + return "atom"; + } + else if (ch == "@") { + stream.eatWhile(/[a-z\d\-]/i); + return "meta"; + } + else { + stream.eatWhile(/[_\w\d]/); + if (stream.eat(":")) { + stream.eatWhile(/[\w\d_\-]/); + return "atom"; + } + var word = stream.current(); + if (ops.test(word)) + return "builtin"; + else if (keywords.test(word)) + return "keyword"; + else + return "variable"; + } + } + + function tokenLiteral(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return "string"; + }; + } + + function pushContext(state, type, col) { + state.context = {prev: state.context, indent: state.indent, col: col, type: type}; + } + function popContext(state) { + state.indent = state.context.indent; + state.context = state.context.prev; + } + + return { + startState: function() { + return {tokenize: tokenBase, + context: null, + indent: 0, + col: 0}; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (state.context && state.context.align == null) state.context.align = false; + state.indent = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + + if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { + state.context.align = true; + } + + if (curPunc == "(") pushContext(state, ")", stream.column()); + else if (curPunc == "[") pushContext(state, "]", stream.column()); + else if (curPunc == "{") pushContext(state, "}", stream.column()); + else if (/[\]\}\)]/.test(curPunc)) { + while (state.context && state.context.type == "pattern") popContext(state); + if (state.context && curPunc == state.context.type) { + popContext(state); + if (curPunc == "}" && state.context && state.context.type == "pattern") + popContext(state); + } + } + else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); + else if (/atom|string|variable/.test(style) && state.context) { + if (/[\}\]]/.test(state.context.type)) + pushContext(state, "pattern", stream.column()); + else if (state.context.type == "pattern" && !state.context.align) { + state.context.align = true; + state.context.col = stream.column(); + } + } + + return style; + }, + + indent: function(state, textAfter) { + var firstChar = textAfter && textAfter.charAt(0); + var context = state.context; + if (/[\]\}]/.test(firstChar)) + while (context && context.type == "pattern") context = context.prev; + + var closing = context && firstChar == context.type; + if (!context) + return 0; + else if (context.type == "pattern") + return context.col; + else if (context.align) + return context.col + (closing ? 0 : 1); + else + return context.indent + (closing ? 0 : indentUnit); + }, + + lineComment: "#" + }; +}); + +CodeMirror.defineMIME("application/sparql-query", "sparql"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/spreadsheet/spreadsheet.js b/docs/js/node_modules/codemirror/mode/spreadsheet/spreadsheet.js new file mode 100644 index 000000000..d87f988d3 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/spreadsheet/spreadsheet.js @@ -0,0 +1,112 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("spreadsheet", function () { + return { + startState: function () { + return { + stringType: null, + stack: [] + }; + }, + token: function (stream, state) { + if (!stream) return; + + //check for state changes + if (state.stack.length === 0) { + //strings + if ((stream.peek() == '"') || (stream.peek() == "'")) { + state.stringType = stream.peek(); + stream.next(); // Skip quote + state.stack.unshift("string"); + } + } + + //return state + //stack has + switch (state.stack[0]) { + case "string": + while (state.stack[0] === "string" && !stream.eol()) { + if (stream.peek() === state.stringType) { + stream.next(); // Skip quote + state.stack.shift(); // Clear flag + } else if (stream.peek() === "\\") { + stream.next(); + stream.next(); + } else { + stream.match(/^.[^\\\"\']*/); + } + } + return "string"; + + case "characterClass": + while (state.stack[0] === "characterClass" && !stream.eol()) { + if (!(stream.match(/^[^\]\\]+/) || stream.match(/^\\./))) + state.stack.shift(); + } + return "operator"; + } + + var peek = stream.peek(); + + //no stack + switch (peek) { + case "[": + stream.next(); + state.stack.unshift("characterClass"); + return "bracket"; + case ":": + stream.next(); + return "operator"; + case "\\": + if (stream.match(/\\[a-z]+/)) return "string-2"; + else { + stream.next(); + return "atom"; + } + case ".": + case ",": + case ";": + case "*": + case "-": + case "+": + case "^": + case "<": + case "/": + case "=": + stream.next(); + return "atom"; + case "$": + stream.next(); + return "builtin"; + } + + if (stream.match(/\d+/)) { + if (stream.match(/^\w+/)) return "error"; + return "number"; + } else if (stream.match(/^[a-zA-Z_]\w*/)) { + if (stream.match(/(?=[\(.])/, false)) return "keyword"; + return "variable-2"; + } else if (["[", "]", "(", ")", "{", "}"].indexOf(peek) != -1) { + stream.next(); + return "bracket"; + } else if (!stream.eatSpace()) { + stream.next(); + } + return null; + } + }; + }); + + CodeMirror.defineMIME("text/x-spreadsheet", "spreadsheet"); +}); diff --git a/docs/js/node_modules/codemirror/mode/sql/sql.js b/docs/js/node_modules/codemirror/mode/sql/sql.js new file mode 100644 index 000000000..35902bbab --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/sql/sql.js @@ -0,0 +1,494 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("sql", function(config, parserConfig) { + var client = parserConfig.client || {}, + atoms = parserConfig.atoms || {"false": true, "true": true, "null": true}, + builtin = parserConfig.builtin || set(defaultBuiltin), + keywords = parserConfig.keywords || set(sqlKeywords), + operatorChars = parserConfig.operatorChars || /^[*+\-%<>!=&|~^\/]/, + support = parserConfig.support || {}, + hooks = parserConfig.hooks || {}, + dateSQL = parserConfig.dateSQL || {"date" : true, "time" : true, "timestamp" : true}, + backslashStringEscapes = parserConfig.backslashStringEscapes !== false, + brackets = parserConfig.brackets || /^[\{}\(\)\[\]]/, + punctuation = parserConfig.punctuation || /^[;.,:]/ + + function tokenBase(stream, state) { + var ch = stream.next(); + + // call hooks from the mime type + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + + if (support.hexNumber && + ((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/)) + || (ch == "x" || ch == "X") && stream.match(/^'[0-9a-fA-F]+'/))) { + // hex + // ref: http://dev.mysql.com/doc/refman/5.5/en/hexadecimal-literals.html + return "number"; + } else if (support.binaryNumber && + (((ch == "b" || ch == "B") && stream.match(/^'[01]+'/)) + || (ch == "0" && stream.match(/^b[01]+/)))) { + // bitstring + // ref: http://dev.mysql.com/doc/refman/5.5/en/bit-field-literals.html + return "number"; + } else if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) { + // numbers + // ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html + stream.match(/^[0-9]*(\.[0-9]+)?([eE][-+]?[0-9]+)?/); + support.decimallessFloat && stream.match(/^\.(?!\.)/); + return "number"; + } else if (ch == "?" && (stream.eatSpace() || stream.eol() || stream.eat(";"))) { + // placeholders + return "variable-3"; + } else if (ch == "'" || (ch == '"' && support.doubleQuote)) { + // strings + // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html + state.tokenize = tokenLiteral(ch); + return state.tokenize(stream, state); + } else if ((((support.nCharCast && (ch == "n" || ch == "N")) + || (support.charsetCast && ch == "_" && stream.match(/[a-z][a-z0-9]*/i))) + && (stream.peek() == "'" || stream.peek() == '"'))) { + // charset casting: _utf8'str', N'str', n'str' + // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html + return "keyword"; + } else if (support.commentSlashSlash && ch == "/" && stream.eat("/")) { + // 1-line comment + stream.skipToEnd(); + return "comment"; + } else if ((support.commentHash && ch == "#") + || (ch == "-" && stream.eat("-") && (!support.commentSpaceRequired || stream.eat(" ")))) { + // 1-line comments + // ref: https://kb.askmonty.org/en/comment-syntax/ + stream.skipToEnd(); + return "comment"; + } else if (ch == "/" && stream.eat("*")) { + // multi-line comments + // ref: https://kb.askmonty.org/en/comment-syntax/ + state.tokenize = tokenComment(1); + return state.tokenize(stream, state); + } else if (ch == ".") { + // .1 for 0.1 + if (support.zerolessFloat && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) + return "number"; + if (stream.match(/^\.+/)) + return null + // .table_name (ODBC) + // // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html + if (support.ODBCdotTable && stream.match(/^[\w\d_]+/)) + return "variable-2"; + } else if (operatorChars.test(ch)) { + // operators + stream.eatWhile(operatorChars); + return "operator"; + } else if (brackets.test(ch)) { + // brackets + return "bracket"; + } else if (punctuation.test(ch)) { + // punctuation + stream.eatWhile(punctuation); + return "punctuation"; + } else if (ch == '{' && + (stream.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/) || stream.match(/^( )*(d|D|t|T|ts|TS)( )*"[^"]*"( )*}/))) { + // dates (weird ODBC syntax) + // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html + return "number"; + } else { + stream.eatWhile(/^[_\w\d]/); + var word = stream.current().toLowerCase(); + // dates (standard SQL syntax) + // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html + if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+"[^"]*"/))) + return "number"; + if (atoms.hasOwnProperty(word)) return "atom"; + if (builtin.hasOwnProperty(word)) return "builtin"; + if (keywords.hasOwnProperty(word)) return "keyword"; + if (client.hasOwnProperty(word)) return "string-2"; + return null; + } + } + + // 'string', with char specified in quote escaped by '\' + function tokenLiteral(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = backslashStringEscapes && !escaped && ch == "\\"; + } + return "string"; + }; + } + function tokenComment(depth) { + return function(stream, state) { + var m = stream.match(/^.*?(\/\*|\*\/)/) + if (!m) stream.skipToEnd() + else if (m[1] == "/*") state.tokenize = tokenComment(depth + 1) + else if (depth > 1) state.tokenize = tokenComment(depth - 1) + else state.tokenize = tokenBase + return "comment" + } + } + + function pushContext(stream, state, type) { + state.context = { + prev: state.context, + indent: stream.indentation(), + col: stream.column(), + type: type + }; + } + + function popContext(state) { + state.indent = state.context.indent; + state.context = state.context.prev; + } + + return { + startState: function() { + return {tokenize: tokenBase, context: null}; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (state.context && state.context.align == null) + state.context.align = false; + } + if (state.tokenize == tokenBase && stream.eatSpace()) return null; + + var style = state.tokenize(stream, state); + if (style == "comment") return style; + + if (state.context && state.context.align == null) + state.context.align = true; + + var tok = stream.current(); + if (tok == "(") + pushContext(stream, state, ")"); + else if (tok == "[") + pushContext(stream, state, "]"); + else if (state.context && state.context.type == tok) + popContext(state); + return style; + }, + + indent: function(state, textAfter) { + var cx = state.context; + if (!cx) return CodeMirror.Pass; + var closing = textAfter.charAt(0) == cx.type; + if (cx.align) return cx.col + (closing ? 0 : 1); + else return cx.indent + (closing ? 0 : config.indentUnit); + }, + + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: support.commentSlashSlash ? "//" : support.commentHash ? "#" : "--", + closeBrackets: "()[]{}''\"\"``" + }; +}); + + // `identifier` + function hookIdentifier(stream) { + // MySQL/MariaDB identifiers + // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html + var ch; + while ((ch = stream.next()) != null) { + if (ch == "`" && !stream.eat("`")) return "variable-2"; + } + stream.backUp(stream.current().length - 1); + return stream.eatWhile(/\w/) ? "variable-2" : null; + } + + // "identifier" + function hookIdentifierDoublequote(stream) { + // Standard SQL /SQLite identifiers + // ref: http://web.archive.org/web/20160813185132/http://savage.net.au/SQL/sql-99.bnf.html#delimited%20identifier + // ref: http://sqlite.org/lang_keywords.html + var ch; + while ((ch = stream.next()) != null) { + if (ch == "\"" && !stream.eat("\"")) return "variable-2"; + } + stream.backUp(stream.current().length - 1); + return stream.eatWhile(/\w/) ? "variable-2" : null; + } + + // variable token + function hookVar(stream) { + // variables + // @@prefix.varName @varName + // varName can be quoted with ` or ' or " + // ref: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html + if (stream.eat("@")) { + stream.match(/^session\./); + stream.match(/^local\./); + stream.match(/^global\./); + } + + if (stream.eat("'")) { + stream.match(/^.*'/); + return "variable-2"; + } else if (stream.eat('"')) { + stream.match(/^.*"/); + return "variable-2"; + } else if (stream.eat("`")) { + stream.match(/^.*`/); + return "variable-2"; + } else if (stream.match(/^[0-9a-zA-Z$\.\_]+/)) { + return "variable-2"; + } + return null; + }; + + // short client keyword token + function hookClient(stream) { + // \N means NULL + // ref: http://dev.mysql.com/doc/refman/5.5/en/null-values.html + if (stream.eat("N")) { + return "atom"; + } + // \g, etc + // ref: http://dev.mysql.com/doc/refman/5.5/en/mysql-commands.html + return stream.match(/^[a-zA-Z.#!?]/) ? "variable-2" : null; + } + + // these keywords are used by all SQL dialects (however, a mode can still overwrite it) + var sqlKeywords = "alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit "; + + // turn a space-separated list into an array + function set(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var defaultBuiltin = "bool boolean bit blob enum long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision real date datetime year unsigned signed decimal numeric" + + // A generic SQL Mode. It's not a standard, it just try to support what is generally supported + CodeMirror.defineMIME("text/x-sql", { + name: "sql", + keywords: set(sqlKeywords + "begin"), + builtin: set(defaultBuiltin), + atoms: set("false true null unknown"), + dateSQL: set("date time timestamp"), + support: set("ODBCdotTable doubleQuote binaryNumber hexNumber") + }); + + CodeMirror.defineMIME("text/x-mssql", { + name: "sql", + client: set("$partition binary_checksum checksum connectionproperty context_info current_request_id error_line error_message error_number error_procedure error_severity error_state formatmessage get_filestream_transaction_context getansinull host_id host_name isnull isnumeric min_active_rowversion newid newsequentialid rowcount_big xact_state object_id"), + keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare exec go if use index holdlock nolock nowait paglock readcommitted readcommittedlock readpast readuncommitted repeatableread rowlock serializable snapshot tablock tablockx updlock with"), + builtin: set("bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table "), + atoms: set("is not null like and or in left right between inner outer join all any some cross unpivot pivot exists"), + operatorChars: /^[*+\-%<>!=^\&|\/]/, + brackets: /^[\{}\(\)]/, + punctuation: /^[;.,:/]/, + backslashStringEscapes: false, + dateSQL: set("date datetimeoffset datetime2 smalldatetime datetime time"), + hooks: { + "@": hookVar + } + }); + + CodeMirror.defineMIME("text/x-mysql", { + name: "sql", + client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), + keywords: set(sqlKeywords + "accessible action add after algorithm all analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general get global grant grants group group_concat handler hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"), + builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"), + atoms: set("false true null unknown"), + operatorChars: /^[*+\-%<>!=&|^]/, + dateSQL: set("date time timestamp"), + support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"), + hooks: { + "@": hookVar, + "`": hookIdentifier, + "\\": hookClient + } + }); + + CodeMirror.defineMIME("text/x-mariadb", { + name: "sql", + client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), + keywords: set(sqlKeywords + "accessible action add after algorithm all always analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general generated get global grant grants group groupby_concat handler hard hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password persistent phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show shutdown signal slave slow smallint snapshot soft soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views virtual warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"), + builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"), + atoms: set("false true null unknown"), + operatorChars: /^[*+\-%<>!=&|^]/, + dateSQL: set("date time timestamp"), + support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"), + hooks: { + "@": hookVar, + "`": hookIdentifier, + "\\": hookClient + } + }); + + // provided by the phpLiteAdmin project - phpliteadmin.org + CodeMirror.defineMIME("text/x-sqlite", { + name: "sql", + // commands of the official SQLite client, ref: https://www.sqlite.org/cli.html#dotcmd + client: set("auth backup bail binary changes check clone databases dbinfo dump echo eqp exit explain fullschema headers help import imposter indexes iotrace limit lint load log mode nullvalue once open output print prompt quit read restore save scanstats schema separator session shell show stats system tables testcase timeout timer trace vfsinfo vfslist vfsname width"), + // ref: http://sqlite.org/lang_keywords.html + keywords: set(sqlKeywords + "abort action add after all analyze attach autoincrement before begin cascade case cast check collate column commit conflict constraint cross current_date current_time current_timestamp database default deferrable deferred detach each else end escape except exclusive exists explain fail for foreign full glob if ignore immediate index indexed initially inner instead intersect isnull key left limit match natural no notnull null of offset outer plan pragma primary query raise recursive references regexp reindex release rename replace restrict right rollback row savepoint temp temporary then to transaction trigger unique using vacuum view virtual when with without"), + // SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types. + builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text clob bigint int int2 int8 integer float double char varchar date datetime year unsigned signed numeric real"), + // ref: http://sqlite.org/syntax/literal-value.html + atoms: set("null current_date current_time current_timestamp"), + // ref: http://sqlite.org/lang_expr.html#binaryops + operatorChars: /^[*+\-%<>!=&|/~]/, + // SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types. + dateSQL: set("date time timestamp datetime"), + support: set("decimallessFloat zerolessFloat"), + identifierQuote: "\"", //ref: http://sqlite.org/lang_keywords.html + hooks: { + // bind-parameters ref:http://sqlite.org/lang_expr.html#varparam + "@": hookVar, + ":": hookVar, + "?": hookVar, + "$": hookVar, + // The preferred way to escape Identifiers is using double quotes, ref: http://sqlite.org/lang_keywords.html + "\"": hookIdentifierDoublequote, + // there is also support for backtics, ref: http://sqlite.org/lang_keywords.html + "`": hookIdentifier + } + }); + + // the query language used by Apache Cassandra is called CQL, but this mime type + // is called Cassandra to avoid confusion with Contextual Query Language + CodeMirror.defineMIME("text/x-cassandra", { + name: "sql", + client: { }, + keywords: set("add all allow alter and any apply as asc authorize batch begin by clustering columnfamily compact consistency count create custom delete desc distinct drop each_quorum exists filtering from grant if in index insert into key keyspace keyspaces level limit local_one local_quorum modify nan norecursive nosuperuser not of on one order password permission permissions primary quorum rename revoke schema select set storage superuser table three to token truncate ttl two type unlogged update use user users using values where with writetime"), + builtin: set("ascii bigint blob boolean counter decimal double float frozen inet int list map static text timestamp timeuuid tuple uuid varchar varint"), + atoms: set("false true infinity NaN"), + operatorChars: /^[<>=]/, + dateSQL: { }, + support: set("commentSlashSlash decimallessFloat"), + hooks: { } + }); + + // this is based on Peter Raganitsch's 'plsql' mode + CodeMirror.defineMIME("text/x-plsql", { + name: "sql", + client: set("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"), + keywords: set("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elseif elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning returns reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"), + builtin: set("abs acos add_months ascii asin atan atan2 average bfile bfilename bigserial bit blob ceil character chartorowid chr clob concat convert cos cosh count dec decode deref dual dump dup_val_on_index empty error exp false float floor found glb greatest hextoraw initcap instr instrb int integer isopen last_day least length lengthb ln lower lpad ltrim lub make_ref max min mlslabel mod months_between natural naturaln nchar nclob new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null number numeric nvarchar2 nvl others power rawtohex real reftohex round rowcount rowidtochar rowtype rpad rtrim serial sign signtype sin sinh smallint soundex sqlcode sqlerrm sqrt stddev string substr substrb sum sysdate tan tanh to_char text to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid unlogged upper user userenv varchar varchar2 variance varying vsize xml"), + operatorChars: /^[*\/+\-%<>!=~]/, + dateSQL: set("date time timestamp"), + support: set("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber") + }); + + // Created to support specific hive keywords + CodeMirror.defineMIME("text/x-hive", { + name: "sql", + keywords: set("select alter $elem$ $key$ $value$ add after all analyze and archive as asc before between binary both bucket buckets by cascade case cast change cluster clustered clusterstatus collection column columns comment compute concatenate continue create cross cursor data database databases dbproperties deferred delete delimited desc describe directory disable distinct distribute drop else enable end escaped exclusive exists explain export extended external fetch fields fileformat first format formatted from full function functions grant group having hold_ddltime idxproperties if import in index indexes inpath inputdriver inputformat insert intersect into is items join keys lateral left like limit lines load local location lock locks mapjoin materialized minus msck no_drop nocompress not of offline on option or order out outer outputdriver outputformat overwrite partition partitioned partitions percent plus preserve procedure purge range rcfile read readonly reads rebuild recordreader recordwriter recover reduce regexp rename repair replace restrict revoke right rlike row schema schemas semi sequencefile serde serdeproperties set shared show show_database sort sorted ssl statistics stored streamtable table tables tablesample tblproperties temporary terminated textfile then tmp to touch transform trigger unarchive undo union uniquejoin unlock update use using utc utc_tmestamp view when where while with admin authorization char compact compactions conf cube current current_date current_timestamp day decimal defined dependency directories elem_type exchange file following for grouping hour ignore inner interval jar less logical macro minute month more none noscan over owner partialscan preceding pretty principals protection reload rewrite role roles rollup rows second server sets skewed transactions truncate unbounded unset uri user values window year"), + builtin: set("bool boolean long timestamp tinyint smallint bigint int float double date datetime unsigned string array struct map uniontype key_type utctimestamp value_type varchar"), + atoms: set("false true null unknown"), + operatorChars: /^[*+\-%<>!=]/, + dateSQL: set("date timestamp"), + support: set("ODBCdotTable doubleQuote binaryNumber hexNumber") + }); + + CodeMirror.defineMIME("text/x-pgsql", { + name: "sql", + client: set("source"), + // For PostgreSQL - https://www.postgresql.org/docs/11/sql-keywords-appendix.html + // For pl/pgsql lang - https://github.com/postgres/postgres/blob/REL_11_2/src/pl/plpgsql/src/pl_scanner.c + keywords: set(sqlKeywords + "a abort abs absent absolute access according action ada add admin after aggregate alias all allocate also alter always analyse analyze and any are array array_agg array_max_cardinality as asc asensitive assert assertion assignment asymmetric at atomic attach attribute attributes authorization avg backward base64 before begin begin_frame begin_partition bernoulli between bigint binary bit bit_length blob blocked bom boolean both breadth by c cache call called cardinality cascade cascaded case cast catalog catalog_name ceil ceiling chain char char_length character character_length character_set_catalog character_set_name character_set_schema characteristics characters check checkpoint class class_origin clob close cluster coalesce cobol collate collation collation_catalog collation_name collation_schema collect column column_name columns command_function command_function_code comment comments commit committed concurrently condition condition_number configuration conflict connect connection connection_name constant constraint constraint_catalog constraint_name constraint_schema constraints constructor contains content continue control conversion convert copy corr corresponding cost count covar_pop covar_samp create cross csv cube cume_dist current current_catalog current_date current_default_transform_group current_path current_role current_row current_schema current_time current_timestamp current_transform_group_for_type current_user cursor cursor_name cycle data database datalink datatype date datetime_interval_code datetime_interval_precision day db deallocate debug dec decimal declare default defaults deferrable deferred defined definer degree delete delimiter delimiters dense_rank depends depth deref derived desc describe descriptor detach detail deterministic diagnostics dictionary disable discard disconnect dispatch distinct dlnewcopy dlpreviouscopy dlurlcomplete dlurlcompleteonly dlurlcompletewrite dlurlpath dlurlpathonly dlurlpathwrite dlurlscheme dlurlserver dlvalue do document domain double drop dump dynamic dynamic_function dynamic_function_code each element else elseif elsif empty enable encoding encrypted end end_frame end_partition endexec enforced enum equals errcode error escape event every except exception exclude excluding exclusive exec execute exists exit exp explain expression extension external extract false family fetch file filter final first first_value flag float floor following for force foreach foreign fortran forward found frame_row free freeze from fs full function functions fusion g general generated get global go goto grant granted greatest group grouping groups handler having header hex hierarchy hint hold hour id identity if ignore ilike immediate immediately immutable implementation implicit import in include including increment indent index indexes indicator info inherit inherits initially inline inner inout input insensitive insert instance instantiable instead int integer integrity intersect intersection interval into invoker is isnull isolation join k key key_member key_type label lag language large last last_value lateral lead leading leakproof least left length level library like like_regex limit link listen ln load local localtime localtimestamp location locator lock locked log logged loop lower m map mapping match matched materialized max max_cardinality maxvalue member merge message message_length message_octet_length message_text method min minute minvalue mod mode modifies module month more move multiset mumps name names namespace national natural nchar nclob nesting new next nfc nfd nfkc nfkd nil no none normalize normalized not nothing notice notify notnull nowait nth_value ntile null nullable nullif nulls number numeric object occurrences_regex octet_length octets of off offset oids old on only open operator option options or order ordering ordinality others out outer output over overlaps overlay overriding owned owner p pad parallel parameter parameter_mode parameter_name parameter_ordinal_position parameter_specific_catalog parameter_specific_name parameter_specific_schema parser partial partition pascal passing passthrough password path percent percent_rank percentile_cont percentile_disc perform period permission pg_context pg_datatype_name pg_exception_context pg_exception_detail pg_exception_hint placing plans pli policy portion position position_regex power precedes preceding precision prepare prepared preserve primary print_strict_params prior privileges procedural procedure procedures program public publication query quote raise range rank read reads real reassign recheck recovery recursive ref references referencing refresh regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy regr_syy reindex relative release rename repeatable replace replica requiring reset respect restart restore restrict result result_oid return returned_cardinality returned_length returned_octet_length returned_sqlstate returning returns reverse revoke right role rollback rollup routine routine_catalog routine_name routine_schema routines row row_count row_number rows rowtype rule savepoint scale schema schema_name schemas scope scope_catalog scope_name scope_schema scroll search second section security select selective self sensitive sequence sequences serializable server server_name session session_user set setof sets share show similar simple size skip slice smallint snapshot some source space specific specific_name specifictype sql sqlcode sqlerror sqlexception sqlstate sqlwarning sqrt stable stacked standalone start state statement static statistics stddev_pop stddev_samp stdin stdout storage strict strip structure style subclass_origin submultiset subscription substring substring_regex succeeds sum symmetric sysid system system_time system_user t table table_name tables tablesample tablespace temp template temporary text then ties time timestamp timezone_hour timezone_minute to token top_level_count trailing transaction transaction_active transactions_committed transactions_rolled_back transform transforms translate translate_regex translation treat trigger trigger_catalog trigger_name trigger_schema trim trim_array true truncate trusted type types uescape unbounded uncommitted under unencrypted union unique unknown unlink unlisten unlogged unnamed unnest until untyped update upper uri usage use_column use_variable user user_defined_type_catalog user_defined_type_code user_defined_type_name user_defined_type_schema using vacuum valid validate validator value value_of values var_pop var_samp varbinary varchar variable_conflict variadic varying verbose version versioning view views volatile warning when whenever where while whitespace width_bucket window with within without work wrapper write xml xmlagg xmlattributes xmlbinary xmlcast xmlcomment xmlconcat xmldeclaration xmldocument xmlelement xmlexists xmlforest xmliterate xmlnamespaces xmlparse xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltext xmlvalidate year yes zone"), + // https://www.postgresql.org/docs/11/datatype.html + builtin: set("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float8 inet integer int int4 interval json jsonb line lseg macaddr macaddr8 money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"), + atoms: set("false true null unknown"), + operatorChars: /^[*\/+\-%<>!=&|^\/#@?~]/, + dateSQL: set("date time timestamp"), + support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast") + }); + + // Google's SQL-like query language, GQL + CodeMirror.defineMIME("text/x-gql", { + name: "sql", + keywords: set("ancestor and asc by contains desc descendant distinct from group has in is limit offset on order select superset where"), + atoms: set("false true"), + builtin: set("blob datetime first key __key__ string integer double boolean null"), + operatorChars: /^[*+\-%<>!=]/ + }); + + // Greenplum + CodeMirror.defineMIME("text/x-gpsql", { + name: "sql", + client: set("source"), + //https://github.com/greenplum-db/gpdb/blob/master/src/include/parser/kwlist.h + keywords: set("abort absolute access action active add admin after aggregate all also alter always analyse analyze and any array as asc assertion assignment asymmetric at authorization backward before begin between bigint binary bit boolean both by cache called cascade cascaded case cast chain char character characteristics check checkpoint class close cluster coalesce codegen collate column comment commit committed concurrency concurrently configuration connection constraint constraints contains content continue conversion copy cost cpu_rate_limit create createdb createexttable createrole createuser cross csv cube current current_catalog current_date current_role current_schema current_time current_timestamp current_user cursor cycle data database day deallocate dec decimal declare decode default defaults deferrable deferred definer delete delimiter delimiters deny desc dictionary disable discard distinct distributed do document domain double drop dxl each else enable encoding encrypted end enum errors escape every except exchange exclude excluding exclusive execute exists explain extension external extract false family fetch fields filespace fill filter first float following for force foreign format forward freeze from full function global grant granted greatest group group_id grouping handler hash having header hold host hour identity if ignore ilike immediate immutable implicit in including inclusive increment index indexes inherit inherits initially inline inner inout input insensitive insert instead int integer intersect interval into invoker is isnull isolation join key language large last leading least left level like limit list listen load local localtime localtimestamp location lock log login mapping master match maxvalue median merge minute minvalue missing mode modifies modify month move name names national natural nchar new newline next no nocreatedb nocreateexttable nocreaterole nocreateuser noinherit nologin none noovercommit nosuperuser not nothing notify notnull nowait null nullif nulls numeric object of off offset oids old on only operator option options or order ordered others out outer over overcommit overlaps overlay owned owner parser partial partition partitions passing password percent percentile_cont percentile_disc placing plans position preceding precision prepare prepared preserve primary prior privileges procedural procedure protocol queue quote randomly range read readable reads real reassign recheck recursive ref references reindex reject relative release rename repeatable replace replica reset resource restart restrict returning returns revoke right role rollback rollup rootpartition row rows rule savepoint scatter schema scroll search second security segment select sequence serializable session session_user set setof sets share show similar simple smallint some split sql stable standalone start statement statistics stdin stdout storage strict strip subpartition subpartitions substring superuser symmetric sysid system table tablespace temp template temporary text then threshold ties time timestamp to trailing transaction treat trigger trim true truncate trusted type unbounded uncommitted unencrypted union unique unknown unlisten until update user using vacuum valid validation validator value values varchar variadic varying verbose version view volatile web when where whitespace window with within without work writable write xml xmlattributes xmlconcat xmlelement xmlexists xmlforest xmlparse xmlpi xmlroot xmlserialize year yes zone"), + builtin: set("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float float8 inet integer int int4 interval json jsonb line lseg macaddr macaddr8 money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"), + atoms: set("false true null unknown"), + operatorChars: /^[*+\-%<>!=&|^\/#@?~]/, + dateSQL: set("date time timestamp"), + support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast") + }); + + // Spark SQL + CodeMirror.defineMIME("text/x-sparksql", { + name: "sql", + keywords: set("add after all alter analyze and anti archive array as asc at between bucket buckets by cache cascade case cast change clear cluster clustered codegen collection column columns comment commit compact compactions compute concatenate cost create cross cube current current_date current_timestamp database databases datata dbproperties defined delete delimited deny desc describe dfs directories distinct distribute drop else end escaped except exchange exists explain export extended external false fields fileformat first following for format formatted from full function functions global grant group grouping having if ignore import in index indexes inner inpath inputformat insert intersect interval into is items join keys last lateral lazy left like limit lines list load local location lock locks logical macro map minus msck natural no not null nulls of on optimize option options or order out outer outputformat over overwrite partition partitioned partitions percent preceding principals purge range recordreader recordwriter recover reduce refresh regexp rename repair replace reset restrict revoke right rlike role roles rollback rollup row rows schema schemas select semi separated serde serdeproperties set sets show skewed sort sorted start statistics stored stratify struct table tables tablesample tblproperties temp temporary terminated then to touch transaction transactions transform true truncate unarchive unbounded uncache union unlock unset use using values view when where window with"), + builtin: set("tinyint smallint int bigint boolean float double string binary timestamp decimal array map struct uniontype delimited serde sequencefile textfile rcfile inputformat outputformat"), + atoms: set("false true null"), + operatorChars: /^[*\/+\-%<>!=~&|^]/, + dateSQL: set("date time timestamp"), + support: set("ODBCdotTable doubleQuote zerolessFloat") + }); + + // Esper + CodeMirror.defineMIME("text/x-esper", { + name: "sql", + client: set("source"), + // http://www.espertech.com/esper/release-5.5.0/esper-reference/html/appendix_keywords.html + keywords: set("alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit after all and as at asc avedev avg between by case cast coalesce count create current_timestamp day days delete define desc distinct else end escape events every exists false first from full group having hour hours in inner insert instanceof into irstream is istream join last lastweekday left limit like max match_recognize matches median measures metadatasql min minute minutes msec millisecond milliseconds not null offset on or order outer output partition pattern prev prior regexp retain-union retain-intersection right rstream sec second seconds select set some snapshot sql stddev sum then true unidirectional until update variable weekday when where window"), + builtin: {}, + atoms: set("false true null"), + operatorChars: /^[*+\-%<>!=&|^\/#@?~]/, + dateSQL: set("time"), + support: set("decimallessFloat zerolessFloat binaryNumber hexNumber") + }); +}); + +/* + How Properties of Mime Types are used by SQL Mode + ================================================= + + keywords: + A list of keywords you want to be highlighted. + builtin: + A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword"). + operatorChars: + All characters that must be handled as operators. + client: + Commands parsed and executed by the client (not the server). + support: + A list of supported syntaxes which are not common, but are supported by more than 1 DBMS. + * ODBCdotTable: .tableName + * zerolessFloat: .1 + * doubleQuote + * nCharCast: N'string' + * charsetCast: _utf8'string' + * commentHash: use # char for comments + * commentSlashSlash: use // for comments + * commentSpaceRequired: require a space after -- for comments + atoms: + Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others: + UNKNOWN, INFINITY, UNDERFLOW, NaN... + dateSQL: + Used for date/time SQL standard syntax, because not all DBMS's support same temporal types. +*/ diff --git a/docs/js/node_modules/codemirror/mode/stex/stex.js b/docs/js/node_modules/codemirror/mode/stex/stex.js new file mode 100644 index 000000000..140e5a8af --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/stex/stex.js @@ -0,0 +1,264 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +/* + * Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de) + * Licence: MIT + */ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("stex", function(_config, parserConfig) { + "use strict"; + + function pushCommand(state, command) { + state.cmdState.push(command); + } + + function peekCommand(state) { + if (state.cmdState.length > 0) { + return state.cmdState[state.cmdState.length - 1]; + } else { + return null; + } + } + + function popCommand(state) { + var plug = state.cmdState.pop(); + if (plug) { + plug.closeBracket(); + } + } + + // returns the non-default plugin closest to the end of the list + function getMostPowerful(state) { + var context = state.cmdState; + for (var i = context.length - 1; i >= 0; i--) { + var plug = context[i]; + if (plug.name == "DEFAULT") { + continue; + } + return plug; + } + return { styleIdentifier: function() { return null; } }; + } + + function addPluginPattern(pluginName, cmdStyle, styles) { + return function () { + this.name = pluginName; + this.bracketNo = 0; + this.style = cmdStyle; + this.styles = styles; + this.argument = null; // \begin and \end have arguments that follow. These are stored in the plugin + + this.styleIdentifier = function() { + return this.styles[this.bracketNo - 1] || null; + }; + this.openBracket = function() { + this.bracketNo++; + return "bracket"; + }; + this.closeBracket = function() {}; + }; + } + + var plugins = {}; + + plugins["importmodule"] = addPluginPattern("importmodule", "tag", ["string", "builtin"]); + plugins["documentclass"] = addPluginPattern("documentclass", "tag", ["", "atom"]); + plugins["usepackage"] = addPluginPattern("usepackage", "tag", ["atom"]); + plugins["begin"] = addPluginPattern("begin", "tag", ["atom"]); + plugins["end"] = addPluginPattern("end", "tag", ["atom"]); + + plugins["label" ] = addPluginPattern("label" , "tag", ["atom"]); + plugins["ref" ] = addPluginPattern("ref" , "tag", ["atom"]); + plugins["eqref" ] = addPluginPattern("eqref" , "tag", ["atom"]); + plugins["cite" ] = addPluginPattern("cite" , "tag", ["atom"]); + plugins["bibitem" ] = addPluginPattern("bibitem" , "tag", ["atom"]); + plugins["Bibitem" ] = addPluginPattern("Bibitem" , "tag", ["atom"]); + plugins["RBibitem" ] = addPluginPattern("RBibitem" , "tag", ["atom"]); + + plugins["DEFAULT"] = function () { + this.name = "DEFAULT"; + this.style = "tag"; + + this.styleIdentifier = this.openBracket = this.closeBracket = function() {}; + }; + + function setState(state, f) { + state.f = f; + } + + // called when in a normal (no environment) context + function normal(source, state) { + var plug; + // Do we look like '\command' ? If so, attempt to apply the plugin 'command' + if (source.match(/^\\[a-zA-Z@]+/)) { + var cmdName = source.current().slice(1); + plug = plugins[cmdName] || plugins["DEFAULT"]; + plug = new plug(); + pushCommand(state, plug); + setState(state, beginParams); + return plug.style; + } + + // escape characters + if (source.match(/^\\[$&%#{}_]/)) { + return "tag"; + } + + // white space control characters + if (source.match(/^\\[,;!\/\\]/)) { + return "tag"; + } + + // find if we're starting various math modes + if (source.match("\\[")) { + setState(state, function(source, state){ return inMathMode(source, state, "\\]"); }); + return "keyword"; + } + if (source.match("\\(")) { + setState(state, function(source, state){ return inMathMode(source, state, "\\)"); }); + return "keyword"; + } + if (source.match("$$")) { + setState(state, function(source, state){ return inMathMode(source, state, "$$"); }); + return "keyword"; + } + if (source.match("$")) { + setState(state, function(source, state){ return inMathMode(source, state, "$"); }); + return "keyword"; + } + + var ch = source.next(); + if (ch == "%") { + source.skipToEnd(); + return "comment"; + } else if (ch == '}' || ch == ']') { + plug = peekCommand(state); + if (plug) { + plug.closeBracket(ch); + setState(state, beginParams); + } else { + return "error"; + } + return "bracket"; + } else if (ch == '{' || ch == '[') { + plug = plugins["DEFAULT"]; + plug = new plug(); + pushCommand(state, plug); + return "bracket"; + } else if (/\d/.test(ch)) { + source.eatWhile(/[\w.%]/); + return "atom"; + } else { + source.eatWhile(/[\w\-_]/); + plug = getMostPowerful(state); + if (plug.name == 'begin') { + plug.argument = source.current(); + } + return plug.styleIdentifier(); + } + } + + function inMathMode(source, state, endModeSeq) { + if (source.eatSpace()) { + return null; + } + if (endModeSeq && source.match(endModeSeq)) { + setState(state, normal); + return "keyword"; + } + if (source.match(/^\\[a-zA-Z@]+/)) { + return "tag"; + } + if (source.match(/^[a-zA-Z]+/)) { + return "variable-2"; + } + // escape characters + if (source.match(/^\\[$&%#{}_]/)) { + return "tag"; + } + // white space control characters + if (source.match(/^\\[,;!\/]/)) { + return "tag"; + } + // special math-mode characters + if (source.match(/^[\^_&]/)) { + return "tag"; + } + // non-special characters + if (source.match(/^[+\-<>|=,\/@!*:;'"`~#?]/)) { + return null; + } + if (source.match(/^(\d+\.\d*|\d*\.\d+|\d+)/)) { + return "number"; + } + var ch = source.next(); + if (ch == "{" || ch == "}" || ch == "[" || ch == "]" || ch == "(" || ch == ")") { + return "bracket"; + } + + if (ch == "%") { + source.skipToEnd(); + return "comment"; + } + return "error"; + } + + function beginParams(source, state) { + var ch = source.peek(), lastPlug; + if (ch == '{' || ch == '[') { + lastPlug = peekCommand(state); + lastPlug.openBracket(ch); + source.eat(ch); + setState(state, normal); + return "bracket"; + } + if (/[ \t\r]/.test(ch)) { + source.eat(ch); + return null; + } + setState(state, normal); + popCommand(state); + + return normal(source, state); + } + + return { + startState: function() { + var f = parserConfig.inMathMode ? function(source, state){ return inMathMode(source, state); } : normal; + return { + cmdState: [], + f: f + }; + }, + copyState: function(s) { + return { + cmdState: s.cmdState.slice(), + f: s.f + }; + }, + token: function(stream, state) { + return state.f(stream, state); + }, + blankLine: function(state) { + state.f = normal; + state.cmdState.length = 0; + }, + lineComment: "%" + }; + }); + + CodeMirror.defineMIME("text/x-stex", "stex"); + CodeMirror.defineMIME("text/x-latex", "stex"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/stylus/stylus.js b/docs/js/node_modules/codemirror/mode/stylus/stylus.js new file mode 100644 index 000000000..dbe241d61 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/stylus/stylus.js @@ -0,0 +1,771 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// Stylus mode created by Dmitry Kiselyov http://git.io/AaRB + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("stylus", function(config) { + var indentUnit = config.indentUnit, + indentUnitString = '', + tagKeywords = keySet(tagKeywords_), + tagVariablesRegexp = /^(a|b|i|s|col|em)$/i, + propertyKeywords = keySet(propertyKeywords_), + nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_), + valueKeywords = keySet(valueKeywords_), + colorKeywords = keySet(colorKeywords_), + documentTypes = keySet(documentTypes_), + documentTypesRegexp = wordRegexp(documentTypes_), + mediaFeatures = keySet(mediaFeatures_), + mediaTypes = keySet(mediaTypes_), + fontProperties = keySet(fontProperties_), + operatorsRegexp = /^\s*([.]{2,3}|&&|\|\||\*\*|[?!=:]?=|[-+*\/%<>]=?|\?:|\~)/, + wordOperatorKeywordsRegexp = wordRegexp(wordOperatorKeywords_), + blockKeywords = keySet(blockKeywords_), + vendorPrefixesRegexp = new RegExp(/^\-(moz|ms|o|webkit)-/i), + commonAtoms = keySet(commonAtoms_), + firstWordMatch = "", + states = {}, + ch, + style, + type, + override; + + while (indentUnitString.length < indentUnit) indentUnitString += ' '; + + /** + * Tokenizers + */ + function tokenBase(stream, state) { + firstWordMatch = stream.string.match(/(^[\w-]+\s*=\s*$)|(^\s*[\w-]+\s*=\s*[\w-])|(^\s*(\.|#|@|\$|\&|\[|\d|\+|::?|\{|\>|~|\/)?\s*[\w-]*([a-z0-9-]|\*|\/\*)(\(|,)?)/); + state.context.line.firstWord = firstWordMatch ? firstWordMatch[0].replace(/^\s*/, "") : ""; + state.context.line.indent = stream.indentation(); + ch = stream.peek(); + + // Line comment + if (stream.match("//")) { + stream.skipToEnd(); + return ["comment", "comment"]; + } + // Block comment + if (stream.match("/*")) { + state.tokenize = tokenCComment; + return tokenCComment(stream, state); + } + // String + if (ch == "\"" || ch == "'") { + stream.next(); + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + // Def + if (ch == "@") { + stream.next(); + stream.eatWhile(/[\w\\-]/); + return ["def", stream.current()]; + } + // ID selector or Hex color + if (ch == "#") { + stream.next(); + // Hex color + if (stream.match(/^[0-9a-f]{3}([0-9a-f]([0-9a-f]{2}){0,2})?\b/i)) { + return ["atom", "atom"]; + } + // ID selector + if (stream.match(/^[a-z][\w-]*/i)) { + return ["builtin", "hash"]; + } + } + // Vendor prefixes + if (stream.match(vendorPrefixesRegexp)) { + return ["meta", "vendor-prefixes"]; + } + // Numbers + if (stream.match(/^-?[0-9]?\.?[0-9]/)) { + stream.eatWhile(/[a-z%]/i); + return ["number", "unit"]; + } + // !important|optional + if (ch == "!") { + stream.next(); + return [stream.match(/^(important|optional)/i) ? "keyword": "operator", "important"]; + } + // Class + if (ch == "." && stream.match(/^\.[a-z][\w-]*/i)) { + return ["qualifier", "qualifier"]; + } + // url url-prefix domain regexp + if (stream.match(documentTypesRegexp)) { + if (stream.peek() == "(") state.tokenize = tokenParenthesized; + return ["property", "word"]; + } + // Mixins / Functions + if (stream.match(/^[a-z][\w-]*\(/i)) { + stream.backUp(1); + return ["keyword", "mixin"]; + } + // Block mixins + if (stream.match(/^(\+|-)[a-z][\w-]*\(/i)) { + stream.backUp(1); + return ["keyword", "block-mixin"]; + } + // Parent Reference BEM naming + if (stream.string.match(/^\s*&/) && stream.match(/^[-_]+[a-z][\w-]*/)) { + return ["qualifier", "qualifier"]; + } + // / Root Reference & Parent Reference + if (stream.match(/^(\/|&)(-|_|:|\.|#|[a-z])/)) { + stream.backUp(1); + return ["variable-3", "reference"]; + } + if (stream.match(/^&{1}\s*$/)) { + return ["variable-3", "reference"]; + } + // Word operator + if (stream.match(wordOperatorKeywordsRegexp)) { + return ["operator", "operator"]; + } + // Word + if (stream.match(/^\$?[-_]*[a-z0-9]+[\w-]*/i)) { + // Variable + if (stream.match(/^(\.|\[)[\w-\'\"\]]+/i, false)) { + if (!wordIsTag(stream.current())) { + stream.match(/\./); + return ["variable-2", "variable-name"]; + } + } + return ["variable-2", "word"]; + } + // Operators + if (stream.match(operatorsRegexp)) { + return ["operator", stream.current()]; + } + // Delimiters + if (/[:;,{}\[\]\(\)]/.test(ch)) { + stream.next(); + return [null, ch]; + } + // Non-detected items + stream.next(); + return [null, null]; + } + + /** + * Token comment + */ + function tokenCComment(stream, state) { + var maybeEnd = false, ch; + while ((ch = stream.next()) != null) { + if (maybeEnd && ch == "/") { + state.tokenize = null; + break; + } + maybeEnd = (ch == "*"); + } + return ["comment", "comment"]; + } + + /** + * Token string + */ + function tokenString(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + if (quote == ")") stream.backUp(1); + break; + } + escaped = !escaped && ch == "\\"; + } + if (ch == quote || !escaped && quote != ")") state.tokenize = null; + return ["string", "string"]; + }; + } + + /** + * Token parenthesized + */ + function tokenParenthesized(stream, state) { + stream.next(); // Must be "(" + if (!stream.match(/\s*[\"\')]/, false)) + state.tokenize = tokenString(")"); + else + state.tokenize = null; + return [null, "("]; + } + + /** + * Context management + */ + function Context(type, indent, prev, line) { + this.type = type; + this.indent = indent; + this.prev = prev; + this.line = line || {firstWord: "", indent: 0}; + } + + function pushContext(state, stream, type, indent) { + indent = indent >= 0 ? indent : indentUnit; + state.context = new Context(type, stream.indentation() + indent, state.context); + return type; + } + + function popContext(state, currentIndent) { + var contextIndent = state.context.indent - indentUnit; + currentIndent = currentIndent || false; + state.context = state.context.prev; + if (currentIndent) state.context.indent = contextIndent; + return state.context.type; + } + + function pass(type, stream, state) { + return states[state.context.type](type, stream, state); + } + + function popAndPass(type, stream, state, n) { + for (var i = n || 1; i > 0; i--) + state.context = state.context.prev; + return pass(type, stream, state); + } + + + /** + * Parser + */ + function wordIsTag(word) { + return word.toLowerCase() in tagKeywords; + } + + function wordIsProperty(word) { + word = word.toLowerCase(); + return word in propertyKeywords || word in fontProperties; + } + + function wordIsBlock(word) { + return word.toLowerCase() in blockKeywords; + } + + function wordIsVendorPrefix(word) { + return word.toLowerCase().match(vendorPrefixesRegexp); + } + + function wordAsValue(word) { + var wordLC = word.toLowerCase(); + var override = "variable-2"; + if (wordIsTag(word)) override = "tag"; + else if (wordIsBlock(word)) override = "block-keyword"; + else if (wordIsProperty(word)) override = "property"; + else if (wordLC in valueKeywords || wordLC in commonAtoms) override = "atom"; + else if (wordLC == "return" || wordLC in colorKeywords) override = "keyword"; + + // Font family + else if (word.match(/^[A-Z]/)) override = "string"; + return override; + } + + function typeIsBlock(type, stream) { + return ((endOfLine(stream) && (type == "{" || type == "]" || type == "hash" || type == "qualifier")) || type == "block-mixin"); + } + + function typeIsInterpolation(type, stream) { + return type == "{" && stream.match(/^\s*\$?[\w-]+/i, false); + } + + function typeIsPseudo(type, stream) { + return type == ":" && stream.match(/^[a-z-]+/, false); + } + + function startOfLine(stream) { + return stream.sol() || stream.string.match(new RegExp("^\\s*" + escapeRegExp(stream.current()))); + } + + function endOfLine(stream) { + return stream.eol() || stream.match(/^\s*$/, false); + } + + function firstWordOfLine(line) { + var re = /^\s*[-_]*[a-z0-9]+[\w-]*/i; + var result = typeof line == "string" ? line.match(re) : line.string.match(re); + return result ? result[0].replace(/^\s*/, "") : ""; + } + + + /** + * Block + */ + states.block = function(type, stream, state) { + if ((type == "comment" && startOfLine(stream)) || + (type == "," && endOfLine(stream)) || + type == "mixin") { + return pushContext(state, stream, "block", 0); + } + if (typeIsInterpolation(type, stream)) { + return pushContext(state, stream, "interpolation"); + } + if (endOfLine(stream) && type == "]") { + if (!/^\s*(\.|#|:|\[|\*|&)/.test(stream.string) && !wordIsTag(firstWordOfLine(stream))) { + return pushContext(state, stream, "block", 0); + } + } + if (typeIsBlock(type, stream)) { + return pushContext(state, stream, "block"); + } + if (type == "}" && endOfLine(stream)) { + return pushContext(state, stream, "block", 0); + } + if (type == "variable-name") { + if (stream.string.match(/^\s?\$[\w-\.\[\]\'\"]+$/) || wordIsBlock(firstWordOfLine(stream))) { + return pushContext(state, stream, "variableName"); + } + else { + return pushContext(state, stream, "variableName", 0); + } + } + if (type == "=") { + if (!endOfLine(stream) && !wordIsBlock(firstWordOfLine(stream))) { + return pushContext(state, stream, "block", 0); + } + return pushContext(state, stream, "block"); + } + if (type == "*") { + if (endOfLine(stream) || stream.match(/\s*(,|\.|#|\[|:|{)/,false)) { + override = "tag"; + return pushContext(state, stream, "block"); + } + } + if (typeIsPseudo(type, stream)) { + return pushContext(state, stream, "pseudo"); + } + if (/@(font-face|media|supports|(-moz-)?document)/.test(type)) { + return pushContext(state, stream, endOfLine(stream) ? "block" : "atBlock"); + } + if (/@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) { + return pushContext(state, stream, "keyframes"); + } + if (/@extends?/.test(type)) { + return pushContext(state, stream, "extend", 0); + } + if (type && type.charAt(0) == "@") { + + // Property Lookup + if (stream.indentation() > 0 && wordIsProperty(stream.current().slice(1))) { + override = "variable-2"; + return "block"; + } + if (/(@import|@require|@charset)/.test(type)) { + return pushContext(state, stream, "block", 0); + } + return pushContext(state, stream, "block"); + } + if (type == "reference" && endOfLine(stream)) { + return pushContext(state, stream, "block"); + } + if (type == "(") { + return pushContext(state, stream, "parens"); + } + + if (type == "vendor-prefixes") { + return pushContext(state, stream, "vendorPrefixes"); + } + if (type == "word") { + var word = stream.current(); + override = wordAsValue(word); + + if (override == "property") { + if (startOfLine(stream)) { + return pushContext(state, stream, "block", 0); + } else { + override = "atom"; + return "block"; + } + } + + if (override == "tag") { + + // tag is a css value + if (/embed|menu|pre|progress|sub|table/.test(word)) { + if (wordIsProperty(firstWordOfLine(stream))) { + override = "atom"; + return "block"; + } + } + + // tag is an attribute + if (stream.string.match(new RegExp("\\[\\s*" + word + "|" + word +"\\s*\\]"))) { + override = "atom"; + return "block"; + } + + // tag is a variable + if (tagVariablesRegexp.test(word)) { + if ((startOfLine(stream) && stream.string.match(/=/)) || + (!startOfLine(stream) && + !stream.string.match(/^(\s*\.|#|\&|\[|\/|>|\*)/) && + !wordIsTag(firstWordOfLine(stream)))) { + override = "variable-2"; + if (wordIsBlock(firstWordOfLine(stream))) return "block"; + return pushContext(state, stream, "block", 0); + } + } + + if (endOfLine(stream)) return pushContext(state, stream, "block"); + } + if (override == "block-keyword") { + override = "keyword"; + + // Postfix conditionals + if (stream.current(/(if|unless)/) && !startOfLine(stream)) { + return "block"; + } + return pushContext(state, stream, "block"); + } + if (word == "return") return pushContext(state, stream, "block", 0); + + // Placeholder selector + if (override == "variable-2" && stream.string.match(/^\s?\$[\w-\.\[\]\'\"]+$/)) { + return pushContext(state, stream, "block"); + } + } + return state.context.type; + }; + + + /** + * Parens + */ + states.parens = function(type, stream, state) { + if (type == "(") return pushContext(state, stream, "parens"); + if (type == ")") { + if (state.context.prev.type == "parens") { + return popContext(state); + } + if ((stream.string.match(/^[a-z][\w-]*\(/i) && endOfLine(stream)) || + wordIsBlock(firstWordOfLine(stream)) || + /(\.|#|:|\[|\*|&|>|~|\+|\/)/.test(firstWordOfLine(stream)) || + (!stream.string.match(/^-?[a-z][\w-\.\[\]\'\"]*\s*=/) && + wordIsTag(firstWordOfLine(stream)))) { + return pushContext(state, stream, "block"); + } + if (stream.string.match(/^[\$-]?[a-z][\w-\.\[\]\'\"]*\s*=/) || + stream.string.match(/^\s*(\(|\)|[0-9])/) || + stream.string.match(/^\s+[a-z][\w-]*\(/i) || + stream.string.match(/^\s+[\$-]?[a-z]/i)) { + return pushContext(state, stream, "block", 0); + } + if (endOfLine(stream)) return pushContext(state, stream, "block"); + else return pushContext(state, stream, "block", 0); + } + if (type && type.charAt(0) == "@" && wordIsProperty(stream.current().slice(1))) { + override = "variable-2"; + } + if (type == "word") { + var word = stream.current(); + override = wordAsValue(word); + if (override == "tag" && tagVariablesRegexp.test(word)) { + override = "variable-2"; + } + if (override == "property" || word == "to") override = "atom"; + } + if (type == "variable-name") { + return pushContext(state, stream, "variableName"); + } + if (typeIsPseudo(type, stream)) { + return pushContext(state, stream, "pseudo"); + } + return state.context.type; + }; + + + /** + * Vendor prefixes + */ + states.vendorPrefixes = function(type, stream, state) { + if (type == "word") { + override = "property"; + return pushContext(state, stream, "block", 0); + } + return popContext(state); + }; + + + /** + * Pseudo + */ + states.pseudo = function(type, stream, state) { + if (!wordIsProperty(firstWordOfLine(stream.string))) { + stream.match(/^[a-z-]+/); + override = "variable-3"; + if (endOfLine(stream)) return pushContext(state, stream, "block"); + return popContext(state); + } + return popAndPass(type, stream, state); + }; + + + /** + * atBlock + */ + states.atBlock = function(type, stream, state) { + if (type == "(") return pushContext(state, stream, "atBlock_parens"); + if (typeIsBlock(type, stream)) { + return pushContext(state, stream, "block"); + } + if (typeIsInterpolation(type, stream)) { + return pushContext(state, stream, "interpolation"); + } + if (type == "word") { + var word = stream.current().toLowerCase(); + if (/^(only|not|and|or)$/.test(word)) + override = "keyword"; + else if (documentTypes.hasOwnProperty(word)) + override = "tag"; + else if (mediaTypes.hasOwnProperty(word)) + override = "attribute"; + else if (mediaFeatures.hasOwnProperty(word)) + override = "property"; + else if (nonStandardPropertyKeywords.hasOwnProperty(word)) + override = "string-2"; + else override = wordAsValue(stream.current()); + if (override == "tag" && endOfLine(stream)) { + return pushContext(state, stream, "block"); + } + } + if (type == "operator" && /^(not|and|or)$/.test(stream.current())) { + override = "keyword"; + } + return state.context.type; + }; + + states.atBlock_parens = function(type, stream, state) { + if (type == "{" || type == "}") return state.context.type; + if (type == ")") { + if (endOfLine(stream)) return pushContext(state, stream, "block"); + else return pushContext(state, stream, "atBlock"); + } + if (type == "word") { + var word = stream.current().toLowerCase(); + override = wordAsValue(word); + if (/^(max|min)/.test(word)) override = "property"; + if (override == "tag") { + tagVariablesRegexp.test(word) ? override = "variable-2" : override = "atom"; + } + return state.context.type; + } + return states.atBlock(type, stream, state); + }; + + + /** + * Keyframes + */ + states.keyframes = function(type, stream, state) { + if (stream.indentation() == "0" && ((type == "}" && startOfLine(stream)) || type == "]" || type == "hash" + || type == "qualifier" || wordIsTag(stream.current()))) { + return popAndPass(type, stream, state); + } + if (type == "{") return pushContext(state, stream, "keyframes"); + if (type == "}") { + if (startOfLine(stream)) return popContext(state, true); + else return pushContext(state, stream, "keyframes"); + } + if (type == "unit" && /^[0-9]+\%$/.test(stream.current())) { + return pushContext(state, stream, "keyframes"); + } + if (type == "word") { + override = wordAsValue(stream.current()); + if (override == "block-keyword") { + override = "keyword"; + return pushContext(state, stream, "keyframes"); + } + } + if (/@(font-face|media|supports|(-moz-)?document)/.test(type)) { + return pushContext(state, stream, endOfLine(stream) ? "block" : "atBlock"); + } + if (type == "mixin") { + return pushContext(state, stream, "block", 0); + } + return state.context.type; + }; + + + /** + * Interpolation + */ + states.interpolation = function(type, stream, state) { + if (type == "{") popContext(state) && pushContext(state, stream, "block"); + if (type == "}") { + if (stream.string.match(/^\s*(\.|#|:|\[|\*|&|>|~|\+|\/)/i) || + (stream.string.match(/^\s*[a-z]/i) && wordIsTag(firstWordOfLine(stream)))) { + return pushContext(state, stream, "block"); + } + if (!stream.string.match(/^(\{|\s*\&)/) || + stream.match(/\s*[\w-]/,false)) { + return pushContext(state, stream, "block", 0); + } + return pushContext(state, stream, "block"); + } + if (type == "variable-name") { + return pushContext(state, stream, "variableName", 0); + } + if (type == "word") { + override = wordAsValue(stream.current()); + if (override == "tag") override = "atom"; + } + return state.context.type; + }; + + + /** + * Extend/s + */ + states.extend = function(type, stream, state) { + if (type == "[" || type == "=") return "extend"; + if (type == "]") return popContext(state); + if (type == "word") { + override = wordAsValue(stream.current()); + return "extend"; + } + return popContext(state); + }; + + + /** + * Variable name + */ + states.variableName = function(type, stream, state) { + if (type == "string" || type == "[" || type == "]" || stream.current().match(/^(\.|\$)/)) { + if (stream.current().match(/^\.[\w-]+/i)) override = "variable-2"; + return "variableName"; + } + return popAndPass(type, stream, state); + }; + + + return { + startState: function(base) { + return { + tokenize: null, + state: "block", + context: new Context("block", base || 0, null) + }; + }, + token: function(stream, state) { + if (!state.tokenize && stream.eatSpace()) return null; + style = (state.tokenize || tokenBase)(stream, state); + if (style && typeof style == "object") { + type = style[1]; + style = style[0]; + } + override = style; + state.state = states[state.state](type, stream, state); + return override; + }, + indent: function(state, textAfter, line) { + + var cx = state.context, + ch = textAfter && textAfter.charAt(0), + indent = cx.indent, + lineFirstWord = firstWordOfLine(textAfter), + lineIndent = line.match(/^\s*/)[0].replace(/\t/g, indentUnitString).length, + prevLineFirstWord = state.context.prev ? state.context.prev.line.firstWord : "", + prevLineIndent = state.context.prev ? state.context.prev.line.indent : lineIndent; + + if (cx.prev && + (ch == "}" && (cx.type == "block" || cx.type == "atBlock" || cx.type == "keyframes") || + ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") || + ch == "{" && (cx.type == "at"))) { + indent = cx.indent - indentUnit; + } else if (!(/(\})/.test(ch))) { + if (/@|\$|\d/.test(ch) || + /^\{/.test(textAfter) || +/^\s*\/(\/|\*)/.test(textAfter) || + /^\s*\/\*/.test(prevLineFirstWord) || + /^\s*[\w-\.\[\]\'\"]+\s*(\?|:|\+)?=/i.test(textAfter) || +/^(\+|-)?[a-z][\w-]*\(/i.test(textAfter) || +/^return/.test(textAfter) || + wordIsBlock(lineFirstWord)) { + indent = lineIndent; + } else if (/(\.|#|:|\[|\*|&|>|~|\+|\/)/.test(ch) || wordIsTag(lineFirstWord)) { + if (/\,\s*$/.test(prevLineFirstWord)) { + indent = prevLineIndent; + } else if (/^\s+/.test(line) && (/(\.|#|:|\[|\*|&|>|~|\+|\/)/.test(prevLineFirstWord) || wordIsTag(prevLineFirstWord))) { + indent = lineIndent <= prevLineIndent ? prevLineIndent : prevLineIndent + indentUnit; + } else { + indent = lineIndent; + } + } else if (!/,\s*$/.test(line) && (wordIsVendorPrefix(lineFirstWord) || wordIsProperty(lineFirstWord))) { + if (wordIsBlock(prevLineFirstWord)) { + indent = lineIndent <= prevLineIndent ? prevLineIndent : prevLineIndent + indentUnit; + } else if (/^\{/.test(prevLineFirstWord)) { + indent = lineIndent <= prevLineIndent ? lineIndent : prevLineIndent + indentUnit; + } else if (wordIsVendorPrefix(prevLineFirstWord) || wordIsProperty(prevLineFirstWord)) { + indent = lineIndent >= prevLineIndent ? prevLineIndent : lineIndent; + } else if (/^(\.|#|:|\[|\*|&|@|\+|\-|>|~|\/)/.test(prevLineFirstWord) || + /=\s*$/.test(prevLineFirstWord) || + wordIsTag(prevLineFirstWord) || + /^\$[\w-\.\[\]\'\"]/.test(prevLineFirstWord)) { + indent = prevLineIndent + indentUnit; + } else { + indent = lineIndent; + } + } + } + return indent; + }, + electricChars: "}", + lineComment: "//", + fold: "indent" + }; + }); + + // developer.mozilla.org/en-US/docs/Web/HTML/Element + var tagKeywords_ = ["a","abbr","address","area","article","aside","audio", "b", "base","bdi", "bdo","bgsound","blockquote","body","br","button","canvas","caption","cite", "code","col","colgroup","data","datalist","dd","del","details","dfn","div", "dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1", "h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe", "img","input","ins","kbd","keygen","label","legend","li","link","main","map", "mark","marquee","menu","menuitem","meta","meter","nav","nobr","noframes", "noscript","object","ol","optgroup","option","output","p","param","pre", "progress","q","rp","rt","ruby","s","samp","script","section","select", "small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","track", "u","ul","var","video"]; + + // github.com/codemirror/CodeMirror/blob/master/mode/css/css.js + var documentTypes_ = ["domain", "regexp", "url", "url-prefix"]; + var mediaTypes_ = ["all","aural","braille","handheld","print","projection","screen","tty","tv","embossed"]; + var mediaFeatures_ = ["width","min-width","max-width","height","min-height","max-height","device-width","min-device-width","max-device-width","device-height","min-device-height","max-device-height","aspect-ratio","min-aspect-ratio","max-aspect-ratio","device-aspect-ratio","min-device-aspect-ratio","max-device-aspect-ratio","color","min-color","max-color","color-index","min-color-index","max-color-index","monochrome","min-monochrome","max-monochrome","resolution","min-resolution","max-resolution","scan","grid"]; + var propertyKeywords_ = ["align-content","align-items","align-self","alignment-adjust","alignment-baseline","anchor-point","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","appearance","azimuth","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","baseline-shift","binding","bleed","bookmark-label","bookmark-level","bookmark-state","bookmark-target","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","color","color-profile","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","crop","cue","cue-after","cue-before","cursor","direction","display","dominant-baseline","drop-initial-after-adjust","drop-initial-after-align","drop-initial-before-adjust","drop-initial-before-align","drop-initial-size","drop-initial-value","elevation","empty-cells","fit","fit-position","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","float-offset","flow-from","flow-into","font","font-feature-settings","font-family","font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style","font-synthesis","font-variant","font-variant-alternates","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-weight","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-position","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","inline-box-align","justify-content","left","letter-spacing","line-break","line-height","line-stacking","line-stacking-ruby","line-stacking-shift","line-stacking-strategy","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marker-offset","marks","marquee-direction","marquee-loop","marquee-play-count","marquee-speed","marquee-style","max-height","max-width","min-height","min-width","move-to","nav-down","nav-index","nav-left","nav-right","nav-up","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-style","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page","page-break-after","page-break-before","page-break-inside","page-policy","pause","pause-after","pause-before","perspective","perspective-origin","pitch","pitch-range","play-during","position","presentation-level","punctuation-trim","quotes","region-break-after","region-break-before","region-break-inside","region-fragment","rendering-intent","resize","rest","rest-after","rest-before","richness","right","rotation","rotation-point","ruby-align","ruby-overhang","ruby-position","ruby-span","shape-image-threshold","shape-inside","shape-margin","shape-outside","size","speak","speak-as","speak-header","speak-numeral","speak-punctuation","speech-rate","stress","string-set","tab-size","table-layout","target","target-name","target-new","target-position","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-skip","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-height","text-indent","text-justify","text-outline","text-overflow","text-shadow","text-size-adjust","text-space-collapse","text-transform","text-underline-position","text-wrap","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","z-index","clip-path","clip-rule","mask","enable-background","filter","flood-color","flood-opacity","lighting-color","stop-color","stop-opacity","pointer-events","color-interpolation","color-interpolation-filters","color-rendering","fill","fill-opacity","fill-rule","image-rendering","marker","marker-end","marker-mid","marker-start","shape-rendering","stroke","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-rendering","baseline-shift","dominant-baseline","glyph-orientation-horizontal","glyph-orientation-vertical","text-anchor","writing-mode","font-smoothing","osx-font-smoothing"]; + var nonStandardPropertyKeywords_ = ["scrollbar-arrow-color","scrollbar-base-color","scrollbar-dark-shadow-color","scrollbar-face-color","scrollbar-highlight-color","scrollbar-shadow-color","scrollbar-3d-light-color","scrollbar-track-color","shape-inside","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","zoom"]; + var fontProperties_ = ["font-family","src","unicode-range","font-variant","font-feature-settings","font-stretch","font-weight","font-style"]; + var colorKeywords_ = ["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"]; + var valueKeywords_ = ["above","absolute","activeborder","additive","activecaption","afar","after-white-space","ahead","alias","all","all-scroll","alphabetic","alternate","always","amharic","amharic-abegede","antialiased","appworkspace","arabic-indic","armenian","asterisks","attr","auto","avoid","avoid-column","avoid-page","avoid-region","background","backwards","baseline","below","bidi-override","binary","bengali","blink","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","bullets","button","button-bevel","buttonface","buttonhighlight","buttonshadow","buttontext","calc","cambodian","capitalize","caps-lock-indicator","caption","captiontext","caret","cell","center","checkbox","circle","cjk-decimal","cjk-earthly-branch","cjk-heavenly-stem","cjk-ideographic","clear","clip","close-quote","col-resize","collapse","column","compact","condensed","contain","content","contents","content-box","context-menu","continuous","copy","counter","counters","cover","crop","cross","crosshair","currentcolor","cursive","cyclic","dashed","decimal","decimal-leading-zero","default","default-button","destination-atop","destination-in","destination-out","destination-over","devanagari","disc","discard","disclosure-closed","disclosure-open","document","dot-dash","dot-dot-dash","dotted","double","down","e-resize","ease","ease-in","ease-in-out","ease-out","element","ellipse","ellipsis","embed","end","ethiopic","ethiopic-abegede","ethiopic-abegede-am-et","ethiopic-abegede-gez","ethiopic-abegede-ti-er","ethiopic-abegede-ti-et","ethiopic-halehame-aa-er","ethiopic-halehame-aa-et","ethiopic-halehame-am-et","ethiopic-halehame-gez","ethiopic-halehame-om-et","ethiopic-halehame-sid-et","ethiopic-halehame-so-et","ethiopic-halehame-ti-er","ethiopic-halehame-ti-et","ethiopic-halehame-tig","ethiopic-numeric","ew-resize","expanded","extends","extra-condensed","extra-expanded","fantasy","fast","fill","fixed","flat","flex","footnotes","forwards","from","geometricPrecision","georgian","graytext","groove","gujarati","gurmukhi","hand","hangul","hangul-consonant","hebrew","help","hidden","hide","higher","highlight","highlighttext","hiragana","hiragana-iroha","horizontal","hsl","hsla","icon","ignore","inactiveborder","inactivecaption","inactivecaptiontext","infinite","infobackground","infotext","inherit","initial","inline","inline-axis","inline-block","inline-flex","inline-table","inset","inside","intrinsic","invert","italic","japanese-formal","japanese-informal","justify","kannada","katakana","katakana-iroha","keep-all","khmer","korean-hangul-formal","korean-hanja-formal","korean-hanja-informal","landscape","lao","large","larger","left","level","lighter","line-through","linear","linear-gradient","lines","list-item","listbox","listitem","local","logical","loud","lower","lower-alpha","lower-armenian","lower-greek","lower-hexadecimal","lower-latin","lower-norwegian","lower-roman","lowercase","ltr","malayalam","match","matrix","matrix3d","media-controls-background","media-current-time-display","media-fullscreen-button","media-mute-button","media-play-button","media-return-to-realtime-button","media-rewind-button","media-seek-back-button","media-seek-forward-button","media-slider","media-sliderthumb","media-time-remaining-display","media-volume-slider","media-volume-slider-container","media-volume-sliderthumb","medium","menu","menulist","menulist-button","menulist-text","menulist-textfield","menutext","message-box","middle","min-intrinsic","mix","mongolian","monospace","move","multiple","myanmar","n-resize","narrower","ne-resize","nesw-resize","no-close-quote","no-drop","no-open-quote","no-repeat","none","normal","not-allowed","nowrap","ns-resize","numbers","numeric","nw-resize","nwse-resize","oblique","octal","open-quote","optimizeLegibility","optimizeSpeed","oriya","oromo","outset","outside","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","persian","perspective","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radial-gradient","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","relative","repeat","repeating-linear-gradient","repeating-radial-gradient","repeat-x","repeat-y","reset","reverse","rgb","rgba","ridge","right","rotate","rotate3d","rotateX","rotateY","rotateZ","round","row-resize","rtl","run-in","running","s-resize","sans-serif","scale","scale3d","scaleX","scaleY","scaleZ","scroll","scrollbar","scroll-position","se-resize","searchfield","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","semi-condensed","semi-expanded","separate","serif","show","sidama","simp-chinese-formal","simp-chinese-informal","single","skew","skewX","skewY","skip-white-space","slide","slider-horizontal","slider-vertical","sliderthumb-horizontal","sliderthumb-vertical","slow","small","small-caps","small-caption","smaller","solid","somali","source-atop","source-in","source-out","source-over","space","spell-out","square","square-button","start","static","status-bar","stretch","stroke","sub","subpixel-antialiased","super","sw-resize","symbolic","symbols","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","tamil","telugu","text","text-bottom","text-top","textarea","textfield","thai","thick","thin","threeddarkshadow","threedface","threedhighlight","threedlightshadow","threedshadow","tibetan","tigre","tigrinya-er","tigrinya-er-abegede","tigrinya-et","tigrinya-et-abegede","to","top","trad-chinese-formal","trad-chinese-informal","translate","translate3d","translateX","translateY","translateZ","transparent","ultra-condensed","ultra-expanded","underline","up","upper-alpha","upper-armenian","upper-greek","upper-hexadecimal","upper-latin","upper-norwegian","upper-roman","uppercase","urdu","url","var","vertical","vertical-text","visible","visibleFill","visiblePainted","visibleStroke","visual","w-resize","wait","wave","wider","window","windowframe","windowtext","words","x-large","x-small","xor","xx-large","xx-small","bicubic","optimizespeed","grayscale","row","row-reverse","wrap","wrap-reverse","column-reverse","flex-start","flex-end","space-between","space-around", "unset"]; + + var wordOperatorKeywords_ = ["in","and","or","not","is not","is a","is","isnt","defined","if unless"], + blockKeywords_ = ["for","if","else","unless", "from", "to"], + commonAtoms_ = ["null","true","false","href","title","type","not-allowed","readonly","disabled"], + commonDef_ = ["@font-face", "@keyframes", "@media", "@viewport", "@page", "@host", "@supports", "@block", "@css"]; + + var hintWords = tagKeywords_.concat(documentTypes_,mediaTypes_,mediaFeatures_, + propertyKeywords_,nonStandardPropertyKeywords_, + colorKeywords_,valueKeywords_,fontProperties_, + wordOperatorKeywords_,blockKeywords_, + commonAtoms_,commonDef_); + + function wordRegexp(words) { + words = words.sort(function(a,b){return b > a;}); + return new RegExp("^((" + words.join(")|(") + "))\\b"); + } + + function keySet(array) { + var keys = {}; + for (var i = 0; i < array.length; ++i) keys[array[i]] = true; + return keys; + } + + function escapeRegExp(text) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + } + + CodeMirror.registerHelper("hintWords", "stylus", hintWords); + CodeMirror.defineMIME("text/x-styl", "stylus"); +}); diff --git a/docs/js/node_modules/codemirror/mode/swift/swift.js b/docs/js/node_modules/codemirror/mode/swift/swift.js new file mode 100644 index 000000000..55e31e270 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/swift/swift.js @@ -0,0 +1,223 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// Swift mode created by Michael Kaminsky https://github.com/mkaminsky11 + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") + mod(require("../../lib/codemirror")) + else if (typeof define == "function" && define.amd) + define(["../../lib/codemirror"], mod) + else + mod(CodeMirror) +})(function(CodeMirror) { + "use strict" + + function wordSet(words) { + var set = {} + for (var i = 0; i < words.length; i++) set[words[i]] = true + return set + } + + var keywords = wordSet(["_","var","let","class","enum","extension","import","protocol","struct","func","typealias","associatedtype", + "open","public","internal","fileprivate","private","deinit","init","new","override","self","subscript","super", + "convenience","dynamic","final","indirect","lazy","required","static","unowned","unowned(safe)","unowned(unsafe)","weak","as","is", + "break","case","continue","default","else","fallthrough","for","guard","if","in","repeat","switch","where","while", + "defer","return","inout","mutating","nonmutating","catch","do","rethrows","throw","throws","try","didSet","get","set","willSet", + "assignment","associativity","infix","left","none","operator","postfix","precedence","precedencegroup","prefix","right", + "Any","AnyObject","Type","dynamicType","Self","Protocol","__COLUMN__","__FILE__","__FUNCTION__","__LINE__"]) + var definingKeywords = wordSet(["var","let","class","enum","extension","import","protocol","struct","func","typealias","associatedtype","for"]) + var atoms = wordSet(["true","false","nil","self","super","_"]) + var types = wordSet(["Array","Bool","Character","Dictionary","Double","Float","Int","Int8","Int16","Int32","Int64","Never","Optional","Set","String", + "UInt8","UInt16","UInt32","UInt64","Void"]) + var operators = "+-/*%=|&<>~^?!" + var punc = ":;,.(){}[]" + var binary = /^\-?0b[01][01_]*/ + var octal = /^\-?0o[0-7][0-7_]*/ + var hexadecimal = /^\-?0x[\dA-Fa-f][\dA-Fa-f_]*(?:(?:\.[\dA-Fa-f][\dA-Fa-f_]*)?[Pp]\-?\d[\d_]*)?/ + var decimal = /^\-?\d[\d_]*(?:\.\d[\d_]*)?(?:[Ee]\-?\d[\d_]*)?/ + var identifier = /^\$\d+|(`?)[_A-Za-z][_A-Za-z$0-9]*\1/ + var property = /^\.(?:\$\d+|(`?)[_A-Za-z][_A-Za-z$0-9]*\1)/ + var instruction = /^\#[A-Za-z]+/ + var attribute = /^@(?:\$\d+|(`?)[_A-Za-z][_A-Za-z$0-9]*\1)/ + //var regexp = /^\/(?!\s)(?:\/\/)?(?:\\.|[^\/])+\// + + function tokenBase(stream, state, prev) { + if (stream.sol()) state.indented = stream.indentation() + if (stream.eatSpace()) return null + + var ch = stream.peek() + if (ch == "/") { + if (stream.match("//")) { + stream.skipToEnd() + return "comment" + } + if (stream.match("/*")) { + state.tokenize.push(tokenComment) + return tokenComment(stream, state) + } + } + if (stream.match(instruction)) return "builtin" + if (stream.match(attribute)) return "attribute" + if (stream.match(binary)) return "number" + if (stream.match(octal)) return "number" + if (stream.match(hexadecimal)) return "number" + if (stream.match(decimal)) return "number" + if (stream.match(property)) return "property" + if (operators.indexOf(ch) > -1) { + stream.next() + return "operator" + } + if (punc.indexOf(ch) > -1) { + stream.next() + stream.match("..") + return "punctuation" + } + var stringMatch + if (stringMatch = stream.match(/("""|"|')/)) { + var tokenize = tokenString.bind(null, stringMatch[0]) + state.tokenize.push(tokenize) + return tokenize(stream, state) + } + + if (stream.match(identifier)) { + var ident = stream.current() + if (types.hasOwnProperty(ident)) return "variable-2" + if (atoms.hasOwnProperty(ident)) return "atom" + if (keywords.hasOwnProperty(ident)) { + if (definingKeywords.hasOwnProperty(ident)) + state.prev = "define" + return "keyword" + } + if (prev == "define") return "def" + return "variable" + } + + stream.next() + return null + } + + function tokenUntilClosingParen() { + var depth = 0 + return function(stream, state, prev) { + var inner = tokenBase(stream, state, prev) + if (inner == "punctuation") { + if (stream.current() == "(") ++depth + else if (stream.current() == ")") { + if (depth == 0) { + stream.backUp(1) + state.tokenize.pop() + return state.tokenize[state.tokenize.length - 1](stream, state) + } + else --depth + } + } + return inner + } + } + + function tokenString(openQuote, stream, state) { + var singleLine = openQuote.length == 1 + var ch, escaped = false + while (ch = stream.peek()) { + if (escaped) { + stream.next() + if (ch == "(") { + state.tokenize.push(tokenUntilClosingParen()) + return "string" + } + escaped = false + } else if (stream.match(openQuote)) { + state.tokenize.pop() + return "string" + } else { + stream.next() + escaped = ch == "\\" + } + } + if (singleLine) { + state.tokenize.pop() + } + return "string" + } + + function tokenComment(stream, state) { + var ch + while (true) { + stream.match(/^[^/*]+/, true) + ch = stream.next() + if (!ch) break + if (ch === "/" && stream.eat("*")) { + state.tokenize.push(tokenComment) + } else if (ch === "*" && stream.eat("/")) { + state.tokenize.pop() + } + } + return "comment" + } + + function Context(prev, align, indented) { + this.prev = prev + this.align = align + this.indented = indented + } + + function pushContext(state, stream) { + var align = stream.match(/^\s*($|\/[\/\*])/, false) ? null : stream.column() + 1 + state.context = new Context(state.context, align, state.indented) + } + + function popContext(state) { + if (state.context) { + state.indented = state.context.indented + state.context = state.context.prev + } + } + + CodeMirror.defineMode("swift", function(config) { + return { + startState: function() { + return { + prev: null, + context: null, + indented: 0, + tokenize: [] + } + }, + + token: function(stream, state) { + var prev = state.prev + state.prev = null + var tokenize = state.tokenize[state.tokenize.length - 1] || tokenBase + var style = tokenize(stream, state, prev) + if (!style || style == "comment") state.prev = prev + else if (!state.prev) state.prev = style + + if (style == "punctuation") { + var bracket = /[\(\[\{]|([\]\)\}])/.exec(stream.current()) + if (bracket) (bracket[1] ? popContext : pushContext)(state, stream) + } + + return style + }, + + indent: function(state, textAfter) { + var cx = state.context + if (!cx) return 0 + var closing = /^[\]\}\)]/.test(textAfter) + if (cx.align != null) return cx.align - (closing ? 1 : 0) + return cx.indented + (closing ? 0 : config.indentUnit) + }, + + electricInput: /^\s*[\)\}\]]$/, + + lineComment: "//", + blockCommentStart: "/*", + blockCommentEnd: "*/", + fold: "brace", + closeBrackets: "()[]{}''\"\"``" + } + }) + + CodeMirror.defineMIME("text/x-swift","swift") +}); diff --git a/docs/js/node_modules/codemirror/mode/tcl/tcl.js b/docs/js/node_modules/codemirror/mode/tcl/tcl.js new file mode 100644 index 000000000..a7ec89c9e --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/tcl/tcl.js @@ -0,0 +1,139 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +//tcl mode by Ford_Lawnmower :: Based on Velocity mode by Steve O'Hara + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("tcl", function() { + function parseWords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + var keywords = parseWords("Tcl safe after append array auto_execok auto_import auto_load " + + "auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror " + + "binary break catch cd close concat continue dde eof encoding error " + + "eval exec exit expr fblocked fconfigure fcopy file fileevent filename " + + "filename flush for foreach format gets glob global history http if " + + "incr info interp join lappend lindex linsert list llength load lrange " + + "lreplace lsearch lset lsort memory msgcat namespace open package parray " + + "pid pkg::create pkg_mkIndex proc puts pwd re_syntax read regex regexp " + + "registry regsub rename resource return scan seek set socket source split " + + "string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord " + + "tcl_wordBreakAfter tcl_startOfPreviousWord tcl_wordBreakBefore tcltest " + + "tclvars tell time trace unknown unset update uplevel upvar variable " + + "vwait"); + var functions = parseWords("if elseif else and not or eq ne in ni for foreach while switch"); + var isOperatorChar = /[+\-*&%=<>!?^\/\|]/; + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + function tokenBase(stream, state) { + var beforeParams = state.beforeParams; + state.beforeParams = false; + var ch = stream.next(); + if ((ch == '"' || ch == "'") && state.inParams) { + return chain(stream, state, tokenString(ch)); + } else if (/[\[\]{}\(\),;\.]/.test(ch)) { + if (ch == "(" && beforeParams) state.inParams = true; + else if (ch == ")") state.inParams = false; + return null; + } else if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } else if (ch == "#") { + if (stream.eat("*")) + return chain(stream, state, tokenComment); + if (ch == "#" && stream.match(/ *\[ *\[/)) + return chain(stream, state, tokenUnparsed); + stream.skipToEnd(); + return "comment"; + } else if (ch == '"') { + stream.skipTo(/"/); + return "comment"; + } else if (ch == "$") { + stream.eatWhile(/[$_a-z0-9A-Z\.{:]/); + stream.eatWhile(/}/); + state.beforeParams = true; + return "builtin"; + } else if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "comment"; + } else { + stream.eatWhile(/[\w\$_{}\xa1-\uffff]/); + var word = stream.current().toLowerCase(); + if (keywords && keywords.propertyIsEnumerable(word)) + return "keyword"; + if (functions && functions.propertyIsEnumerable(word)) { + state.beforeParams = true; + return "keyword"; + } + return null; + } + } + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) { + end = true; + break; + } + escaped = !escaped && next == "\\"; + } + if (end) state.tokenize = tokenBase; + return "string"; + }; + } + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "#" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + function tokenUnparsed(stream, state) { + var maybeEnd = 0, ch; + while (ch = stream.next()) { + if (ch == "#" && maybeEnd == 2) { + state.tokenize = tokenBase; + break; + } + if (ch == "]") + maybeEnd++; + else if (ch != " ") + maybeEnd = 0; + } + return "meta"; + } + return { + startState: function() { + return { + tokenize: tokenBase, + beforeParams: false, + inParams: false + }; + }, + token: function(stream, state) { + if (stream.eatSpace()) return null; + return state.tokenize(stream, state); + } + }; +}); +CodeMirror.defineMIME("text/x-tcl", "tcl"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/textile/textile.js b/docs/js/node_modules/codemirror/mode/textile/textile.js new file mode 100644 index 000000000..b378fb61f --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/textile/textile.js @@ -0,0 +1,469 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") { // CommonJS + mod(require("../../lib/codemirror")); + } else if (typeof define == "function" && define.amd) { // AMD + define(["../../lib/codemirror"], mod); + } else { // Plain browser env + mod(CodeMirror); + } +})(function(CodeMirror) { + "use strict"; + + var TOKEN_STYLES = { + addition: "positive", + attributes: "attribute", + bold: "strong", + cite: "keyword", + code: "atom", + definitionList: "number", + deletion: "negative", + div: "punctuation", + em: "em", + footnote: "variable", + footCite: "qualifier", + header: "header", + html: "comment", + image: "string", + italic: "em", + link: "link", + linkDefinition: "link", + list1: "variable-2", + list2: "variable-3", + list3: "keyword", + notextile: "string-2", + pre: "operator", + p: "property", + quote: "bracket", + span: "quote", + specialChar: "tag", + strong: "strong", + sub: "builtin", + sup: "builtin", + table: "variable-3", + tableHeading: "operator" + }; + + function startNewLine(stream, state) { + state.mode = Modes.newLayout; + state.tableHeading = false; + + if (state.layoutType === "definitionList" && state.spanningLayout && + stream.match(RE("definitionListEnd"), false)) + state.spanningLayout = false; + } + + function handlePhraseModifier(stream, state, ch) { + if (ch === "_") { + if (stream.eat("_")) + return togglePhraseModifier(stream, state, "italic", /__/, 2); + else + return togglePhraseModifier(stream, state, "em", /_/, 1); + } + + if (ch === "*") { + if (stream.eat("*")) { + return togglePhraseModifier(stream, state, "bold", /\*\*/, 2); + } + return togglePhraseModifier(stream, state, "strong", /\*/, 1); + } + + if (ch === "[") { + if (stream.match(/\d+\]/)) state.footCite = true; + return tokenStyles(state); + } + + if (ch === "(") { + var spec = stream.match(/^(r|tm|c)\)/); + if (spec) + return tokenStylesWith(state, TOKEN_STYLES.specialChar); + } + + if (ch === "<" && stream.match(/(\w+)[^>]+>[^<]+<\/\1>/)) + return tokenStylesWith(state, TOKEN_STYLES.html); + + if (ch === "?" && stream.eat("?")) + return togglePhraseModifier(stream, state, "cite", /\?\?/, 2); + + if (ch === "=" && stream.eat("=")) + return togglePhraseModifier(stream, state, "notextile", /==/, 2); + + if (ch === "-" && !stream.eat("-")) + return togglePhraseModifier(stream, state, "deletion", /-/, 1); + + if (ch === "+") + return togglePhraseModifier(stream, state, "addition", /\+/, 1); + + if (ch === "~") + return togglePhraseModifier(stream, state, "sub", /~/, 1); + + if (ch === "^") + return togglePhraseModifier(stream, state, "sup", /\^/, 1); + + if (ch === "%") + return togglePhraseModifier(stream, state, "span", /%/, 1); + + if (ch === "@") + return togglePhraseModifier(stream, state, "code", /@/, 1); + + if (ch === "!") { + var type = togglePhraseModifier(stream, state, "image", /(?:\([^\)]+\))?!/, 1); + stream.match(/^:\S+/); // optional Url portion + return type; + } + return tokenStyles(state); + } + + function togglePhraseModifier(stream, state, phraseModifier, closeRE, openSize) { + var charBefore = stream.pos > openSize ? stream.string.charAt(stream.pos - openSize - 1) : null; + var charAfter = stream.peek(); + if (state[phraseModifier]) { + if ((!charAfter || /\W/.test(charAfter)) && charBefore && /\S/.test(charBefore)) { + var type = tokenStyles(state); + state[phraseModifier] = false; + return type; + } + } else if ((!charBefore || /\W/.test(charBefore)) && charAfter && /\S/.test(charAfter) && + stream.match(new RegExp("^.*\\S" + closeRE.source + "(?:\\W|$)"), false)) { + state[phraseModifier] = true; + state.mode = Modes.attributes; + } + return tokenStyles(state); + }; + + function tokenStyles(state) { + var disabled = textileDisabled(state); + if (disabled) return disabled; + + var styles = []; + if (state.layoutType) styles.push(TOKEN_STYLES[state.layoutType]); + + styles = styles.concat(activeStyles( + state, "addition", "bold", "cite", "code", "deletion", "em", "footCite", + "image", "italic", "link", "span", "strong", "sub", "sup", "table", "tableHeading")); + + if (state.layoutType === "header") + styles.push(TOKEN_STYLES.header + "-" + state.header); + + return styles.length ? styles.join(" ") : null; + } + + function textileDisabled(state) { + var type = state.layoutType; + + switch(type) { + case "notextile": + case "code": + case "pre": + return TOKEN_STYLES[type]; + default: + if (state.notextile) + return TOKEN_STYLES.notextile + (type ? (" " + TOKEN_STYLES[type]) : ""); + return null; + } + } + + function tokenStylesWith(state, extraStyles) { + var disabled = textileDisabled(state); + if (disabled) return disabled; + + var type = tokenStyles(state); + if (extraStyles) + return type ? (type + " " + extraStyles) : extraStyles; + else + return type; + } + + function activeStyles(state) { + var styles = []; + for (var i = 1; i < arguments.length; ++i) { + if (state[arguments[i]]) + styles.push(TOKEN_STYLES[arguments[i]]); + } + return styles; + } + + function blankLine(state) { + var spanningLayout = state.spanningLayout, type = state.layoutType; + + for (var key in state) if (state.hasOwnProperty(key)) + delete state[key]; + + state.mode = Modes.newLayout; + if (spanningLayout) { + state.layoutType = type; + state.spanningLayout = true; + } + } + + var REs = { + cache: {}, + single: { + bc: "bc", + bq: "bq", + definitionList: /- .*?:=+/, + definitionListEnd: /.*=:\s*$/, + div: "div", + drawTable: /\|.*\|/, + foot: /fn\d+/, + header: /h[1-6]/, + html: /\s*<(?:\/)?(\w+)(?:[^>]+)?>(?:[^<]+<\/\1>)?/, + link: /[^"]+":\S/, + linkDefinition: /\[[^\s\]]+\]\S+/, + list: /(?:#+|\*+)/, + notextile: "notextile", + para: "p", + pre: "pre", + table: "table", + tableCellAttributes: /[\/\\]\d+/, + tableHeading: /\|_\./, + tableText: /[^"_\*\[\(\?\+~\^%@|-]+/, + text: /[^!"_=\*\[\(<\?\+~\^%@-]+/ + }, + attributes: { + align: /(?:<>|<|>|=)/, + selector: /\([^\(][^\)]+\)/, + lang: /\[[^\[\]]+\]/, + pad: /(?:\(+|\)+){1,2}/, + css: /\{[^\}]+\}/ + }, + createRe: function(name) { + switch (name) { + case "drawTable": + return REs.makeRe("^", REs.single.drawTable, "$"); + case "html": + return REs.makeRe("^", REs.single.html, "(?:", REs.single.html, ")*", "$"); + case "linkDefinition": + return REs.makeRe("^", REs.single.linkDefinition, "$"); + case "listLayout": + return REs.makeRe("^", REs.single.list, RE("allAttributes"), "*\\s+"); + case "tableCellAttributes": + return REs.makeRe("^", REs.choiceRe(REs.single.tableCellAttributes, + RE("allAttributes")), "+\\."); + case "type": + return REs.makeRe("^", RE("allTypes")); + case "typeLayout": + return REs.makeRe("^", RE("allTypes"), RE("allAttributes"), + "*\\.\\.?", "(\\s+|$)"); + case "attributes": + return REs.makeRe("^", RE("allAttributes"), "+"); + + case "allTypes": + return REs.choiceRe(REs.single.div, REs.single.foot, + REs.single.header, REs.single.bc, REs.single.bq, + REs.single.notextile, REs.single.pre, REs.single.table, + REs.single.para); + + case "allAttributes": + return REs.choiceRe(REs.attributes.selector, REs.attributes.css, + REs.attributes.lang, REs.attributes.align, REs.attributes.pad); + + default: + return REs.makeRe("^", REs.single[name]); + } + }, + makeRe: function() { + var pattern = ""; + for (var i = 0; i < arguments.length; ++i) { + var arg = arguments[i]; + pattern += (typeof arg === "string") ? arg : arg.source; + } + return new RegExp(pattern); + }, + choiceRe: function() { + var parts = [arguments[0]]; + for (var i = 1; i < arguments.length; ++i) { + parts[i * 2 - 1] = "|"; + parts[i * 2] = arguments[i]; + } + + parts.unshift("(?:"); + parts.push(")"); + return REs.makeRe.apply(null, parts); + } + }; + + function RE(name) { + return (REs.cache[name] || (REs.cache[name] = REs.createRe(name))); + } + + var Modes = { + newLayout: function(stream, state) { + if (stream.match(RE("typeLayout"), false)) { + state.spanningLayout = false; + return (state.mode = Modes.blockType)(stream, state); + } + var newMode; + if (!textileDisabled(state)) { + if (stream.match(RE("listLayout"), false)) + newMode = Modes.list; + else if (stream.match(RE("drawTable"), false)) + newMode = Modes.table; + else if (stream.match(RE("linkDefinition"), false)) + newMode = Modes.linkDefinition; + else if (stream.match(RE("definitionList"))) + newMode = Modes.definitionList; + else if (stream.match(RE("html"), false)) + newMode = Modes.html; + } + return (state.mode = (newMode || Modes.text))(stream, state); + }, + + blockType: function(stream, state) { + var match, type; + state.layoutType = null; + + if (match = stream.match(RE("type"))) + type = match[0]; + else + return (state.mode = Modes.text)(stream, state); + + if (match = type.match(RE("header"))) { + state.layoutType = "header"; + state.header = parseInt(match[0][1]); + } else if (type.match(RE("bq"))) { + state.layoutType = "quote"; + } else if (type.match(RE("bc"))) { + state.layoutType = "code"; + } else if (type.match(RE("foot"))) { + state.layoutType = "footnote"; + } else if (type.match(RE("notextile"))) { + state.layoutType = "notextile"; + } else if (type.match(RE("pre"))) { + state.layoutType = "pre"; + } else if (type.match(RE("div"))) { + state.layoutType = "div"; + } else if (type.match(RE("table"))) { + state.layoutType = "table"; + } + + state.mode = Modes.attributes; + return tokenStyles(state); + }, + + text: function(stream, state) { + if (stream.match(RE("text"))) return tokenStyles(state); + + var ch = stream.next(); + if (ch === '"') + return (state.mode = Modes.link)(stream, state); + return handlePhraseModifier(stream, state, ch); + }, + + attributes: function(stream, state) { + state.mode = Modes.layoutLength; + + if (stream.match(RE("attributes"))) + return tokenStylesWith(state, TOKEN_STYLES.attributes); + else + return tokenStyles(state); + }, + + layoutLength: function(stream, state) { + if (stream.eat(".") && stream.eat(".")) + state.spanningLayout = true; + + state.mode = Modes.text; + return tokenStyles(state); + }, + + list: function(stream, state) { + var match = stream.match(RE("list")); + state.listDepth = match[0].length; + var listMod = (state.listDepth - 1) % 3; + if (!listMod) + state.layoutType = "list1"; + else if (listMod === 1) + state.layoutType = "list2"; + else + state.layoutType = "list3"; + + state.mode = Modes.attributes; + return tokenStyles(state); + }, + + link: function(stream, state) { + state.mode = Modes.text; + if (stream.match(RE("link"))) { + stream.match(/\S+/); + return tokenStylesWith(state, TOKEN_STYLES.link); + } + return tokenStyles(state); + }, + + linkDefinition: function(stream, state) { + stream.skipToEnd(); + return tokenStylesWith(state, TOKEN_STYLES.linkDefinition); + }, + + definitionList: function(stream, state) { + stream.match(RE("definitionList")); + + state.layoutType = "definitionList"; + + if (stream.match(/\s*$/)) + state.spanningLayout = true; + else + state.mode = Modes.attributes; + + return tokenStyles(state); + }, + + html: function(stream, state) { + stream.skipToEnd(); + return tokenStylesWith(state, TOKEN_STYLES.html); + }, + + table: function(stream, state) { + state.layoutType = "table"; + return (state.mode = Modes.tableCell)(stream, state); + }, + + tableCell: function(stream, state) { + if (stream.match(RE("tableHeading"))) + state.tableHeading = true; + else + stream.eat("|"); + + state.mode = Modes.tableCellAttributes; + return tokenStyles(state); + }, + + tableCellAttributes: function(stream, state) { + state.mode = Modes.tableText; + + if (stream.match(RE("tableCellAttributes"))) + return tokenStylesWith(state, TOKEN_STYLES.attributes); + else + return tokenStyles(state); + }, + + tableText: function(stream, state) { + if (stream.match(RE("tableText"))) + return tokenStyles(state); + + if (stream.peek() === "|") { // end of cell + state.mode = Modes.tableCell; + return tokenStyles(state); + } + return handlePhraseModifier(stream, state, stream.next()); + } + }; + + CodeMirror.defineMode("textile", function() { + return { + startState: function() { + return { mode: Modes.newLayout }; + }, + token: function(stream, state) { + if (stream.sol()) startNewLine(stream, state); + return state.mode(stream, state); + }, + blankLine: blankLine + }; + }); + + CodeMirror.defineMIME("text/x-textile", "textile"); +}); diff --git a/docs/js/node_modules/codemirror/mode/tiddlywiki/tiddlywiki.css b/docs/js/node_modules/codemirror/mode/tiddlywiki/tiddlywiki.css new file mode 100644 index 000000000..9a69b639f --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/tiddlywiki/tiddlywiki.css @@ -0,0 +1,14 @@ +span.cm-underlined { + text-decoration: underline; +} +span.cm-strikethrough { + text-decoration: line-through; +} +span.cm-brace { + color: #170; + font-weight: bold; +} +span.cm-table { + color: blue; + font-weight: bold; +} diff --git a/docs/js/node_modules/codemirror/mode/tiddlywiki/tiddlywiki.js b/docs/js/node_modules/codemirror/mode/tiddlywiki/tiddlywiki.js new file mode 100644 index 000000000..a4fb89f65 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/tiddlywiki/tiddlywiki.js @@ -0,0 +1,308 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +/*** + |''Name''|tiddlywiki.js| + |''Description''|Enables TiddlyWikiy syntax highlighting using CodeMirror| + |''Author''|PMario| + |''Version''|0.1.7| + |''Status''|''stable''| + |''Source''|[[GitHub|https://github.com/pmario/CodeMirror2/blob/tw-syntax/mode/tiddlywiki]]| + |''Documentation''|https://codemirror.tiddlyspace.com/| + |''License''|[[MIT License|http://www.opensource.org/licenses/mit-license.php]]| + |''CoreVersion''|2.5.0| + |''Requires''|codemirror.js| + |''Keywords''|syntax highlighting color code mirror codemirror| + ! Info + CoreVersion parameter is needed for TiddlyWiki only! +***/ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("tiddlywiki", function () { + // Tokenizer + var textwords = {}; + + var keywords = { + "allTags": true, "closeAll": true, "list": true, + "newJournal": true, "newTiddler": true, + "permaview": true, "saveChanges": true, + "search": true, "slider": true, "tabs": true, + "tag": true, "tagging": true, "tags": true, + "tiddler": true, "timeline": true, + "today": true, "version": true, "option": true, + "with": true, "filter": true + }; + + var isSpaceName = /[\w_\-]/i, + reHR = /^\-\-\-\-+$/, //
        + reWikiCommentStart = /^\/\*\*\*$/, // /*** + reWikiCommentStop = /^\*\*\*\/$/, // ***/ + reBlockQuote = /^<<<$/, + + reJsCodeStart = /^\/\/\{\{\{$/, // //{{{ js block start + reJsCodeStop = /^\/\/\}\}\}$/, // //}}} js stop + reXmlCodeStart = /^$/, // xml block start + reXmlCodeStop = /^$/, // xml stop + + reCodeBlockStart = /^\{\{\{$/, // {{{ TW text div block start + reCodeBlockStop = /^\}\}\}$/, // }}} TW text stop + + reUntilCodeStop = /.*?\}\}\}/; + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + function tokenBase(stream, state) { + var sol = stream.sol(), ch = stream.peek(); + + state.block = false; // indicates the start of a code block. + + // check start of blocks + if (sol && /[<\/\*{}\-]/.test(ch)) { + if (stream.match(reCodeBlockStart)) { + state.block = true; + return chain(stream, state, twTokenCode); + } + if (stream.match(reBlockQuote)) + return 'quote'; + if (stream.match(reWikiCommentStart) || stream.match(reWikiCommentStop)) + return 'comment'; + if (stream.match(reJsCodeStart) || stream.match(reJsCodeStop) || stream.match(reXmlCodeStart) || stream.match(reXmlCodeStop)) + return 'comment'; + if (stream.match(reHR)) + return 'hr'; + } + + stream.next(); + if (sol && /[\/\*!#;:>|]/.test(ch)) { + if (ch == "!") { // tw header + stream.skipToEnd(); + return "header"; + } + if (ch == "*") { // tw list + stream.eatWhile('*'); + return "comment"; + } + if (ch == "#") { // tw numbered list + stream.eatWhile('#'); + return "comment"; + } + if (ch == ";") { // definition list, term + stream.eatWhile(';'); + return "comment"; + } + if (ch == ":") { // definition list, description + stream.eatWhile(':'); + return "comment"; + } + if (ch == ">") { // single line quote + stream.eatWhile(">"); + return "quote"; + } + if (ch == '|') + return 'header'; + } + + if (ch == '{' && stream.match(/\{\{/)) + return chain(stream, state, twTokenCode); + + // rudimentary html:// file:// link matching. TW knows much more ... + if (/[hf]/i.test(ch) && + /[ti]/i.test(stream.peek()) && + stream.match(/\b(ttps?|tp|ile):\/\/[\-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i)) + return "link"; + + // just a little string indicator, don't want to have the whole string covered + if (ch == '"') + return 'string'; + + if (ch == '~') // _no_ CamelCase indicator should be bold + return 'brace'; + + if (/[\[\]]/.test(ch) && stream.match(ch)) // check for [[..]] + return 'brace'; + + if (ch == "@") { // check for space link. TODO fix @@...@@ highlighting + stream.eatWhile(isSpaceName); + return "link"; + } + + if (/\d/.test(ch)) { // numbers + stream.eatWhile(/\d/); + return "number"; + } + + if (ch == "/") { // tw invisible comment + if (stream.eat("%")) { + return chain(stream, state, twTokenComment); + } else if (stream.eat("/")) { // + return chain(stream, state, twTokenEm); + } + } + + if (ch == "_" && stream.eat("_")) // tw underline + return chain(stream, state, twTokenUnderline); + + // strikethrough and mdash handling + if (ch == "-" && stream.eat("-")) { + // if strikethrough looks ugly, change CSS. + if (stream.peek() != ' ') + return chain(stream, state, twTokenStrike); + // mdash + if (stream.peek() == ' ') + return 'brace'; + } + + if (ch == "'" && stream.eat("'")) // tw bold + return chain(stream, state, twTokenStrong); + + if (ch == "<" && stream.eat("<")) // tw macro + return chain(stream, state, twTokenMacro); + + // core macro handling + stream.eatWhile(/[\w\$_]/); + return textwords.propertyIsEnumerable(stream.current()) ? "keyword" : null + } + + // tw invisible comment + function twTokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "%"); + } + return "comment"; + } + + // tw strong / bold + function twTokenStrong(stream, state) { + var maybeEnd = false, + ch; + while (ch = stream.next()) { + if (ch == "'" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "'"); + } + return "strong"; + } + + // tw code + function twTokenCode(stream, state) { + var sb = state.block; + + if (sb && stream.current()) { + return "comment"; + } + + if (!sb && stream.match(reUntilCodeStop)) { + state.tokenize = tokenBase; + return "comment"; + } + + if (sb && stream.sol() && stream.match(reCodeBlockStop)) { + state.tokenize = tokenBase; + return "comment"; + } + + stream.next(); + return "comment"; + } + + // tw em / italic + function twTokenEm(stream, state) { + var maybeEnd = false, + ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "/"); + } + return "em"; + } + + // tw underlined text + function twTokenUnderline(stream, state) { + var maybeEnd = false, + ch; + while (ch = stream.next()) { + if (ch == "_" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "_"); + } + return "underlined"; + } + + // tw strike through text looks ugly + // change CSS if needed + function twTokenStrike(stream, state) { + var maybeEnd = false, ch; + + while (ch = stream.next()) { + if (ch == "-" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "-"); + } + return "strikethrough"; + } + + // macro + function twTokenMacro(stream, state) { + if (stream.current() == '<<') { + return 'macro'; + } + + var ch = stream.next(); + if (!ch) { + state.tokenize = tokenBase; + return null; + } + if (ch == ">") { + if (stream.peek() == '>') { + stream.next(); + state.tokenize = tokenBase; + return "macro"; + } + } + + stream.eatWhile(/[\w\$_]/); + return keywords.propertyIsEnumerable(stream.current()) ? "keyword" : null + } + + // Interface + return { + startState: function () { + return {tokenize: tokenBase}; + }, + + token: function (stream, state) { + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + return style; + } + }; +}); + +CodeMirror.defineMIME("text/x-tiddlywiki", "tiddlywiki"); +}); diff --git a/docs/js/node_modules/codemirror/mode/tiki/tiki.css b/docs/js/node_modules/codemirror/mode/tiki/tiki.css new file mode 100644 index 000000000..1d8704c78 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/tiki/tiki.css @@ -0,0 +1,26 @@ +.cm-tw-syntaxerror { + color: #FFF; + background-color: #900; +} + +.cm-tw-deleted { + text-decoration: line-through; +} + +.cm-tw-header5 { + font-weight: bold; +} +.cm-tw-listitem:first-child { /*Added first child to fix duplicate padding when highlighting*/ + padding-left: 10px; +} + +.cm-tw-box { + border-top-width: 0px !important; + border-style: solid; + border-width: 1px; + border-color: inherit; +} + +.cm-tw-underline { + text-decoration: underline; +} \ No newline at end of file diff --git a/docs/js/node_modules/codemirror/mode/tiki/tiki.js b/docs/js/node_modules/codemirror/mode/tiki/tiki.js new file mode 100644 index 000000000..092b85953 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/tiki/tiki.js @@ -0,0 +1,312 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode('tiki', function(config) { + function inBlock(style, terminator, returnTokenizer) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.match(terminator)) { + state.tokenize = inText; + break; + } + stream.next(); + } + + if (returnTokenizer) state.tokenize = returnTokenizer; + + return style; + }; + } + + function inLine(style) { + return function(stream, state) { + while(!stream.eol()) { + stream.next(); + } + state.tokenize = inText; + return style; + }; + } + + function inText(stream, state) { + function chain(parser) { + state.tokenize = parser; + return parser(stream, state); + } + + var sol = stream.sol(); + var ch = stream.next(); + + //non start of line + switch (ch) { //switch is generally much faster than if, so it is used here + case "{": //plugin + stream.eat("/"); + stream.eatSpace(); + stream.eatWhile(/[^\s\u00a0=\"\'\/?(}]/); + state.tokenize = inPlugin; + return "tag"; + case "_": //bold + if (stream.eat("_")) + return chain(inBlock("strong", "__", inText)); + break; + case "'": //italics + if (stream.eat("'")) + return chain(inBlock("em", "''", inText)); + break; + case "(":// Wiki Link + if (stream.eat("(")) + return chain(inBlock("variable-2", "))", inText)); + break; + case "[":// Weblink + return chain(inBlock("variable-3", "]", inText)); + break; + case "|": //table + if (stream.eat("|")) + return chain(inBlock("comment", "||")); + break; + case "-": + if (stream.eat("=")) {//titleBar + return chain(inBlock("header string", "=-", inText)); + } else if (stream.eat("-")) {//deleted + return chain(inBlock("error tw-deleted", "--", inText)); + } + break; + case "=": //underline + if (stream.match("==")) + return chain(inBlock("tw-underline", "===", inText)); + break; + case ":": + if (stream.eat(":")) + return chain(inBlock("comment", "::")); + break; + case "^": //box + return chain(inBlock("tw-box", "^")); + break; + case "~": //np + if (stream.match("np~")) + return chain(inBlock("meta", "~/np~")); + break; + } + + //start of line types + if (sol) { + switch (ch) { + case "!": //header at start of line + if (stream.match('!!!!!')) { + return chain(inLine("header string")); + } else if (stream.match('!!!!')) { + return chain(inLine("header string")); + } else if (stream.match('!!!')) { + return chain(inLine("header string")); + } else if (stream.match('!!')) { + return chain(inLine("header string")); + } else { + return chain(inLine("header string")); + } + break; + case "*": //unordered list line item, or
      1. at start of line + case "#": //ordered list line item, or
      2. at start of line + case "+": //ordered list line item, or
      3. at start of line + return chain(inLine("tw-listitem bracket")); + break; + } + } + + //stream.eatWhile(/[&{]/); was eating up plugins, turned off to act less like html and more like tiki + return null; + } + + var indentUnit = config.indentUnit; + + // Return variables for tokenizers + var pluginName, type; + function inPlugin(stream, state) { + var ch = stream.next(); + var peek = stream.peek(); + + if (ch == "}") { + state.tokenize = inText; + //type = ch == ")" ? "endPlugin" : "selfclosePlugin"; inPlugin + return "tag"; + } else if (ch == "(" || ch == ")") { + return "bracket"; + } else if (ch == "=") { + type = "equals"; + + if (peek == ">") { + stream.next(); + peek = stream.peek(); + } + + //here we detect values directly after equal character with no quotes + if (!/[\'\"]/.test(peek)) { + state.tokenize = inAttributeNoQuote(); + } + //end detect values + + return "operator"; + } else if (/[\'\"]/.test(ch)) { + state.tokenize = inAttribute(ch); + return state.tokenize(stream, state); + } else { + stream.eatWhile(/[^\s\u00a0=\"\'\/?]/); + return "keyword"; + } + } + + function inAttribute(quote) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.next() == quote) { + state.tokenize = inPlugin; + break; + } + } + return "string"; + }; + } + + function inAttributeNoQuote() { + return function(stream, state) { + while (!stream.eol()) { + var ch = stream.next(); + var peek = stream.peek(); + if (ch == " " || ch == "," || /[ )}]/.test(peek)) { + state.tokenize = inPlugin; + break; + } + } + return "string"; +}; + } + +var curState, setStyle; +function pass() { + for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); +} + +function cont() { + pass.apply(null, arguments); + return true; +} + +function pushContext(pluginName, startOfLine) { + var noIndent = curState.context && curState.context.noIndent; + curState.context = { + prev: curState.context, + pluginName: pluginName, + indent: curState.indented, + startOfLine: startOfLine, + noIndent: noIndent + }; +} + +function popContext() { + if (curState.context) curState.context = curState.context.prev; +} + +function element(type) { + if (type == "openPlugin") {curState.pluginName = pluginName; return cont(attributes, endplugin(curState.startOfLine));} + else if (type == "closePlugin") { + var err = false; + if (curState.context) { + err = curState.context.pluginName != pluginName; + popContext(); + } else { + err = true; + } + if (err) setStyle = "error"; + return cont(endcloseplugin(err)); + } + else if (type == "string") { + if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata"); + if (curState.tokenize == inText) popContext(); + return cont(); + } + else return cont(); +} + +function endplugin(startOfLine) { + return function(type) { + if ( + type == "selfclosePlugin" || + type == "endPlugin" + ) + return cont(); + if (type == "endPlugin") {pushContext(curState.pluginName, startOfLine); return cont();} + return cont(); + }; +} + +function endcloseplugin(err) { + return function(type) { + if (err) setStyle = "error"; + if (type == "endPlugin") return cont(); + return pass(); + }; +} + +function attributes(type) { + if (type == "keyword") {setStyle = "attribute"; return cont(attributes);} + if (type == "equals") return cont(attvalue, attributes); + return pass(); +} +function attvalue(type) { + if (type == "keyword") {setStyle = "string"; return cont();} + if (type == "string") return cont(attvaluemaybe); + return pass(); +} +function attvaluemaybe(type) { + if (type == "string") return cont(attvaluemaybe); + else return pass(); +} +return { + startState: function() { + return {tokenize: inText, cc: [], indented: 0, startOfLine: true, pluginName: null, context: null}; + }, + token: function(stream, state) { + if (stream.sol()) { + state.startOfLine = true; + state.indented = stream.indentation(); + } + if (stream.eatSpace()) return null; + + setStyle = type = pluginName = null; + var style = state.tokenize(stream, state); + if ((style || type) && style != "comment") { + curState = state; + while (true) { + var comb = state.cc.pop() || element; + if (comb(type || style)) break; + } + } + state.startOfLine = false; + return setStyle || style; + }, + indent: function(state, textAfter) { + var context = state.context; + if (context && context.noIndent) return 0; + if (context && /^{\//.test(textAfter)) + context = context.prev; + while (context && !context.startOfLine) + context = context.prev; + if (context) return context.indent + indentUnit; + else return 0; + }, + electricChars: "/" +}; +}); + +CodeMirror.defineMIME("text/tiki", "tiki"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/toml/toml.js b/docs/js/node_modules/codemirror/mode/toml/toml.js new file mode 100644 index 000000000..891f384b5 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/toml/toml.js @@ -0,0 +1,88 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("toml", function () { + return { + startState: function () { + return { + inString: false, + stringType: "", + lhs: true, + inArray: 0 + }; + }, + token: function (stream, state) { + //check for state changes + if (!state.inString && ((stream.peek() == '"') || (stream.peek() == "'"))) { + state.stringType = stream.peek(); + stream.next(); // Skip quote + state.inString = true; // Update state + } + if (stream.sol() && state.inArray === 0) { + state.lhs = true; + } + //return state + if (state.inString) { + while (state.inString && !stream.eol()) { + if (stream.peek() === state.stringType) { + stream.next(); // Skip quote + state.inString = false; // Clear flag + } else if (stream.peek() === '\\') { + stream.next(); + stream.next(); + } else { + stream.match(/^.[^\\\"\']*/); + } + } + return state.lhs ? "property string" : "string"; // Token style + } else if (state.inArray && stream.peek() === ']') { + stream.next(); + state.inArray--; + return 'bracket'; + } else if (state.lhs && stream.peek() === '[' && stream.skipTo(']')) { + stream.next();//skip closing ] + // array of objects has an extra open & close [] + if (stream.peek() === ']') stream.next(); + return "atom"; + } else if (stream.peek() === "#") { + stream.skipToEnd(); + return "comment"; + } else if (stream.eatSpace()) { + return null; + } else if (state.lhs && stream.eatWhile(function (c) { return c != '=' && c != ' '; })) { + return "property"; + } else if (state.lhs && stream.peek() === "=") { + stream.next(); + state.lhs = false; + return null; + } else if (!state.lhs && stream.match(/^\d\d\d\d[\d\-\:\.T]*Z/)) { + return 'atom'; //date + } else if (!state.lhs && (stream.match('true') || stream.match('false'))) { + return 'atom'; + } else if (!state.lhs && stream.peek() === '[') { + state.inArray++; + stream.next(); + return 'bracket'; + } else if (!state.lhs && stream.match(/^\-?\d+(?:\.\d+)?/)) { + return 'number'; + } else if (!stream.eatSpace()) { + stream.next(); + } + return null; + } + }; +}); + +CodeMirror.defineMIME('text/x-toml', 'toml'); + +}); diff --git a/docs/js/node_modules/codemirror/mode/tornado/tornado.js b/docs/js/node_modules/codemirror/mode/tornado/tornado.js new file mode 100644 index 000000000..aa589a08c --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/tornado/tornado.js @@ -0,0 +1,68 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), + require("../../addon/mode/overlay")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../htmlmixed/htmlmixed", + "../../addon/mode/overlay"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("tornado:inner", function() { + var keywords = ["and","as","assert","autoescape","block","break","class","comment","context", + "continue","datetime","def","del","elif","else","end","escape","except", + "exec","extends","false","finally","for","from","global","if","import","in", + "include","is","json_encode","lambda","length","linkify","load","module", + "none","not","or","pass","print","put","raise","raw","return","self","set", + "squeeze","super","true","try","url_escape","while","with","without","xhtml_escape","yield"]; + keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b"); + + function tokenBase (stream, state) { + stream.eatWhile(/[^\{]/); + var ch = stream.next(); + if (ch == "{") { + if (ch = stream.eat(/\{|%|#/)) { + state.tokenize = inTag(ch); + return "tag"; + } + } + } + function inTag (close) { + if (close == "{") { + close = "}"; + } + return function (stream, state) { + var ch = stream.next(); + if ((ch == close) && stream.eat("}")) { + state.tokenize = tokenBase; + return "tag"; + } + if (stream.match(keywords)) { + return "keyword"; + } + return close == "#" ? "comment" : "string"; + }; + } + return { + startState: function () { + return {tokenize: tokenBase}; + }, + token: function (stream, state) { + return state.tokenize(stream, state); + } + }; + }); + + CodeMirror.defineMode("tornado", function(config) { + var htmlBase = CodeMirror.getMode(config, "text/html"); + var tornadoInner = CodeMirror.getMode(config, "tornado:inner"); + return CodeMirror.overlayMode(htmlBase, tornadoInner); + }); + + CodeMirror.defineMIME("text/x-tornado", "tornado"); +}); diff --git a/docs/js/node_modules/codemirror/mode/troff/troff.js b/docs/js/node_modules/codemirror/mode/troff/troff.js new file mode 100644 index 000000000..0c2220d2c --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/troff/troff.js @@ -0,0 +1,84 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) + define(["../../lib/codemirror"], mod); + else + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode('troff', function() { + + var words = {}; + + function tokenBase(stream) { + if (stream.eatSpace()) return null; + + var sol = stream.sol(); + var ch = stream.next(); + + if (ch === '\\') { + if (stream.match('fB') || stream.match('fR') || stream.match('fI') || + stream.match('u') || stream.match('d') || + stream.match('%') || stream.match('&')) { + return 'string'; + } + if (stream.match('m[')) { + stream.skipTo(']'); + stream.next(); + return 'string'; + } + if (stream.match('s+') || stream.match('s-')) { + stream.eatWhile(/[\d-]/); + return 'string'; + } + if (stream.match('\(') || stream.match('*\(')) { + stream.eatWhile(/[\w-]/); + return 'string'; + } + return 'string'; + } + if (sol && (ch === '.' || ch === '\'')) { + if (stream.eat('\\') && stream.eat('\"')) { + stream.skipToEnd(); + return 'comment'; + } + } + if (sol && ch === '.') { + if (stream.match('B ') || stream.match('I ') || stream.match('R ')) { + return 'attribute'; + } + if (stream.match('TH ') || stream.match('SH ') || stream.match('SS ') || stream.match('HP ')) { + stream.skipToEnd(); + return 'quote'; + } + if ((stream.match(/[A-Z]/) && stream.match(/[A-Z]/)) || (stream.match(/[a-z]/) && stream.match(/[a-z]/))) { + return 'attribute'; + } + } + stream.eatWhile(/[\w-]/); + var cur = stream.current(); + return words.hasOwnProperty(cur) ? words[cur] : null; + } + + function tokenize(stream, state) { + return (state.tokens[0] || tokenBase) (stream, state); + }; + + return { + startState: function() {return {tokens:[]};}, + token: function(stream, state) { + return tokenize(stream, state); + } + }; +}); + +CodeMirror.defineMIME('text/troff', 'troff'); +CodeMirror.defineMIME('text/x-troff', 'troff'); +CodeMirror.defineMIME('application/x-troff', 'troff'); + +}); diff --git a/docs/js/node_modules/codemirror/mode/ttcn-cfg/ttcn-cfg.js b/docs/js/node_modules/codemirror/mode/ttcn-cfg/ttcn-cfg.js new file mode 100644 index 000000000..9d4b8405a --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/ttcn-cfg/ttcn-cfg.js @@ -0,0 +1,214 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("ttcn-cfg", function(config, parserConfig) { + var indentUnit = config.indentUnit, + keywords = parserConfig.keywords || {}, + fileNCtrlMaskOptions = parserConfig.fileNCtrlMaskOptions || {}, + externalCommands = parserConfig.externalCommands || {}, + multiLineStrings = parserConfig.multiLineStrings, + indentStatements = parserConfig.indentStatements !== false; + var isOperatorChar = /[\|]/; + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[:=]/.test(ch)) { + curPunc = ch; + return "punctuation"; + } + if (ch == "#"){ + stream.skipToEnd(); + return "comment"; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + if (ch == "["){ + stream.eatWhile(/[\w_\]]/); + return "number sectionTitle"; + } + + stream.eatWhile(/[\w\$_]/); + var cur = stream.current(); + if (keywords.propertyIsEnumerable(cur)) return "keyword"; + if (fileNCtrlMaskOptions.propertyIsEnumerable(cur)) + return "negative fileNCtrlMaskOptions"; + if (externalCommands.propertyIsEnumerable(cur)) return "negative externalCommands"; + + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped){ + var afterNext = stream.peek(); + //look if the character if the quote is like the B in '10100010'B + if (afterNext){ + afterNext = afterNext.toLowerCase(); + if(afterNext == "b" || afterNext == "h" || afterNext == "o") + stream.next(); + } + end = true; break; + } + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = null; + return "string"; + }; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + var indent = state.indented; + if (state.context && state.context.type == "statement") + indent = state.context.indented; + return state.context = new Context(indent, col, type, null, state.context); + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + //Interface + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment") return style; + if (ctx.align == null) ctx.align = true; + + if ((curPunc == ";" || curPunc == ":" || curPunc == ",") + && ctx.type == "statement"){ + popContext(state); + } + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (indentStatements && (((ctx.type == "}" || ctx.type == "top") + && curPunc != ';') || (ctx.type == "statement" + && curPunc == "newstatement"))) + pushContext(state, stream.column(), "statement"); + state.startOfLine = false; + return style; + }, + + electricChars: "{}", + lineComment: "#", + fold: "brace" + }; + }); + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) + obj[words[i]] = true; + return obj; + } + + CodeMirror.defineMIME("text/x-ttcn-cfg", { + name: "ttcn-cfg", + keywords: words("Yes No LogFile FileMask ConsoleMask AppendFile" + + " TimeStampFormat LogEventTypes SourceInfoFormat" + + " LogEntityName LogSourceInfo DiskFullAction" + + " LogFileNumber LogFileSize MatchingHints Detailed" + + " Compact SubCategories Stack Single None Seconds" + + " DateTime Time Stop Error Retry Delete TCPPort KillTimer" + + " NumHCs UnixSocketsEnabled LocalAddress"), + fileNCtrlMaskOptions: words("TTCN_EXECUTOR TTCN_ERROR TTCN_WARNING" + + " TTCN_PORTEVENT TTCN_TIMEROP TTCN_VERDICTOP" + + " TTCN_DEFAULTOP TTCN_TESTCASE TTCN_ACTION" + + " TTCN_USER TTCN_FUNCTION TTCN_STATISTICS" + + " TTCN_PARALLEL TTCN_MATCHING TTCN_DEBUG" + + " EXECUTOR ERROR WARNING PORTEVENT TIMEROP" + + " VERDICTOP DEFAULTOP TESTCASE ACTION USER" + + " FUNCTION STATISTICS PARALLEL MATCHING DEBUG" + + " LOG_ALL LOG_NOTHING ACTION_UNQUALIFIED" + + " DEBUG_ENCDEC DEBUG_TESTPORT" + + " DEBUG_UNQUALIFIED DEFAULTOP_ACTIVATE" + + " DEFAULTOP_DEACTIVATE DEFAULTOP_EXIT" + + " DEFAULTOP_UNQUALIFIED ERROR_UNQUALIFIED" + + " EXECUTOR_COMPONENT EXECUTOR_CONFIGDATA" + + " EXECUTOR_EXTCOMMAND EXECUTOR_LOGOPTIONS" + + " EXECUTOR_RUNTIME EXECUTOR_UNQUALIFIED" + + " FUNCTION_RND FUNCTION_UNQUALIFIED" + + " MATCHING_DONE MATCHING_MCSUCCESS" + + " MATCHING_MCUNSUCC MATCHING_MMSUCCESS" + + " MATCHING_MMUNSUCC MATCHING_PCSUCCESS" + + " MATCHING_PCUNSUCC MATCHING_PMSUCCESS" + + " MATCHING_PMUNSUCC MATCHING_PROBLEM" + + " MATCHING_TIMEOUT MATCHING_UNQUALIFIED" + + " PARALLEL_PORTCONN PARALLEL_PORTMAP" + + " PARALLEL_PTC PARALLEL_UNQUALIFIED" + + " PORTEVENT_DUALRECV PORTEVENT_DUALSEND" + + " PORTEVENT_MCRECV PORTEVENT_MCSEND" + + " PORTEVENT_MMRECV PORTEVENT_MMSEND" + + " PORTEVENT_MQUEUE PORTEVENT_PCIN" + + " PORTEVENT_PCOUT PORTEVENT_PMIN" + + " PORTEVENT_PMOUT PORTEVENT_PQUEUE" + + " PORTEVENT_STATE PORTEVENT_UNQUALIFIED" + + " STATISTICS_UNQUALIFIED STATISTICS_VERDICT" + + " TESTCASE_FINISH TESTCASE_START" + + " TESTCASE_UNQUALIFIED TIMEROP_GUARD" + + " TIMEROP_READ TIMEROP_START TIMEROP_STOP" + + " TIMEROP_TIMEOUT TIMEROP_UNQUALIFIED" + + " USER_UNQUALIFIED VERDICTOP_FINAL" + + " VERDICTOP_GETVERDICT VERDICTOP_SETVERDICT" + + " VERDICTOP_UNQUALIFIED WARNING_UNQUALIFIED"), + externalCommands: words("BeginControlPart EndControlPart BeginTestCase" + + " EndTestCase"), + multiLineStrings: true + }); +}); \ No newline at end of file diff --git a/docs/js/node_modules/codemirror/mode/ttcn/ttcn.js b/docs/js/node_modules/codemirror/mode/ttcn/ttcn.js new file mode 100644 index 000000000..0304e7c53 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/ttcn/ttcn.js @@ -0,0 +1,283 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("ttcn", function(config, parserConfig) { + var indentUnit = config.indentUnit, + keywords = parserConfig.keywords || {}, + builtin = parserConfig.builtin || {}, + timerOps = parserConfig.timerOps || {}, + portOps = parserConfig.portOps || {}, + configOps = parserConfig.configOps || {}, + verdictOps = parserConfig.verdictOps || {}, + sutOps = parserConfig.sutOps || {}, + functionOps = parserConfig.functionOps || {}, + + verdictConsts = parserConfig.verdictConsts || {}, + booleanConsts = parserConfig.booleanConsts || {}, + otherConsts = parserConfig.otherConsts || {}, + + types = parserConfig.types || {}, + visibilityModifiers = parserConfig.visibilityModifiers || {}, + templateMatch = parserConfig.templateMatch || {}, + multiLineStrings = parserConfig.multiLineStrings, + indentStatements = parserConfig.indentStatements !== false; + var isOperatorChar = /[+\-*&@=<>!\/]/; + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\[\]{}\(\),;\\:\?\.]/.test(ch)) { + curPunc = ch; + return "punctuation"; + } + if (ch == "#"){ + stream.skipToEnd(); + return "atom preprocessor"; + } + if (ch == "%"){ + stream.eatWhile(/\b/); + return "atom ttcn3Macros"; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + if(ch == "@"){ + if(stream.match("try") || stream.match("catch") + || stream.match("lazy")){ + return "keyword"; + } + } + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + var cur = stream.current(); + + if (keywords.propertyIsEnumerable(cur)) return "keyword"; + if (builtin.propertyIsEnumerable(cur)) return "builtin"; + + if (timerOps.propertyIsEnumerable(cur)) return "def timerOps"; + if (configOps.propertyIsEnumerable(cur)) return "def configOps"; + if (verdictOps.propertyIsEnumerable(cur)) return "def verdictOps"; + if (portOps.propertyIsEnumerable(cur)) return "def portOps"; + if (sutOps.propertyIsEnumerable(cur)) return "def sutOps"; + if (functionOps.propertyIsEnumerable(cur)) return "def functionOps"; + + if (verdictConsts.propertyIsEnumerable(cur)) return "string verdictConsts"; + if (booleanConsts.propertyIsEnumerable(cur)) return "string booleanConsts"; + if (otherConsts.propertyIsEnumerable(cur)) return "string otherConsts"; + + if (types.propertyIsEnumerable(cur)) return "builtin types"; + if (visibilityModifiers.propertyIsEnumerable(cur)) + return "builtin visibilityModifiers"; + if (templateMatch.propertyIsEnumerable(cur)) return "atom templateMatch"; + + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped){ + var afterQuote = stream.peek(); + //look if the character after the quote is like the B in '10100010'B + if (afterQuote){ + afterQuote = afterQuote.toLowerCase(); + if(afterQuote == "b" || afterQuote == "h" || afterQuote == "o") + stream.next(); + } + end = true; break; + } + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = null; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = null; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + + function pushContext(state, col, type) { + var indent = state.indented; + if (state.context && state.context.type == "statement") + indent = state.context.indented; + return state.context = new Context(indent, col, type, null, state.context); + } + + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + //Interface + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment") return style; + if (ctx.align == null) ctx.align = true; + + if ((curPunc == ";" || curPunc == ":" || curPunc == ",") + && ctx.type == "statement"){ + popContext(state); + } + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (indentStatements && + (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || + (ctx.type == "statement" && curPunc == "newstatement"))) + pushContext(state, stream.column(), "statement"); + + state.startOfLine = false; + + return style; + }, + + electricChars: "{}", + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//", + fold: "brace" + }; + }); + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + function def(mimes, mode) { + if (typeof mimes == "string") mimes = [mimes]; + var words = []; + function add(obj) { + if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop)) + words.push(prop); + } + + add(mode.keywords); + add(mode.builtin); + add(mode.timerOps); + add(mode.portOps); + + if (words.length) { + mode.helperType = mimes[0]; + CodeMirror.registerHelper("hintWords", mimes[0], words); + } + + for (var i = 0; i < mimes.length; ++i) + CodeMirror.defineMIME(mimes[i], mode); + } + + def(["text/x-ttcn", "text/x-ttcn3", "text/x-ttcnpp"], { + name: "ttcn", + keywords: words("activate address alive all alt altstep and and4b any" + + " break case component const continue control deactivate" + + " display do else encode enumerated except exception" + + " execute extends extension external for from function" + + " goto group if import in infinity inout interleave" + + " label language length log match message mixed mod" + + " modifies module modulepar mtc noblock not not4b nowait" + + " of on optional or or4b out override param pattern port" + + " procedure record recursive rem repeat return runs select" + + " self sender set signature system template testcase to" + + " type union value valueof var variant while with xor xor4b"), + builtin: words("bit2hex bit2int bit2oct bit2str char2int char2oct encvalue" + + " decomp decvalue float2int float2str hex2bit hex2int" + + " hex2oct hex2str int2bit int2char int2float int2hex" + + " int2oct int2str int2unichar isbound ischosen ispresent" + + " isvalue lengthof log2str oct2bit oct2char oct2hex oct2int" + + " oct2str regexp replace rnd sizeof str2bit str2float" + + " str2hex str2int str2oct substr unichar2int unichar2char" + + " enum2int"), + types: words("anytype bitstring boolean char charstring default float" + + " hexstring integer objid octetstring universal verdicttype timer"), + timerOps: words("read running start stop timeout"), + portOps: words("call catch check clear getcall getreply halt raise receive" + + " reply send trigger"), + configOps: words("create connect disconnect done kill killed map unmap"), + verdictOps: words("getverdict setverdict"), + sutOps: words("action"), + functionOps: words("apply derefers refers"), + + verdictConsts: words("error fail inconc none pass"), + booleanConsts: words("true false"), + otherConsts: words("null NULL omit"), + + visibilityModifiers: words("private public friend"), + templateMatch: words("complement ifpresent subset superset permutation"), + multiLineStrings: true + }); +}); diff --git a/docs/js/node_modules/codemirror/mode/turtle/turtle.js b/docs/js/node_modules/codemirror/mode/turtle/turtle.js new file mode 100644 index 000000000..695239661 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/turtle/turtle.js @@ -0,0 +1,162 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("turtle", function(config) { + var indentUnit = config.indentUnit; + var curPunc; + + function wordRegexp(words) { + return new RegExp("^(?:" + words.join("|") + ")$", "i"); + } + var ops = wordRegexp([]); + var keywords = wordRegexp(["@prefix", "@base", "a"]); + var operatorChars = /[*+\-<>=&|]/; + + function tokenBase(stream, state) { + var ch = stream.next(); + curPunc = null; + if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { + stream.match(/^[^\s\u00a0>]*>?/); + return "atom"; + } + else if (ch == "\"" || ch == "'") { + state.tokenize = tokenLiteral(ch); + return state.tokenize(stream, state); + } + else if (/[{}\(\),\.;\[\]]/.test(ch)) { + curPunc = ch; + return null; + } + else if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } + else if (operatorChars.test(ch)) { + stream.eatWhile(operatorChars); + return null; + } + else if (ch == ":") { + return "operator"; + } else { + stream.eatWhile(/[_\w\d]/); + if(stream.peek() == ":") { + return "variable-3"; + } else { + var word = stream.current(); + + if(keywords.test(word)) { + return "meta"; + } + + if(ch >= "A" && ch <= "Z") { + return "comment"; + } else { + return "keyword"; + } + } + var word = stream.current(); + if (ops.test(word)) + return null; + else if (keywords.test(word)) + return "meta"; + else + return "variable"; + } + } + + function tokenLiteral(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return "string"; + }; + } + + function pushContext(state, type, col) { + state.context = {prev: state.context, indent: state.indent, col: col, type: type}; + } + function popContext(state) { + state.indent = state.context.indent; + state.context = state.context.prev; + } + + return { + startState: function() { + return {tokenize: tokenBase, + context: null, + indent: 0, + col: 0}; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (state.context && state.context.align == null) state.context.align = false; + state.indent = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + + if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { + state.context.align = true; + } + + if (curPunc == "(") pushContext(state, ")", stream.column()); + else if (curPunc == "[") pushContext(state, "]", stream.column()); + else if (curPunc == "{") pushContext(state, "}", stream.column()); + else if (/[\]\}\)]/.test(curPunc)) { + while (state.context && state.context.type == "pattern") popContext(state); + if (state.context && curPunc == state.context.type) popContext(state); + } + else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); + else if (/atom|string|variable/.test(style) && state.context) { + if (/[\}\]]/.test(state.context.type)) + pushContext(state, "pattern", stream.column()); + else if (state.context.type == "pattern" && !state.context.align) { + state.context.align = true; + state.context.col = stream.column(); + } + } + + return style; + }, + + indent: function(state, textAfter) { + var firstChar = textAfter && textAfter.charAt(0); + var context = state.context; + if (/[\]\}]/.test(firstChar)) + while (context && context.type == "pattern") context = context.prev; + + var closing = context && firstChar == context.type; + if (!context) + return 0; + else if (context.type == "pattern") + return context.col; + else if (context.align) + return context.col + (closing ? 0 : 1); + else + return context.indent + (closing ? 0 : indentUnit); + }, + + lineComment: "#" + }; +}); + +CodeMirror.defineMIME("text/turtle", "turtle"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/twig/twig.js b/docs/js/node_modules/codemirror/mode/twig/twig.js new file mode 100644 index 000000000..a6dd3f1a6 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/twig/twig.js @@ -0,0 +1,141 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../../addon/mode/multiplex")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../../addon/mode/multiplex"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("twig:inner", function() { + var keywords = ["and", "as", "autoescape", "endautoescape", "block", "do", "endblock", "else", "elseif", "extends", "for", "endfor", "embed", "endembed", "filter", "endfilter", "flush", "from", "if", "endif", "in", "is", "include", "import", "not", "or", "set", "spaceless", "endspaceless", "with", "endwith", "trans", "endtrans", "blocktrans", "endblocktrans", "macro", "endmacro", "use", "verbatim", "endverbatim"], + operator = /^[+\-*&%=<>!?|~^]/, + sign = /^[:\[\(\{]/, + atom = ["true", "false", "null", "empty", "defined", "divisibleby", "divisible by", "even", "odd", "iterable", "sameas", "same as"], + number = /^(\d[+\-\*\/])?\d+(\.\d+)?/; + + keywords = new RegExp("((" + keywords.join(")|(") + "))\\b"); + atom = new RegExp("((" + atom.join(")|(") + "))\\b"); + + function tokenBase (stream, state) { + var ch = stream.peek(); + + //Comment + if (state.incomment) { + if (!stream.skipTo("#}")) { + stream.skipToEnd(); + } else { + stream.eatWhile(/\#|}/); + state.incomment = false; + } + return "comment"; + //Tag + } else if (state.intag) { + //After operator + if (state.operator) { + state.operator = false; + if (stream.match(atom)) { + return "atom"; + } + if (stream.match(number)) { + return "number"; + } + } + //After sign + if (state.sign) { + state.sign = false; + if (stream.match(atom)) { + return "atom"; + } + if (stream.match(number)) { + return "number"; + } + } + + if (state.instring) { + if (ch == state.instring) { + state.instring = false; + } + stream.next(); + return "string"; + } else if (ch == "'" || ch == '"') { + state.instring = ch; + stream.next(); + return "string"; + } else if (stream.match(state.intag + "}") || stream.eat("-") && stream.match(state.intag + "}")) { + state.intag = false; + return "tag"; + } else if (stream.match(operator)) { + state.operator = true; + return "operator"; + } else if (stream.match(sign)) { + state.sign = true; + } else { + if (stream.eat(" ") || stream.sol()) { + if (stream.match(keywords)) { + return "keyword"; + } + if (stream.match(atom)) { + return "atom"; + } + if (stream.match(number)) { + return "number"; + } + if (stream.sol()) { + stream.next(); + } + } else { + stream.next(); + } + + } + return "variable"; + } else if (stream.eat("{")) { + if (stream.eat("#")) { + state.incomment = true; + if (!stream.skipTo("#}")) { + stream.skipToEnd(); + } else { + stream.eatWhile(/\#|}/); + state.incomment = false; + } + return "comment"; + //Open tag + } else if (ch = stream.eat(/\{|%/)) { + //Cache close tag + state.intag = ch; + if (ch == "{") { + state.intag = "}"; + } + stream.eat("-"); + return "tag"; + } + } + stream.next(); + }; + + return { + startState: function () { + return {}; + }, + token: function (stream, state) { + return tokenBase(stream, state); + } + }; + }); + + CodeMirror.defineMode("twig", function(config, parserConfig) { + var twigInner = CodeMirror.getMode(config, "twig:inner"); + if (!parserConfig || !parserConfig.base) return twigInner; + return CodeMirror.multiplexingMode( + CodeMirror.getMode(config, parserConfig.base), { + open: /\{[{#%]/, close: /[}#%]\}/, mode: twigInner, parseDelimiters: true + } + ); + }); + CodeMirror.defineMIME("text/x-twig", "twig"); +}); diff --git a/docs/js/node_modules/codemirror/mode/vb/vb.js b/docs/js/node_modules/codemirror/mode/vb/vb.js new file mode 100644 index 000000000..6e4b47630 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/vb/vb.js @@ -0,0 +1,275 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("vb", function(conf, parserConf) { + var ERRORCLASS = 'error'; + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); + } + + var singleOperators = new RegExp("^[\\+\\-\\*/%&\\\\|\\^~<>!]"); + var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]'); + var doubleOperators = new RegExp("^((==)|(<>)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))"); + var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); + var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); + var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); + + var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property', 'try', 'structure', 'synclock', 'using', 'with']; + var middleKeywords = ['else','elseif','case', 'catch', 'finally']; + var endKeywords = ['next','loop']; + + var operatorKeywords = ['and', "andalso", 'or', 'orelse', 'xor', 'in', 'not', 'is', 'isnot', 'like']; + var wordOperators = wordRegexp(operatorKeywords); + + var commonKeywords = ["#const", "#else", "#elseif", "#end", "#if", "#region", "addhandler", "addressof", "alias", "as", "byref", "byval", "cbool", "cbyte", "cchar", "cdate", "cdbl", "cdec", "cint", "clng", "cobj", "compare", "const", "continue", "csbyte", "cshort", "csng", "cstr", "cuint", "culng", "cushort", "declare", "default", "delegate", "dim", "directcast", "each", "erase", "error", "event", "exit", "explicit", "false", "for", "friend", "gettype", "goto", "handles", "implements", "imports", "infer", "inherits", "interface", "isfalse", "istrue", "lib", "me", "mod", "mustinherit", "mustoverride", "my", "mybase", "myclass", "namespace", "narrowing", "new", "nothing", "notinheritable", "notoverridable", "of", "off", "on", "operator", "option", "optional", "out", "overloads", "overridable", "overrides", "paramarray", "partial", "private", "protected", "public", "raiseevent", "readonly", "redim", "removehandler", "resume", "return", "shadows", "shared", "static", "step", "stop", "strict", "then", "throw", "to", "true", "trycast", "typeof", "until", "until", "when", "widening", "withevents", "writeonly"]; + + var commontypes = ['object', 'boolean', 'char', 'string', 'byte', 'sbyte', 'short', 'ushort', 'int16', 'uint16', 'integer', 'uinteger', 'int32', 'uint32', 'long', 'ulong', 'int64', 'uint64', 'decimal', 'single', 'double', 'float', 'date', 'datetime', 'intptr', 'uintptr']; + + var keywords = wordRegexp(commonKeywords); + var types = wordRegexp(commontypes); + var stringPrefixes = '"'; + + var opening = wordRegexp(openingKeywords); + var middle = wordRegexp(middleKeywords); + var closing = wordRegexp(endKeywords); + var doubleClosing = wordRegexp(['end']); + var doOpening = wordRegexp(['do']); + + var indentInfo = null; + + CodeMirror.registerHelper("hintWords", "vb", openingKeywords.concat(middleKeywords).concat(endKeywords) + .concat(operatorKeywords).concat(commonKeywords).concat(commontypes)); + + function indent(_stream, state) { + state.currentIndent++; + } + + function dedent(_stream, state) { + state.currentIndent--; + } + // tokenizers + function tokenBase(stream, state) { + if (stream.eatSpace()) { + return null; + } + + var ch = stream.peek(); + + // Handle Comments + if (ch === "'") { + stream.skipToEnd(); + return 'comment'; + } + + + // Handle Number Literals + if (stream.match(/^((&H)|(&O))?[0-9\.a-f]/i, false)) { + var floatLiteral = false; + // Floats + if (stream.match(/^\d*\.\d+F?/i)) { floatLiteral = true; } + else if (stream.match(/^\d+\.\d*F?/)) { floatLiteral = true; } + else if (stream.match(/^\.\d+F?/)) { floatLiteral = true; } + + if (floatLiteral) { + // Float literals may be "imaginary" + stream.eat(/J/i); + return 'number'; + } + // Integers + var intLiteral = false; + // Hex + if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; } + // Octal + else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; } + // Decimal + else if (stream.match(/^[1-9]\d*F?/)) { + // Decimal literals may be "imaginary" + stream.eat(/J/i); + // TODO - Can you have imaginary longs? + intLiteral = true; + } + // Zero by itself with no other piece of number. + else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } + if (intLiteral) { + // Integer literals may be "long" + stream.eat(/L/i); + return 'number'; + } + } + + // Handle Strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenStringFactory(stream.current()); + return state.tokenize(stream, state); + } + + // Handle operators and Delimiters + if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { + return null; + } + if (stream.match(doubleOperators) + || stream.match(singleOperators) + || stream.match(wordOperators)) { + return 'operator'; + } + if (stream.match(singleDelimiters)) { + return null; + } + if (stream.match(doOpening)) { + indent(stream,state); + state.doInCurrentLine = true; + return 'keyword'; + } + if (stream.match(opening)) { + if (! state.doInCurrentLine) + indent(stream,state); + else + state.doInCurrentLine = false; + return 'keyword'; + } + if (stream.match(middle)) { + return 'keyword'; + } + + if (stream.match(doubleClosing)) { + dedent(stream,state); + dedent(stream,state); + return 'keyword'; + } + if (stream.match(closing)) { + dedent(stream,state); + return 'keyword'; + } + + if (stream.match(types)) { + return 'keyword'; + } + + if (stream.match(keywords)) { + return 'keyword'; + } + + if (stream.match(identifiers)) { + return 'variable'; + } + + // Handle non-detected items + stream.next(); + return ERRORCLASS; + } + + function tokenStringFactory(delimiter) { + var singleline = delimiter.length == 1; + var OUTCLASS = 'string'; + + return function(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"]/); + if (stream.match(delimiter)) { + state.tokenize = tokenBase; + return OUTCLASS; + } else { + stream.eat(/['"]/); + } + } + if (singleline) { + if (parserConf.singleLineStringErrors) { + return ERRORCLASS; + } else { + state.tokenize = tokenBase; + } + } + return OUTCLASS; + }; + } + + + function tokenLexer(stream, state) { + var style = state.tokenize(stream, state); + var current = stream.current(); + + // Handle '.' connected identifiers + if (current === '.') { + style = state.tokenize(stream, state); + if (style === 'variable') { + return 'variable'; + } else { + return ERRORCLASS; + } + } + + + var delimiter_index = '[({'.indexOf(current); + if (delimiter_index !== -1) { + indent(stream, state ); + } + if (indentInfo === 'dedent') { + if (dedent(stream, state)) { + return ERRORCLASS; + } + } + delimiter_index = '])}'.indexOf(current); + if (delimiter_index !== -1) { + if (dedent(stream, state)) { + return ERRORCLASS; + } + } + + return style; + } + + var external = { + electricChars:"dDpPtTfFeE ", + startState: function() { + return { + tokenize: tokenBase, + lastToken: null, + currentIndent: 0, + nextLineIndent: 0, + doInCurrentLine: false + + + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + state.currentIndent += state.nextLineIndent; + state.nextLineIndent = 0; + state.doInCurrentLine = 0; + } + var style = tokenLexer(stream, state); + + state.lastToken = {style:style, content: stream.current()}; + + + + return style; + }, + + indent: function(state, textAfter) { + var trueText = textAfter.replace(/^\s+|\s+$/g, '') ; + if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1); + if(state.currentIndent < 0) return 0; + return state.currentIndent * conf.indentUnit; + }, + + lineComment: "'" + }; + return external; +}); + +CodeMirror.defineMIME("text/x-vb", "vb"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/vbscript/vbscript.js b/docs/js/node_modules/codemirror/mode/vbscript/vbscript.js new file mode 100644 index 000000000..0670c0cee --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/vbscript/vbscript.js @@ -0,0 +1,350 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +/* +For extra ASP classic objects, initialize CodeMirror instance with this option: + isASP: true + +E.G.: + var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + lineNumbers: true, + isASP: true + }); +*/ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("vbscript", function(conf, parserConf) { + var ERRORCLASS = 'error'; + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); + } + + var singleOperators = new RegExp("^[\\+\\-\\*/&\\\\\\^<>=]"); + var doubleOperators = new RegExp("^((<>)|(<=)|(>=))"); + var singleDelimiters = new RegExp('^[\\.,]'); + var brakets = new RegExp('^[\\(\\)]'); + var identifiers = new RegExp("^[A-Za-z][_A-Za-z0-9]*"); + + var openingKeywords = ['class','sub','select','while','if','function', 'property', 'with', 'for']; + var middleKeywords = ['else','elseif','case']; + var endKeywords = ['next','loop','wend']; + + var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'is', 'mod', 'eqv', 'imp']); + var commonkeywords = ['dim', 'redim', 'then', 'until', 'randomize', + 'byval','byref','new','property', 'exit', 'in', + 'const','private', 'public', + 'get','set','let', 'stop', 'on error resume next', 'on error goto 0', 'option explicit', 'call', 'me']; + + //This list was from: http://msdn.microsoft.com/en-us/library/f8tbc79x(v=vs.84).aspx + var atomWords = ['true', 'false', 'nothing', 'empty', 'null']; + //This list was from: http://msdn.microsoft.com/en-us/library/3ca8tfek(v=vs.84).aspx + var builtinFuncsWords = ['abs', 'array', 'asc', 'atn', 'cbool', 'cbyte', 'ccur', 'cdate', 'cdbl', 'chr', 'cint', 'clng', 'cos', 'csng', 'cstr', 'date', 'dateadd', 'datediff', 'datepart', + 'dateserial', 'datevalue', 'day', 'escape', 'eval', 'execute', 'exp', 'filter', 'formatcurrency', 'formatdatetime', 'formatnumber', 'formatpercent', 'getlocale', 'getobject', + 'getref', 'hex', 'hour', 'inputbox', 'instr', 'instrrev', 'int', 'fix', 'isarray', 'isdate', 'isempty', 'isnull', 'isnumeric', 'isobject', 'join', 'lbound', 'lcase', 'left', + 'len', 'loadpicture', 'log', 'ltrim', 'rtrim', 'trim', 'maths', 'mid', 'minute', 'month', 'monthname', 'msgbox', 'now', 'oct', 'replace', 'rgb', 'right', 'rnd', 'round', + 'scriptengine', 'scriptenginebuildversion', 'scriptenginemajorversion', 'scriptengineminorversion', 'second', 'setlocale', 'sgn', 'sin', 'space', 'split', 'sqr', 'strcomp', + 'string', 'strreverse', 'tan', 'time', 'timer', 'timeserial', 'timevalue', 'typename', 'ubound', 'ucase', 'unescape', 'vartype', 'weekday', 'weekdayname', 'year']; + + //This list was from: http://msdn.microsoft.com/en-us/library/ydz4cfk3(v=vs.84).aspx + var builtinConsts = ['vbBlack', 'vbRed', 'vbGreen', 'vbYellow', 'vbBlue', 'vbMagenta', 'vbCyan', 'vbWhite', 'vbBinaryCompare', 'vbTextCompare', + 'vbSunday', 'vbMonday', 'vbTuesday', 'vbWednesday', 'vbThursday', 'vbFriday', 'vbSaturday', 'vbUseSystemDayOfWeek', 'vbFirstJan1', 'vbFirstFourDays', 'vbFirstFullWeek', + 'vbGeneralDate', 'vbLongDate', 'vbShortDate', 'vbLongTime', 'vbShortTime', 'vbObjectError', + 'vbOKOnly', 'vbOKCancel', 'vbAbortRetryIgnore', 'vbYesNoCancel', 'vbYesNo', 'vbRetryCancel', 'vbCritical', 'vbQuestion', 'vbExclamation', 'vbInformation', 'vbDefaultButton1', 'vbDefaultButton2', + 'vbDefaultButton3', 'vbDefaultButton4', 'vbApplicationModal', 'vbSystemModal', 'vbOK', 'vbCancel', 'vbAbort', 'vbRetry', 'vbIgnore', 'vbYes', 'vbNo', + 'vbCr', 'VbCrLf', 'vbFormFeed', 'vbLf', 'vbNewLine', 'vbNullChar', 'vbNullString', 'vbTab', 'vbVerticalTab', 'vbUseDefault', 'vbTrue', 'vbFalse', + 'vbEmpty', 'vbNull', 'vbInteger', 'vbLong', 'vbSingle', 'vbDouble', 'vbCurrency', 'vbDate', 'vbString', 'vbObject', 'vbError', 'vbBoolean', 'vbVariant', 'vbDataObject', 'vbDecimal', 'vbByte', 'vbArray']; + //This list was from: http://msdn.microsoft.com/en-us/library/hkc375ea(v=vs.84).aspx + var builtinObjsWords = ['WScript', 'err', 'debug', 'RegExp']; + var knownProperties = ['description', 'firstindex', 'global', 'helpcontext', 'helpfile', 'ignorecase', 'length', 'number', 'pattern', 'source', 'value', 'count']; + var knownMethods = ['clear', 'execute', 'raise', 'replace', 'test', 'write', 'writeline', 'close', 'open', 'state', 'eof', 'update', 'addnew', 'end', 'createobject', 'quit']; + + var aspBuiltinObjsWords = ['server', 'response', 'request', 'session', 'application']; + var aspKnownProperties = ['buffer', 'cachecontrol', 'charset', 'contenttype', 'expires', 'expiresabsolute', 'isclientconnected', 'pics', 'status', //response + 'clientcertificate', 'cookies', 'form', 'querystring', 'servervariables', 'totalbytes', //request + 'contents', 'staticobjects', //application + 'codepage', 'lcid', 'sessionid', 'timeout', //session + 'scripttimeout']; //server + var aspKnownMethods = ['addheader', 'appendtolog', 'binarywrite', 'end', 'flush', 'redirect', //response + 'binaryread', //request + 'remove', 'removeall', 'lock', 'unlock', //application + 'abandon', //session + 'getlasterror', 'htmlencode', 'mappath', 'transfer', 'urlencode']; //server + + var knownWords = knownMethods.concat(knownProperties); + + builtinObjsWords = builtinObjsWords.concat(builtinConsts); + + if (conf.isASP){ + builtinObjsWords = builtinObjsWords.concat(aspBuiltinObjsWords); + knownWords = knownWords.concat(aspKnownMethods, aspKnownProperties); + }; + + var keywords = wordRegexp(commonkeywords); + var atoms = wordRegexp(atomWords); + var builtinFuncs = wordRegexp(builtinFuncsWords); + var builtinObjs = wordRegexp(builtinObjsWords); + var known = wordRegexp(knownWords); + var stringPrefixes = '"'; + + var opening = wordRegexp(openingKeywords); + var middle = wordRegexp(middleKeywords); + var closing = wordRegexp(endKeywords); + var doubleClosing = wordRegexp(['end']); + var doOpening = wordRegexp(['do']); + var noIndentWords = wordRegexp(['on error resume next', 'exit']); + var comment = wordRegexp(['rem']); + + + function indent(_stream, state) { + state.currentIndent++; + } + + function dedent(_stream, state) { + state.currentIndent--; + } + // tokenizers + function tokenBase(stream, state) { + if (stream.eatSpace()) { + return 'space'; + //return null; + } + + var ch = stream.peek(); + + // Handle Comments + if (ch === "'") { + stream.skipToEnd(); + return 'comment'; + } + if (stream.match(comment)){ + stream.skipToEnd(); + return 'comment'; + } + + + // Handle Number Literals + if (stream.match(/^((&H)|(&O))?[0-9\.]/i, false) && !stream.match(/^((&H)|(&O))?[0-9\.]+[a-z_]/i, false)) { + var floatLiteral = false; + // Floats + if (stream.match(/^\d*\.\d+/i)) { floatLiteral = true; } + else if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; } + else if (stream.match(/^\.\d+/)) { floatLiteral = true; } + + if (floatLiteral) { + // Float literals may be "imaginary" + stream.eat(/J/i); + return 'number'; + } + // Integers + var intLiteral = false; + // Hex + if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; } + // Octal + else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; } + // Decimal + else if (stream.match(/^[1-9]\d*F?/)) { + // Decimal literals may be "imaginary" + stream.eat(/J/i); + // TODO - Can you have imaginary longs? + intLiteral = true; + } + // Zero by itself with no other piece of number. + else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } + if (intLiteral) { + // Integer literals may be "long" + stream.eat(/L/i); + return 'number'; + } + } + + // Handle Strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenStringFactory(stream.current()); + return state.tokenize(stream, state); + } + + // Handle operators and Delimiters + if (stream.match(doubleOperators) + || stream.match(singleOperators) + || stream.match(wordOperators)) { + return 'operator'; + } + if (stream.match(singleDelimiters)) { + return null; + } + + if (stream.match(brakets)) { + return "bracket"; + } + + if (stream.match(noIndentWords)) { + state.doInCurrentLine = true; + + return 'keyword'; + } + + if (stream.match(doOpening)) { + indent(stream,state); + state.doInCurrentLine = true; + + return 'keyword'; + } + if (stream.match(opening)) { + if (! state.doInCurrentLine) + indent(stream,state); + else + state.doInCurrentLine = false; + + return 'keyword'; + } + if (stream.match(middle)) { + return 'keyword'; + } + + + if (stream.match(doubleClosing)) { + dedent(stream,state); + dedent(stream,state); + + return 'keyword'; + } + if (stream.match(closing)) { + if (! state.doInCurrentLine) + dedent(stream,state); + else + state.doInCurrentLine = false; + + return 'keyword'; + } + + if (stream.match(keywords)) { + return 'keyword'; + } + + if (stream.match(atoms)) { + return 'atom'; + } + + if (stream.match(known)) { + return 'variable-2'; + } + + if (stream.match(builtinFuncs)) { + return 'builtin'; + } + + if (stream.match(builtinObjs)){ + return 'variable-2'; + } + + if (stream.match(identifiers)) { + return 'variable'; + } + + // Handle non-detected items + stream.next(); + return ERRORCLASS; + } + + function tokenStringFactory(delimiter) { + var singleline = delimiter.length == 1; + var OUTCLASS = 'string'; + + return function(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"]/); + if (stream.match(delimiter)) { + state.tokenize = tokenBase; + return OUTCLASS; + } else { + stream.eat(/['"]/); + } + } + if (singleline) { + if (parserConf.singleLineStringErrors) { + return ERRORCLASS; + } else { + state.tokenize = tokenBase; + } + } + return OUTCLASS; + }; + } + + + function tokenLexer(stream, state) { + var style = state.tokenize(stream, state); + var current = stream.current(); + + // Handle '.' connected identifiers + if (current === '.') { + style = state.tokenize(stream, state); + + current = stream.current(); + if (style && (style.substr(0, 8) === 'variable' || style==='builtin' || style==='keyword')){//|| knownWords.indexOf(current.substring(1)) > -1) { + if (style === 'builtin' || style === 'keyword') style='variable'; + if (knownWords.indexOf(current.substr(1)) > -1) style='variable-2'; + + return style; + } else { + return ERRORCLASS; + } + } + + return style; + } + + var external = { + electricChars:"dDpPtTfFeE ", + startState: function() { + return { + tokenize: tokenBase, + lastToken: null, + currentIndent: 0, + nextLineIndent: 0, + doInCurrentLine: false, + ignoreKeyword: false + + + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + state.currentIndent += state.nextLineIndent; + state.nextLineIndent = 0; + state.doInCurrentLine = 0; + } + var style = tokenLexer(stream, state); + + state.lastToken = {style:style, content: stream.current()}; + + if (style==='space') style=null; + + return style; + }, + + indent: function(state, textAfter) { + var trueText = textAfter.replace(/^\s+|\s+$/g, '') ; + if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1); + if(state.currentIndent < 0) return 0; + return state.currentIndent * conf.indentUnit; + } + + }; + return external; +}); + +CodeMirror.defineMIME("text/vbscript", "vbscript"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/velocity/velocity.js b/docs/js/node_modules/codemirror/mode/velocity/velocity.js new file mode 100644 index 000000000..56caa671b --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/velocity/velocity.js @@ -0,0 +1,201 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("velocity", function() { + function parseWords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var keywords = parseWords("#end #else #break #stop #[[ #]] " + + "#{end} #{else} #{break} #{stop}"); + var functions = parseWords("#if #elseif #foreach #set #include #parse #macro #define #evaluate " + + "#{if} #{elseif} #{foreach} #{set} #{include} #{parse} #{macro} #{define} #{evaluate}"); + var specials = parseWords("$foreach.count $foreach.hasNext $foreach.first $foreach.last $foreach.topmost $foreach.parent.count $foreach.parent.hasNext $foreach.parent.first $foreach.parent.last $foreach.parent $velocityCount $!bodyContent $bodyContent"); + var isOperatorChar = /[+\-*&%=<>!?:\/|]/; + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + function tokenBase(stream, state) { + var beforeParams = state.beforeParams; + state.beforeParams = false; + var ch = stream.next(); + // start of unparsed string? + if ((ch == "'") && !state.inString && state.inParams) { + state.lastTokenWasBuiltin = false; + return chain(stream, state, tokenString(ch)); + } + // start of parsed string? + else if ((ch == '"')) { + state.lastTokenWasBuiltin = false; + if (state.inString) { + state.inString = false; + return "string"; + } + else if (state.inParams) + return chain(stream, state, tokenString(ch)); + } + // is it one of the special signs []{}().,;? Seperator? + else if (/[\[\]{}\(\),;\.]/.test(ch)) { + if (ch == "(" && beforeParams) + state.inParams = true; + else if (ch == ")") { + state.inParams = false; + state.lastTokenWasBuiltin = true; + } + return null; + } + // start of a number value? + else if (/\d/.test(ch)) { + state.lastTokenWasBuiltin = false; + stream.eatWhile(/[\w\.]/); + return "number"; + } + // multi line comment? + else if (ch == "#" && stream.eat("*")) { + state.lastTokenWasBuiltin = false; + return chain(stream, state, tokenComment); + } + // unparsed content? + else if (ch == "#" && stream.match(/ *\[ *\[/)) { + state.lastTokenWasBuiltin = false; + return chain(stream, state, tokenUnparsed); + } + // single line comment? + else if (ch == "#" && stream.eat("#")) { + state.lastTokenWasBuiltin = false; + stream.skipToEnd(); + return "comment"; + } + // variable? + else if (ch == "$") { + stream.eatWhile(/[\w\d\$_\.{}-]/); + // is it one of the specials? + if (specials && specials.propertyIsEnumerable(stream.current())) { + return "keyword"; + } + else { + state.lastTokenWasBuiltin = true; + state.beforeParams = true; + return "builtin"; + } + } + // is it a operator? + else if (isOperatorChar.test(ch)) { + state.lastTokenWasBuiltin = false; + stream.eatWhile(isOperatorChar); + return "operator"; + } + else { + // get the whole word + stream.eatWhile(/[\w\$_{}@]/); + var word = stream.current(); + // is it one of the listed keywords? + if (keywords && keywords.propertyIsEnumerable(word)) + return "keyword"; + // is it one of the listed functions? + if (functions && functions.propertyIsEnumerable(word) || + (stream.current().match(/^#@?[a-z0-9_]+ *$/i) && stream.peek()=="(") && + !(functions && functions.propertyIsEnumerable(word.toLowerCase()))) { + state.beforeParams = true; + state.lastTokenWasBuiltin = false; + return "keyword"; + } + if (state.inString) { + state.lastTokenWasBuiltin = false; + return "string"; + } + if (stream.pos > word.length && stream.string.charAt(stream.pos-word.length-1)=="." && state.lastTokenWasBuiltin) + return "builtin"; + // default: just a "word" + state.lastTokenWasBuiltin = false; + return null; + } + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if ((next == quote) && !escaped) { + end = true; + break; + } + if (quote=='"' && stream.peek() == '$' && !escaped) { + state.inString = true; + end = true; + break; + } + escaped = !escaped && next == "\\"; + } + if (end) state.tokenize = tokenBase; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "#" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function tokenUnparsed(stream, state) { + var maybeEnd = 0, ch; + while (ch = stream.next()) { + if (ch == "#" && maybeEnd == 2) { + state.tokenize = tokenBase; + break; + } + if (ch == "]") + maybeEnd++; + else if (ch != " ") + maybeEnd = 0; + } + return "meta"; + } + // Interface + + return { + startState: function() { + return { + tokenize: tokenBase, + beforeParams: false, + inParams: false, + inString: false, + lastTokenWasBuiltin: false + }; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + return state.tokenize(stream, state); + }, + blockCommentStart: "#*", + blockCommentEnd: "*#", + lineComment: "##", + fold: "velocity" + }; +}); + +CodeMirror.defineMIME("text/velocity", "velocity"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/verilog/verilog.js b/docs/js/node_modules/codemirror/mode/verilog/verilog.js new file mode 100644 index 000000000..2b6850659 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/verilog/verilog.js @@ -0,0 +1,675 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("verilog", function(config, parserConfig) { + + var indentUnit = config.indentUnit, + statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, + dontAlignCalls = parserConfig.dontAlignCalls, + noIndentKeywords = parserConfig.noIndentKeywords || [], + multiLineStrings = parserConfig.multiLineStrings, + hooks = parserConfig.hooks || {}; + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + /** + * Keywords from IEEE 1800-2012 + */ + var keywords = words( + "accept_on alias always always_comb always_ff always_latch and assert assign assume automatic before begin bind " + + "bins binsof bit break buf bufif0 bufif1 byte case casex casez cell chandle checker class clocking cmos config " + + "const constraint context continue cover covergroup coverpoint cross deassign default defparam design disable " + + "dist do edge else end endcase endchecker endclass endclocking endconfig endfunction endgenerate endgroup " + + "endinterface endmodule endpackage endprimitive endprogram endproperty endspecify endsequence endtable endtask " + + "enum event eventually expect export extends extern final first_match for force foreach forever fork forkjoin " + + "function generate genvar global highz0 highz1 if iff ifnone ignore_bins illegal_bins implements implies import " + + "incdir include initial inout input inside instance int integer interconnect interface intersect join join_any " + + "join_none large let liblist library local localparam logic longint macromodule matches medium modport module " + + "nand negedge nettype new nexttime nmos nor noshowcancelled not notif0 notif1 null or output package packed " + + "parameter pmos posedge primitive priority program property protected pull0 pull1 pulldown pullup " + + "pulsestyle_ondetect pulsestyle_onevent pure rand randc randcase randsequence rcmos real realtime ref reg " + + "reject_on release repeat restrict return rnmos rpmos rtran rtranif0 rtranif1 s_always s_eventually s_nexttime " + + "s_until s_until_with scalared sequence shortint shortreal showcancelled signed small soft solve specify " + + "specparam static string strong strong0 strong1 struct super supply0 supply1 sync_accept_on sync_reject_on " + + "table tagged task this throughout time timeprecision timeunit tran tranif0 tranif1 tri tri0 tri1 triand trior " + + "trireg type typedef union unique unique0 unsigned until until_with untyped use uwire var vectored virtual void " + + "wait wait_order wand weak weak0 weak1 while wildcard wire with within wor xnor xor"); + + /** Operators from IEEE 1800-2012 + unary_operator ::= + + | - | ! | ~ | & | ~& | | | ~| | ^ | ~^ | ^~ + binary_operator ::= + + | - | * | / | % | == | != | === | !== | ==? | !=? | && | || | ** + | < | <= | > | >= | & | | | ^ | ^~ | ~^ | >> | << | >>> | <<< + | -> | <-> + inc_or_dec_operator ::= ++ | -- + unary_module_path_operator ::= + ! | ~ | & | ~& | | | ~| | ^ | ~^ | ^~ + binary_module_path_operator ::= + == | != | && | || | & | | | ^ | ^~ | ~^ + */ + var isOperatorChar = /[\+\-\*\/!~&|^%=?:]/; + var isBracketChar = /[\[\]{}()]/; + + var unsignedNumber = /\d[0-9_]*/; + var decimalLiteral = /\d*\s*'s?d\s*\d[0-9_]*/i; + var binaryLiteral = /\d*\s*'s?b\s*[xz01][xz01_]*/i; + var octLiteral = /\d*\s*'s?o\s*[xz0-7][xz0-7_]*/i; + var hexLiteral = /\d*\s*'s?h\s*[0-9a-fxz?][0-9a-fxz?_]*/i; + var realLiteral = /(\d[\d_]*(\.\d[\d_]*)?E-?[\d_]+)|(\d[\d_]*\.\d[\d_]*)/i; + + var closingBracketOrWord = /^((\w+)|[)}\]])/; + var closingBracket = /[)}\]]/; + + var curPunc; + var curKeyword; + + // Block openings which are closed by a matching keyword in the form of ("end" + keyword) + // E.g. "task" => "endtask" + var blockKeywords = words( + "case checker class clocking config function generate interface module package " + + "primitive program property specify sequence table task" + ); + + // Opening/closing pairs + var openClose = {}; + for (var keyword in blockKeywords) { + openClose[keyword] = "end" + keyword; + } + openClose["begin"] = "end"; + openClose["casex"] = "endcase"; + openClose["casez"] = "endcase"; + openClose["do" ] = "while"; + openClose["fork" ] = "join;join_any;join_none"; + openClose["covergroup"] = "endgroup"; + + for (var i in noIndentKeywords) { + var keyword = noIndentKeywords[i]; + if (openClose[keyword]) { + openClose[keyword] = undefined; + } + } + + // Keywords which open statements that are ended with a semi-colon + var statementKeywords = words("always always_comb always_ff always_latch assert assign assume else export for foreach forever if import initial repeat while"); + + function tokenBase(stream, state) { + var ch = stream.peek(), style; + if (hooks[ch] && (style = hooks[ch](stream, state)) != false) return style; + if (hooks.tokenBase && (style = hooks.tokenBase(stream, state)) != false) + return style; + + if (/[,;:\.]/.test(ch)) { + curPunc = stream.next(); + return null; + } + if (isBracketChar.test(ch)) { + curPunc = stream.next(); + return "bracket"; + } + // Macros (tick-defines) + if (ch == '`') { + stream.next(); + if (stream.eatWhile(/[\w\$_]/)) { + return "def"; + } else { + return null; + } + } + // System calls + if (ch == '$') { + stream.next(); + if (stream.eatWhile(/[\w\$_]/)) { + return "meta"; + } else { + return null; + } + } + // Time literals + if (ch == '#') { + stream.next(); + stream.eatWhile(/[\d_.]/); + return "def"; + } + // Strings + if (ch == '"') { + stream.next(); + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + // Comments + if (ch == "/") { + stream.next(); + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + stream.backUp(1); + } + + // Numeric literals + if (stream.match(realLiteral) || + stream.match(decimalLiteral) || + stream.match(binaryLiteral) || + stream.match(octLiteral) || + stream.match(hexLiteral) || + stream.match(unsignedNumber) || + stream.match(realLiteral)) { + return "number"; + } + + // Operators + if (stream.eatWhile(isOperatorChar)) { + return "meta"; + } + + // Keywords / plain variables + if (stream.eatWhile(/[\w\$_]/)) { + var cur = stream.current(); + if (keywords[cur]) { + if (openClose[cur]) { + curPunc = "newblock"; + } + if (statementKeywords[cur]) { + curPunc = "newstatement"; + } + curKeyword = cur; + return "keyword"; + } + return "variable"; + } + + stream.next(); + return null; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = tokenBase; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + var indent = state.indented; + var c = new Context(indent, col, type, null, state.context); + return state.context = c; + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") { + state.indented = state.context.indented; + } + return state.context = state.context.prev; + } + + function isClosing(text, contextClosing) { + if (text == contextClosing) { + return true; + } else { + // contextClosing may be multiple keywords separated by ; + var closingKeywords = contextClosing.split(";"); + for (var i in closingKeywords) { + if (text == closingKeywords[i]) { + return true; + } + } + return false; + } + } + + function buildElectricInputRegEx() { + // Reindentation should occur on any bracket char: {}()[] + // or on a match of any of the block closing keywords, at + // the end of a line + var allClosings = []; + for (var i in openClose) { + if (openClose[i]) { + var closings = openClose[i].split(";"); + for (var j in closings) { + allClosings.push(closings[j]); + } + } + } + var re = new RegExp("[{}()\\[\\]]|(" + allClosings.join("|") + ")$"); + return re; + } + + // Interface + return { + + // Regex to force current line to reindent + electricInput: buildElectricInputRegEx(), + + startState: function(basecolumn) { + var state = { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + if (hooks.startState) hooks.startState(state); + return state; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (hooks.token) { + // Call hook, with an optional return value of a style to override verilog styling. + var style = hooks.token(stream, state); + if (style !== undefined) { + return style; + } + } + if (stream.eatSpace()) return null; + curPunc = null; + curKeyword = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment" || style == "meta" || style == "variable") return style; + if (ctx.align == null) ctx.align = true; + + if (curPunc == ctx.type) { + popContext(state); + } else if ((curPunc == ";" && ctx.type == "statement") || + (ctx.type && isClosing(curKeyword, ctx.type))) { + ctx = popContext(state); + while (ctx && ctx.type == "statement") ctx = popContext(state); + } else if (curPunc == "{") { + pushContext(state, stream.column(), "}"); + } else if (curPunc == "[") { + pushContext(state, stream.column(), "]"); + } else if (curPunc == "(") { + pushContext(state, stream.column(), ")"); + } else if (ctx && ctx.type == "endcase" && curPunc == ":") { + pushContext(state, stream.column(), "statement"); + } else if (curPunc == "newstatement") { + pushContext(state, stream.column(), "statement"); + } else if (curPunc == "newblock") { + if (curKeyword == "function" && ctx && (ctx.type == "statement" || ctx.type == "endgroup")) { + // The 'function' keyword can appear in some other contexts where it actually does not + // indicate a function (import/export DPI and covergroup definitions). + // Do nothing in this case + } else if (curKeyword == "task" && ctx && ctx.type == "statement") { + // Same thing for task + } else { + var close = openClose[curKeyword]; + pushContext(state, stream.column(), close); + } + } + + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; + if (hooks.indent) { + var fromHook = hooks.indent(state); + if (fromHook >= 0) return fromHook; + } + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; + var closing = false; + var possibleClosing = textAfter.match(closingBracketOrWord); + if (possibleClosing) + closing = isClosing(possibleClosing[0], ctx.type); + if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); + else if (closingBracket.test(ctx.type) && ctx.align && !dontAlignCalls) return ctx.column + (closing ? 0 : 1); + else if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit; + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//" + }; +}); + + CodeMirror.defineMIME("text/x-verilog", { + name: "verilog" + }); + + CodeMirror.defineMIME("text/x-systemverilog", { + name: "verilog" + }); + + + + // TL-Verilog mode. + // See tl-x.org for language spec. + // See the mode in action at makerchip.com. + // Contact: steve.hoover@redwoodeda.com + + // TLV Identifier prefixes. + // Note that sign is not treated separately, so "+/-" versions of numeric identifiers + // are included. + var tlvIdentifierStyle = { + "|": "link", + ">": "property", // Should condition this off for > TLV 1c. + "$": "variable", + "$$": "variable", + "?$": "qualifier", + "?*": "qualifier", + "-": "hr", + "/": "property", + "/-": "property", + "@": "variable-3", + "@-": "variable-3", + "@++": "variable-3", + "@+=": "variable-3", + "@+=-": "variable-3", + "@--": "variable-3", + "@-=": "variable-3", + "%+": "tag", + "%-": "tag", + "%": "tag", + ">>": "tag", + "<<": "tag", + "<>": "tag", + "#": "tag", // Need to choose a style for this. + "^": "attribute", + "^^": "attribute", + "^!": "attribute", + "*": "variable-2", + "**": "variable-2", + "\\": "keyword", + "\"": "comment" + }; + + // Lines starting with these characters define scope (result in indentation). + var tlvScopePrefixChars = { + "/": "beh-hier", + ">": "beh-hier", + "-": "phys-hier", + "|": "pipe", + "?": "when", + "@": "stage", + "\\": "keyword" + }; + var tlvIndentUnit = 3; + var tlvTrackStatements = false; + var tlvIdentMatch = /^([~!@#\$%\^&\*-\+=\?\/\\\|'"<>]+)([\d\w_]*)/; // Matches an identifiere. + // Note that ':' is excluded, because of it's use in [:]. + var tlvFirstLevelIndentMatch = /^[! ] /; + var tlvLineIndentationMatch = /^[! ] */; + var tlvCommentMatch = /^\/[\/\*]/; + + + // Returns a style specific to the scope at the given indentation column. + // Type is one of: "indent", "scope-ident", "before-scope-ident". + function tlvScopeStyle(state, indentation, type) { + // Begin scope. + var depth = indentation / tlvIndentUnit; // TODO: Pass this in instead. + return "tlv-" + state.tlvIndentationStyle[depth] + "-" + type; + } + + // Return true if the next thing in the stream is an identifier with a mnemonic. + function tlvIdentNext(stream) { + var match; + return (match = stream.match(tlvIdentMatch, false)) && match[2].length > 0; + } + + CodeMirror.defineMIME("text/x-tlv", { + name: "verilog", + + hooks: { + + electricInput: false, + + + // Return undefined for verilog tokenizing, or style for TLV token (null not used). + // Standard CM styles are used for most formatting, but some TL-Verilog-specific highlighting + // can be enabled with the definition of cm-tlv-* styles, including highlighting for: + // - M4 tokens + // - TLV scope indentation + // - Statement delimitation (enabled by tlvTrackStatements) + token: function(stream, state) { + var style = undefined; + var match; // Return value of pattern matches. + + // Set highlighting mode based on code region (TLV or SV). + if (stream.sol() && ! state.tlvInBlockComment) { + // Process region. + if (stream.peek() == '\\') { + style = "def"; + stream.skipToEnd(); + if (stream.string.match(/\\SV/)) { + state.tlvCodeActive = false; + } else if (stream.string.match(/\\TLV/)){ + state.tlvCodeActive = true; + } + } + // Correct indentation in the face of a line prefix char. + if (state.tlvCodeActive && stream.pos == 0 && + (state.indented == 0) && (match = stream.match(tlvLineIndentationMatch, false))) { + state.indented = match[0].length; + } + + // Compute indentation state: + // o Auto indentation on next line + // o Indentation scope styles + var indented = state.indented; + var depth = indented / tlvIndentUnit; + if (depth <= state.tlvIndentationStyle.length) { + // not deeper than current scope + + var blankline = stream.string.length == indented; + var chPos = depth * tlvIndentUnit; + if (chPos < stream.string.length) { + var bodyString = stream.string.slice(chPos); + var ch = bodyString[0]; + if (tlvScopePrefixChars[ch] && ((match = bodyString.match(tlvIdentMatch)) && + tlvIdentifierStyle[match[1]])) { + // This line begins scope. + // Next line gets indented one level. + indented += tlvIndentUnit; + // Style the next level of indentation (except non-region keyword identifiers, + // which are statements themselves) + if (!(ch == "\\" && chPos > 0)) { + state.tlvIndentationStyle[depth] = tlvScopePrefixChars[ch]; + if (tlvTrackStatements) {state.statementComment = false;} + depth++; + } + } + } + // Clear out deeper indentation levels unless line is blank. + if (!blankline) { + while (state.tlvIndentationStyle.length > depth) { + state.tlvIndentationStyle.pop(); + } + } + } + // Set next level of indentation. + state.tlvNextIndent = indented; + } + + if (state.tlvCodeActive) { + // Highlight as TLV. + + var beginStatement = false; + if (tlvTrackStatements) { + // This starts a statement if the position is at the scope level + // and we're not within a statement leading comment. + beginStatement = + (stream.peek() != " ") && // not a space + (style === undefined) && // not a region identifier + !state.tlvInBlockComment && // not in block comment + //!stream.match(tlvCommentMatch, false) && // not comment start + (stream.column() == state.tlvIndentationStyle.length * tlvIndentUnit); // at scope level + if (beginStatement) { + if (state.statementComment) { + // statement already started by comment + beginStatement = false; + } + state.statementComment = + stream.match(tlvCommentMatch, false); // comment start + } + } + + var match; + if (style !== undefined) { + // Region line. + style += " " + tlvScopeStyle(state, 0, "scope-ident") + } else if (((stream.pos / tlvIndentUnit) < state.tlvIndentationStyle.length) && + (match = stream.match(stream.sol() ? tlvFirstLevelIndentMatch : /^ /))) { + // Indentation + style = // make this style distinct from the previous one to prevent + // codemirror from combining spans + "tlv-indent-" + (((stream.pos % 2) == 0) ? "even" : "odd") + + // and style it + " " + tlvScopeStyle(state, stream.pos - tlvIndentUnit, "indent"); + // Style the line prefix character. + if (match[0].charAt(0) == "!") { + style += " tlv-alert-line-prefix"; + } + // Place a class before a scope identifier. + if (tlvIdentNext(stream)) { + style += " " + tlvScopeStyle(state, stream.pos, "before-scope-ident"); + } + } else if (state.tlvInBlockComment) { + // In a block comment. + if (stream.match(/^.*?\*\//)) { + // Exit block comment. + state.tlvInBlockComment = false; + if (tlvTrackStatements && !stream.eol()) { + // Anything after comment is assumed to be real statement content. + state.statementComment = false; + } + } else { + stream.skipToEnd(); + } + style = "comment"; + } else if ((match = stream.match(tlvCommentMatch)) && !state.tlvInBlockComment) { + // Start comment. + if (match[0] == "//") { + // Line comment. + stream.skipToEnd(); + } else { + // Block comment. + state.tlvInBlockComment = true; + } + style = "comment"; + } else if (match = stream.match(tlvIdentMatch)) { + // looks like an identifier (or identifier prefix) + var prefix = match[1]; + var mnemonic = match[2]; + if (// is identifier prefix + tlvIdentifierStyle.hasOwnProperty(prefix) && + // has mnemonic or we're at the end of the line (maybe it hasn't been typed yet) + (mnemonic.length > 0 || stream.eol())) { + style = tlvIdentifierStyle[prefix]; + if (stream.column() == state.indented) { + // Begin scope. + style += " " + tlvScopeStyle(state, stream.column(), "scope-ident") + } + } else { + // Just swallow one character and try again. + // This enables subsequent identifier match with preceding symbol character, which + // is legal within a statement. (Eg, !$reset). It also enables detection of + // comment start with preceding symbols. + stream.backUp(stream.current().length - 1); + style = "tlv-default"; + } + } else if (stream.match(/^\t+/)) { + // Highlight tabs, which are illegal. + style = "tlv-tab"; + } else if (stream.match(/^[\[\]{}\(\);\:]+/)) { + // [:], (), {}, ;. + style = "meta"; + } else if (match = stream.match(/^[mM]4([\+_])?[\w\d_]*/)) { + // m4 pre proc + style = (match[1] == "+") ? "tlv-m4-plus" : "tlv-m4"; + } else if (stream.match(/^ +/)){ + // Skip over spaces. + if (stream.eol()) { + // Trailing spaces. + style = "error"; + } else { + // Non-trailing spaces. + style = "tlv-default"; + } + } else if (stream.match(/^[\w\d_]+/)) { + // alpha-numeric token. + style = "number"; + } else { + // Eat the next char w/ no formatting. + stream.next(); + style = "tlv-default"; + } + if (beginStatement) { + style += " tlv-statement"; + } + } else { + if (stream.match(/^[mM]4([\w\d_]*)/)) { + // m4 pre proc + style = "tlv-m4"; + } + } + return style; + }, + + indent: function(state) { + return (state.tlvCodeActive == true) ? state.tlvNextIndent : -1; + }, + + startState: function(state) { + state.tlvIndentationStyle = []; // Styles to use for each level of indentation. + state.tlvCodeActive = true; // True when we're in a TLV region (and at beginning of file). + state.tlvNextIndent = -1; // The number of spaces to autoindent the next line if tlvCodeActive. + state.tlvInBlockComment = false; // True inside /**/ comment. + if (tlvTrackStatements) { + state.statementComment = false; // True inside a statement's header comment. + } + } + + } + }); +}); diff --git a/docs/js/node_modules/codemirror/mode/vhdl/vhdl.js b/docs/js/node_modules/codemirror/mode/vhdl/vhdl.js new file mode 100644 index 000000000..133e67a26 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/vhdl/vhdl.js @@ -0,0 +1,189 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// Originally written by Alf Nielsen, re-written by Michael Zhou +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +function words(str) { + var obj = {}, words = str.split(","); + for (var i = 0; i < words.length; ++i) { + var allCaps = words[i].toUpperCase(); + var firstCap = words[i].charAt(0).toUpperCase() + words[i].slice(1); + obj[words[i]] = true; + obj[allCaps] = true; + obj[firstCap] = true; + } + return obj; +} + +function metaHook(stream) { + stream.eatWhile(/[\w\$_]/); + return "meta"; +} + +CodeMirror.defineMode("vhdl", function(config, parserConfig) { + var indentUnit = config.indentUnit, + atoms = parserConfig.atoms || words("null"), + hooks = parserConfig.hooks || {"`": metaHook, "$": metaHook}, + multiLineStrings = parserConfig.multiLineStrings; + + var keywords = words("abs,access,after,alias,all,and,architecture,array,assert,attribute,begin,block," + + "body,buffer,bus,case,component,configuration,constant,disconnect,downto,else,elsif,end,end block,end case," + + "end component,end for,end generate,end if,end loop,end process,end record,end units,entity,exit,file,for," + + "function,generate,generic,generic map,group,guarded,if,impure,in,inertial,inout,is,label,library,linkage," + + "literal,loop,map,mod,nand,new,next,nor,null,of,on,open,or,others,out,package,package body,port,port map," + + "postponed,procedure,process,pure,range,record,register,reject,rem,report,return,rol,ror,select,severity,signal," + + "sla,sll,sra,srl,subtype,then,to,transport,type,unaffected,units,until,use,variable,wait,when,while,with,xnor,xor"); + + var blockKeywords = words("architecture,entity,begin,case,port,else,elsif,end,for,function,if"); + + var isOperatorChar = /[&|~>?]/; +var integers = /^-?([1-9][0-9]*|0[Xx][0-9A-Fa-f]+|0[0-7]*)/; +var floats = /^-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+)/; +var identifiers = /^_?[A-Za-z][0-9A-Z_a-z-]*/; +var identifiersEnd = /^_?[A-Za-z][0-9A-Z_a-z-]*(?=\s*;)/; +var strings = /^"[^"]*"/; +var multilineComments = /^\/\*.*?\*\//; +var multilineCommentsStart = /^\/\*.*/; +var multilineCommentsEnd = /^.*?\*\//; + +function readToken(stream, state) { + // whitespace + if (stream.eatSpace()) return null; + + // comment + if (state.inComment) { + if (stream.match(multilineCommentsEnd)) { + state.inComment = false; + return "comment"; + } + stream.skipToEnd(); + return "comment"; + } + if (stream.match("//")) { + stream.skipToEnd(); + return "comment"; + } + if (stream.match(multilineComments)) return "comment"; + if (stream.match(multilineCommentsStart)) { + state.inComment = true; + return "comment"; + } + + // integer and float + if (stream.match(/^-?[0-9\.]/, false)) { + if (stream.match(integers) || stream.match(floats)) return "number"; + } + + // string + if (stream.match(strings)) return "string"; + + // identifier + if (state.startDef && stream.match(identifiers)) return "def"; + + if (state.endDef && stream.match(identifiersEnd)) { + state.endDef = false; + return "def"; + } + + if (stream.match(keywords)) return "keyword"; + + if (stream.match(types)) { + var lastToken = state.lastToken; + var nextToken = (stream.match(/^\s*(.+?)\b/, false) || [])[1]; + + if (lastToken === ":" || lastToken === "implements" || + nextToken === "implements" || nextToken === "=") { + // Used as identifier + return "builtin"; + } else { + // Used as type + return "variable-3"; + } + } + + if (stream.match(builtins)) return "builtin"; + if (stream.match(atoms)) return "atom"; + if (stream.match(identifiers)) return "variable"; + + // other + if (stream.match(singleOperators)) return "operator"; + + // unrecognized + stream.next(); + return null; +}; + +CodeMirror.defineMode("webidl", function() { + return { + startState: function() { + return { + // Is in multiline comment + inComment: false, + // Last non-whitespace, matched token + lastToken: "", + // Next token is a definition + startDef: false, + // Last token of the statement is a definition + endDef: false + }; + }, + token: function(stream, state) { + var style = readToken(stream, state); + + if (style) { + var cur = stream.current(); + state.lastToken = cur; + if (style === "keyword") { + state.startDef = startDefs.test(cur); + state.endDef = state.endDef || endDefs.test(cur); + } else { + state.startDef = false; + } + } + + return style; + } + }; +}); + +CodeMirror.defineMIME("text/x-webidl", "webidl"); +}); diff --git a/docs/js/node_modules/codemirror/mode/xml/xml.js b/docs/js/node_modules/codemirror/mode/xml/xml.js new file mode 100644 index 000000000..73c6e0e0d --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/xml/xml.js @@ -0,0 +1,413 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +var htmlConfig = { + autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, + 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, + 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, + 'track': true, 'wbr': true, 'menuitem': true}, + implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, + 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, + 'th': true, 'tr': true}, + contextGrabbers: { + 'dd': {'dd': true, 'dt': true}, + 'dt': {'dd': true, 'dt': true}, + 'li': {'li': true}, + 'option': {'option': true, 'optgroup': true}, + 'optgroup': {'optgroup': true}, + 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, + 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, + 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, + 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, + 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, + 'rp': {'rp': true, 'rt': true}, + 'rt': {'rp': true, 'rt': true}, + 'tbody': {'tbody': true, 'tfoot': true}, + 'td': {'td': true, 'th': true}, + 'tfoot': {'tbody': true}, + 'th': {'td': true, 'th': true}, + 'thead': {'tbody': true, 'tfoot': true}, + 'tr': {'tr': true} + }, + doNotIndent: {"pre": true}, + allowUnquoted: true, + allowMissing: true, + caseFold: true +} + +var xmlConfig = { + autoSelfClosers: {}, + implicitlyClosed: {}, + contextGrabbers: {}, + doNotIndent: {}, + allowUnquoted: false, + allowMissing: false, + allowMissingTagName: false, + caseFold: false +} + +CodeMirror.defineMode("xml", function(editorConf, config_) { + var indentUnit = editorConf.indentUnit + var config = {} + var defaults = config_.htmlMode ? htmlConfig : xmlConfig + for (var prop in defaults) config[prop] = defaults[prop] + for (var prop in config_) config[prop] = config_[prop] + + // Return variables for tokenizers + var type, setStyle; + + function inText(stream, state) { + function chain(parser) { + state.tokenize = parser; + return parser(stream, state); + } + + var ch = stream.next(); + if (ch == "<") { + if (stream.eat("!")) { + if (stream.eat("[")) { + if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); + else return null; + } else if (stream.match("--")) { + return chain(inBlock("comment", "-->")); + } else if (stream.match("DOCTYPE", true, true)) { + stream.eatWhile(/[\w\._\-]/); + return chain(doctype(1)); + } else { + return null; + } + } else if (stream.eat("?")) { + stream.eatWhile(/[\w\._\-]/); + state.tokenize = inBlock("meta", "?>"); + return "meta"; + } else { + type = stream.eat("/") ? "closeTag" : "openTag"; + state.tokenize = inTag; + return "tag bracket"; + } + } else if (ch == "&") { + var ok; + if (stream.eat("#")) { + if (stream.eat("x")) { + ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); + } else { + ok = stream.eatWhile(/[\d]/) && stream.eat(";"); + } + } else { + ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); + } + return ok ? "atom" : "error"; + } else { + stream.eatWhile(/[^&<]/); + return null; + } + } + inText.isInText = true; + + function inTag(stream, state) { + var ch = stream.next(); + if (ch == ">" || (ch == "/" && stream.eat(">"))) { + state.tokenize = inText; + type = ch == ">" ? "endTag" : "selfcloseTag"; + return "tag bracket"; + } else if (ch == "=") { + type = "equals"; + return null; + } else if (ch == "<") { + state.tokenize = inText; + state.state = baseState; + state.tagName = state.tagStart = null; + var next = state.tokenize(stream, state); + return next ? next + " tag error" : "tag error"; + } else if (/[\'\"]/.test(ch)) { + state.tokenize = inAttribute(ch); + state.stringStartCol = stream.column(); + return state.tokenize(stream, state); + } else { + stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/); + return "word"; + } + } + + function inAttribute(quote) { + var closure = function(stream, state) { + while (!stream.eol()) { + if (stream.next() == quote) { + state.tokenize = inTag; + break; + } + } + return "string"; + }; + closure.isInAttribute = true; + return closure; + } + + function inBlock(style, terminator) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.match(terminator)) { + state.tokenize = inText; + break; + } + stream.next(); + } + return style; + } + } + + function doctype(depth) { + return function(stream, state) { + var ch; + while ((ch = stream.next()) != null) { + if (ch == "<") { + state.tokenize = doctype(depth + 1); + return state.tokenize(stream, state); + } else if (ch == ">") { + if (depth == 1) { + state.tokenize = inText; + break; + } else { + state.tokenize = doctype(depth - 1); + return state.tokenize(stream, state); + } + } + } + return "meta"; + }; + } + + function Context(state, tagName, startOfLine) { + this.prev = state.context; + this.tagName = tagName; + this.indent = state.indented; + this.startOfLine = startOfLine; + if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) + this.noIndent = true; + } + function popContext(state) { + if (state.context) state.context = state.context.prev; + } + function maybePopContext(state, nextTagName) { + var parentTagName; + while (true) { + if (!state.context) { + return; + } + parentTagName = state.context.tagName; + if (!config.contextGrabbers.hasOwnProperty(parentTagName) || + !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { + return; + } + popContext(state); + } + } + + function baseState(type, stream, state) { + if (type == "openTag") { + state.tagStart = stream.column(); + return tagNameState; + } else if (type == "closeTag") { + return closeTagNameState; + } else { + return baseState; + } + } + function tagNameState(type, stream, state) { + if (type == "word") { + state.tagName = stream.current(); + setStyle = "tag"; + return attrState; + } else if (config.allowMissingTagName && type == "endTag") { + setStyle = "tag bracket"; + return attrState(type, stream, state); + } else { + setStyle = "error"; + return tagNameState; + } + } + function closeTagNameState(type, stream, state) { + if (type == "word") { + var tagName = stream.current(); + if (state.context && state.context.tagName != tagName && + config.implicitlyClosed.hasOwnProperty(state.context.tagName)) + popContext(state); + if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) { + setStyle = "tag"; + return closeState; + } else { + setStyle = "tag error"; + return closeStateErr; + } + } else if (config.allowMissingTagName && type == "endTag") { + setStyle = "tag bracket"; + return closeState(type, stream, state); + } else { + setStyle = "error"; + return closeStateErr; + } + } + + function closeState(type, _stream, state) { + if (type != "endTag") { + setStyle = "error"; + return closeState; + } + popContext(state); + return baseState; + } + function closeStateErr(type, stream, state) { + setStyle = "error"; + return closeState(type, stream, state); + } + + function attrState(type, _stream, state) { + if (type == "word") { + setStyle = "attribute"; + return attrEqState; + } else if (type == "endTag" || type == "selfcloseTag") { + var tagName = state.tagName, tagStart = state.tagStart; + state.tagName = state.tagStart = null; + if (type == "selfcloseTag" || + config.autoSelfClosers.hasOwnProperty(tagName)) { + maybePopContext(state, tagName); + } else { + maybePopContext(state, tagName); + state.context = new Context(state, tagName, tagStart == state.indented); + } + return baseState; + } + setStyle = "error"; + return attrState; + } + function attrEqState(type, stream, state) { + if (type == "equals") return attrValueState; + if (!config.allowMissing) setStyle = "error"; + return attrState(type, stream, state); + } + function attrValueState(type, stream, state) { + if (type == "string") return attrContinuedState; + if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;} + setStyle = "error"; + return attrState(type, stream, state); + } + function attrContinuedState(type, stream, state) { + if (type == "string") return attrContinuedState; + return attrState(type, stream, state); + } + + return { + startState: function(baseIndent) { + var state = {tokenize: inText, + state: baseState, + indented: baseIndent || 0, + tagName: null, tagStart: null, + context: null} + if (baseIndent != null) state.baseIndent = baseIndent + return state + }, + + token: function(stream, state) { + if (!state.tagName && stream.sol()) + state.indented = stream.indentation(); + + if (stream.eatSpace()) return null; + type = null; + var style = state.tokenize(stream, state); + if ((style || type) && style != "comment") { + setStyle = null; + state.state = state.state(type || style, stream, state); + if (setStyle) + style = setStyle == "error" ? style + " error" : setStyle; + } + return style; + }, + + indent: function(state, textAfter, fullLine) { + var context = state.context; + // Indent multi-line strings (e.g. css). + if (state.tokenize.isInAttribute) { + if (state.tagStart == state.indented) + return state.stringStartCol + 1; + else + return state.indented + indentUnit; + } + if (context && context.noIndent) return CodeMirror.Pass; + if (state.tokenize != inTag && state.tokenize != inText) + return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; + // Indent the starts of attribute names. + if (state.tagName) { + if (config.multilineTagIndentPastTag !== false) + return state.tagStart + state.tagName.length + 2; + else + return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1); + } + if (config.alignCDATA && /$/, + blockCommentStart: "", + + configuration: config.htmlMode ? "html" : "xml", + helperType: config.htmlMode ? "html" : "xml", + + skipAttribute: function(state) { + if (state.state == attrValueState) + state.state = attrState + }, + + xmlCurrentTag: function(state) { + return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null + }, + + xmlCurrentContext: function(state) { + var context = [] + for (var cx = state.context; cx; cx = cx.prev) + if (cx.tagName) context.push(cx.tagName) + return context.reverse() + } + }; +}); + +CodeMirror.defineMIME("text/xml", "xml"); +CodeMirror.defineMIME("application/xml", "xml"); +if (!CodeMirror.mimeModes.hasOwnProperty("text/html")) + CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true}); + +}); diff --git a/docs/js/node_modules/codemirror/mode/xquery/xquery.js b/docs/js/node_modules/codemirror/mode/xquery/xquery.js new file mode 100644 index 000000000..395b6a701 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/xquery/xquery.js @@ -0,0 +1,448 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("xquery", function() { + + // The keywords object is set to the result of this self executing + // function. Each keyword is a property of the keywords object whose + // value is {type: atype, style: astyle} + var keywords = function(){ + // convenience functions used to build keywords object + function kw(type) {return {type: type, style: "keyword"};} + var operator = kw("operator") + , atom = {type: "atom", style: "atom"} + , punctuation = {type: "punctuation", style: null} + , qualifier = {type: "axis_specifier", style: "qualifier"}; + + // kwObj is what is return from this function at the end + var kwObj = { + ',': punctuation + }; + + // a list of 'basic' keywords. For each add a property to kwObj with the value of + // {type: basic[i], style: "keyword"} e.g. 'after' --> {type: "after", style: "keyword"} + var basic = ['after', 'all', 'allowing', 'ancestor', 'ancestor-or-self', 'any', 'array', 'as', + 'ascending', 'at', 'attribute', 'base-uri', 'before', 'boundary-space', 'by', 'case', 'cast', + 'castable', 'catch', 'child', 'collation', 'comment', 'construction', 'contains', 'content', + 'context', 'copy', 'copy-namespaces', 'count', 'decimal-format', 'declare', 'default', 'delete', + 'descendant', 'descendant-or-self', 'descending', 'diacritics', 'different', 'distance', + 'document', 'document-node', 'element', 'else', 'empty', 'empty-sequence', 'encoding', 'end', + 'entire', 'every', 'exactly', 'except', 'external', 'first', 'following', 'following-sibling', + 'for', 'from', 'ftand', 'ftnot', 'ft-option', 'ftor', 'function', 'fuzzy', 'greatest', 'group', + 'if', 'import', 'in', 'inherit', 'insensitive', 'insert', 'instance', 'intersect', 'into', + 'invoke', 'is', 'item', 'language', 'last', 'lax', 'least', 'let', 'levels', 'lowercase', 'map', + 'modify', 'module', 'most', 'namespace', 'next', 'no', 'node', 'nodes', 'no-inherit', + 'no-preserve', 'not', 'occurs', 'of', 'only', 'option', 'order', 'ordered', 'ordering', + 'paragraph', 'paragraphs', 'parent', 'phrase', 'preceding', 'preceding-sibling', 'preserve', + 'previous', 'processing-instruction', 'relationship', 'rename', 'replace', 'return', + 'revalidation', 'same', 'satisfies', 'schema', 'schema-attribute', 'schema-element', 'score', + 'self', 'sensitive', 'sentence', 'sentences', 'sequence', 'skip', 'sliding', 'some', 'stable', + 'start', 'stemming', 'stop', 'strict', 'strip', 'switch', 'text', 'then', 'thesaurus', 'times', + 'to', 'transform', 'treat', 'try', 'tumbling', 'type', 'typeswitch', 'union', 'unordered', + 'update', 'updating', 'uppercase', 'using', 'validate', 'value', 'variable', 'version', + 'weight', 'when', 'where', 'wildcards', 'window', 'with', 'without', 'word', 'words', 'xquery']; + for(var i=0, l=basic.length; i < l; i++) { kwObj[basic[i]] = kw(basic[i]);}; + + // a list of types. For each add a property to kwObj with the value of + // {type: "atom", style: "atom"} + var types = ['xs:anyAtomicType', 'xs:anySimpleType', 'xs:anyType', 'xs:anyURI', + 'xs:base64Binary', 'xs:boolean', 'xs:byte', 'xs:date', 'xs:dateTime', 'xs:dateTimeStamp', + 'xs:dayTimeDuration', 'xs:decimal', 'xs:double', 'xs:duration', 'xs:ENTITIES', 'xs:ENTITY', + 'xs:float', 'xs:gDay', 'xs:gMonth', 'xs:gMonthDay', 'xs:gYear', 'xs:gYearMonth', 'xs:hexBinary', + 'xs:ID', 'xs:IDREF', 'xs:IDREFS', 'xs:int', 'xs:integer', 'xs:item', 'xs:java', 'xs:language', + 'xs:long', 'xs:Name', 'xs:NCName', 'xs:negativeInteger', 'xs:NMTOKEN', 'xs:NMTOKENS', + 'xs:nonNegativeInteger', 'xs:nonPositiveInteger', 'xs:normalizedString', 'xs:NOTATION', + 'xs:numeric', 'xs:positiveInteger', 'xs:precisionDecimal', 'xs:QName', 'xs:short', 'xs:string', + 'xs:time', 'xs:token', 'xs:unsignedByte', 'xs:unsignedInt', 'xs:unsignedLong', + 'xs:unsignedShort', 'xs:untyped', 'xs:untypedAtomic', 'xs:yearMonthDuration']; + for(var i=0, l=types.length; i < l; i++) { kwObj[types[i]] = atom;}; + + // each operator will add a property to kwObj with value of {type: "operator", style: "keyword"} + var operators = ['eq', 'ne', 'lt', 'le', 'gt', 'ge', ':=', '=', '>', '>=', '<', '<=', '.', '|', '?', 'and', 'or', 'div', 'idiv', 'mod', '*', '/', '+', '-']; + for(var i=0, l=operators.length; i < l; i++) { kwObj[operators[i]] = operator;}; + + // each axis_specifiers will add a property to kwObj with value of {type: "axis_specifier", style: "qualifier"} + var axis_specifiers = ["self::", "attribute::", "child::", "descendant::", "descendant-or-self::", "parent::", + "ancestor::", "ancestor-or-self::", "following::", "preceding::", "following-sibling::", "preceding-sibling::"]; + for(var i=0, l=axis_specifiers.length; i < l; i++) { kwObj[axis_specifiers[i]] = qualifier; }; + + return kwObj; + }(); + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + // the primary mode tokenizer + function tokenBase(stream, state) { + var ch = stream.next(), + mightBeFunction = false, + isEQName = isEQNameAhead(stream); + + // an XML tag (if not in some sub, chained tokenizer) + if (ch == "<") { + if(stream.match("!--", true)) + return chain(stream, state, tokenXMLComment); + + if(stream.match("![CDATA", false)) { + state.tokenize = tokenCDATA; + return "tag"; + } + + if(stream.match("?", false)) { + return chain(stream, state, tokenPreProcessing); + } + + var isclose = stream.eat("/"); + stream.eatSpace(); + var tagName = "", c; + while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; + + return chain(stream, state, tokenTag(tagName, isclose)); + } + // start code block + else if(ch == "{") { + pushStateStack(state, { type: "codeblock"}); + return null; + } + // end code block + else if(ch == "}") { + popStateStack(state); + return null; + } + // if we're in an XML block + else if(isInXmlBlock(state)) { + if(ch == ">") + return "tag"; + else if(ch == "/" && stream.eat(">")) { + popStateStack(state); + return "tag"; + } + else + return "variable"; + } + // if a number + else if (/\d/.test(ch)) { + stream.match(/^\d*(?:\.\d*)?(?:E[+\-]?\d+)?/); + return "atom"; + } + // comment start + else if (ch === "(" && stream.eat(":")) { + pushStateStack(state, { type: "comment"}); + return chain(stream, state, tokenComment); + } + // quoted string + else if (!isEQName && (ch === '"' || ch === "'")) + return chain(stream, state, tokenString(ch)); + // variable + else if(ch === "$") { + return chain(stream, state, tokenVariable); + } + // assignment + else if(ch ===":" && stream.eat("=")) { + return "keyword"; + } + // open paren + else if(ch === "(") { + pushStateStack(state, { type: "paren"}); + return null; + } + // close paren + else if(ch === ")") { + popStateStack(state); + return null; + } + // open paren + else if(ch === "[") { + pushStateStack(state, { type: "bracket"}); + return null; + } + // close paren + else if(ch === "]") { + popStateStack(state); + return null; + } + else { + var known = keywords.propertyIsEnumerable(ch) && keywords[ch]; + + // if there's a EQName ahead, consume the rest of the string portion, it's likely a function + if(isEQName && ch === '\"') while(stream.next() !== '"'){} + if(isEQName && ch === '\'') while(stream.next() !== '\''){} + + // gobble up a word if the character is not known + if(!known) stream.eatWhile(/[\w\$_-]/); + + // gobble a colon in the case that is a lib func type call fn:doc + var foundColon = stream.eat(":"); + + // if there's not a second colon, gobble another word. Otherwise, it's probably an axis specifier + // which should get matched as a keyword + if(!stream.eat(":") && foundColon) { + stream.eatWhile(/[\w\$_-]/); + } + // if the next non whitespace character is an open paren, this is probably a function (if not a keyword of other sort) + if(stream.match(/^[ \t]*\(/, false)) { + mightBeFunction = true; + } + // is the word a keyword? + var word = stream.current(); + known = keywords.propertyIsEnumerable(word) && keywords[word]; + + // if we think it's a function call but not yet known, + // set style to variable for now for lack of something better + if(mightBeFunction && !known) known = {type: "function_call", style: "variable def"}; + + // if the previous word was element, attribute, axis specifier, this word should be the name of that + if(isInXmlConstructor(state)) { + popStateStack(state); + return "variable"; + } + // as previously checked, if the word is element,attribute, axis specifier, call it an "xmlconstructor" and + // push the stack so we know to look for it on the next word + if(word == "element" || word == "attribute" || known.type == "axis_specifier") pushStateStack(state, {type: "xmlconstructor"}); + + // if the word is known, return the details of that else just call this a generic 'word' + return known ? known.style : "variable"; + } + } + + // handle comments, including nested + function tokenComment(stream, state) { + var maybeEnd = false, maybeNested = false, nestedCount = 0, ch; + while (ch = stream.next()) { + if (ch == ")" && maybeEnd) { + if(nestedCount > 0) + nestedCount--; + else { + popStateStack(state); + break; + } + } + else if(ch == ":" && maybeNested) { + nestedCount++; + } + maybeEnd = (ch == ":"); + maybeNested = (ch == "("); + } + + return "comment"; + } + + // tokenizer for string literals + // optionally pass a tokenizer function to set state.tokenize back to when finished + function tokenString(quote, f) { + return function(stream, state) { + var ch; + + if(isInString(state) && stream.current() == quote) { + popStateStack(state); + if(f) state.tokenize = f; + return "string"; + } + + pushStateStack(state, { type: "string", name: quote, tokenize: tokenString(quote, f) }); + + // if we're in a string and in an XML block, allow an embedded code block + if(stream.match("{", false) && isInXmlAttributeBlock(state)) { + state.tokenize = tokenBase; + return "string"; + } + + + while (ch = stream.next()) { + if (ch == quote) { + popStateStack(state); + if(f) state.tokenize = f; + break; + } + else { + // if we're in a string and in an XML block, allow an embedded code block in an attribute + if(stream.match("{", false) && isInXmlAttributeBlock(state)) { + state.tokenize = tokenBase; + return "string"; + } + + } + } + + return "string"; + }; + } + + // tokenizer for variables + function tokenVariable(stream, state) { + var isVariableChar = /[\w\$_-]/; + + // a variable may start with a quoted EQName so if the next character is quote, consume to the next quote + if(stream.eat("\"")) { + while(stream.next() !== '\"'){}; + stream.eat(":"); + } else { + stream.eatWhile(isVariableChar); + if(!stream.match(":=", false)) stream.eat(":"); + } + stream.eatWhile(isVariableChar); + state.tokenize = tokenBase; + return "variable"; + } + + // tokenizer for XML tags + function tokenTag(name, isclose) { + return function(stream, state) { + stream.eatSpace(); + if(isclose && stream.eat(">")) { + popStateStack(state); + state.tokenize = tokenBase; + return "tag"; + } + // self closing tag without attributes? + if(!stream.eat("/")) + pushStateStack(state, { type: "tag", name: name, tokenize: tokenBase}); + if(!stream.eat(">")) { + state.tokenize = tokenAttribute; + return "tag"; + } + else { + state.tokenize = tokenBase; + } + return "tag"; + }; + } + + // tokenizer for XML attributes + function tokenAttribute(stream, state) { + var ch = stream.next(); + + if(ch == "/" && stream.eat(">")) { + if(isInXmlAttributeBlock(state)) popStateStack(state); + if(isInXmlBlock(state)) popStateStack(state); + return "tag"; + } + if(ch == ">") { + if(isInXmlAttributeBlock(state)) popStateStack(state); + return "tag"; + } + if(ch == "=") + return null; + // quoted string + if (ch == '"' || ch == "'") + return chain(stream, state, tokenString(ch, tokenAttribute)); + + if(!isInXmlAttributeBlock(state)) + pushStateStack(state, { type: "attribute", tokenize: tokenAttribute}); + + stream.eat(/[a-zA-Z_:]/); + stream.eatWhile(/[-a-zA-Z0-9_:.]/); + stream.eatSpace(); + + // the case where the attribute has not value and the tag was closed + if(stream.match(">", false) || stream.match("/", false)) { + popStateStack(state); + state.tokenize = tokenBase; + } + + return "attribute"; + } + + // handle comments, including nested + function tokenXMLComment(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "-" && stream.match("->", true)) { + state.tokenize = tokenBase; + return "comment"; + } + } + } + + + // handle CDATA + function tokenCDATA(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "]" && stream.match("]", true)) { + state.tokenize = tokenBase; + return "comment"; + } + } + } + + // handle preprocessing instructions + function tokenPreProcessing(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "?" && stream.match(">", true)) { + state.tokenize = tokenBase; + return "comment meta"; + } + } + } + + + // functions to test the current context of the state + function isInXmlBlock(state) { return isIn(state, "tag"); } + function isInXmlAttributeBlock(state) { return isIn(state, "attribute"); } + function isInXmlConstructor(state) { return isIn(state, "xmlconstructor"); } + function isInString(state) { return isIn(state, "string"); } + + function isEQNameAhead(stream) { + // assume we've already eaten a quote (") + if(stream.current() === '"') + return stream.match(/^[^\"]+\"\:/, false); + else if(stream.current() === '\'') + return stream.match(/^[^\"]+\'\:/, false); + else + return false; + } + + function isIn(state, type) { + return (state.stack.length && state.stack[state.stack.length - 1].type == type); + } + + function pushStateStack(state, newState) { + state.stack.push(newState); + } + + function popStateStack(state) { + state.stack.pop(); + var reinstateTokenize = state.stack.length && state.stack[state.stack.length-1].tokenize; + state.tokenize = reinstateTokenize || tokenBase; + } + + // the interface for the mode API + return { + startState: function() { + return { + tokenize: tokenBase, + cc: [], + stack: [] + }; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + return style; + }, + + blockCommentStart: "(:", + blockCommentEnd: ":)" + + }; + +}); + +CodeMirror.defineMIME("application/xquery", "xquery"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/yacas/yacas.js b/docs/js/node_modules/codemirror/mode/yacas/yacas.js new file mode 100644 index 000000000..b7ac96b71 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/yacas/yacas.js @@ -0,0 +1,204 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// Yacas mode copyright (c) 2015 by Grzegorz Mazur +// Loosely based on mathematica mode by Calin Barbat + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode('yacas', function(_config, _parserConfig) { + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var bodiedOps = words("Assert BackQuote D Defun Deriv For ForEach FromFile " + + "FromString Function Integrate InverseTaylor Limit " + + "LocalSymbols Macro MacroRule MacroRulePattern " + + "NIntegrate Rule RulePattern Subst TD TExplicitSum " + + "TSum Taylor Taylor1 Taylor2 Taylor3 ToFile " + + "ToStdout ToString TraceRule Until While"); + + // patterns + var pFloatForm = "(?:(?:\\.\\d+|\\d+\\.\\d*|\\d+)(?:[eE][+-]?\\d+)?)"; + var pIdentifier = "(?:[a-zA-Z\\$'][a-zA-Z0-9\\$']*)"; + + // regular expressions + var reFloatForm = new RegExp(pFloatForm); + var reIdentifier = new RegExp(pIdentifier); + var rePattern = new RegExp(pIdentifier + "?_" + pIdentifier); + var reFunctionLike = new RegExp(pIdentifier + "\\s*\\("); + + function tokenBase(stream, state) { + var ch; + + // get next character + ch = stream.next(); + + // string + if (ch === '"') { + state.tokenize = tokenString; + return state.tokenize(stream, state); + } + + // comment + if (ch === '/') { + if (stream.eat('*')) { + state.tokenize = tokenComment; + return state.tokenize(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + + // go back one character + stream.backUp(1); + + // update scope info + var m = stream.match(/^(\w+)\s*\(/, false); + if (m !== null && bodiedOps.hasOwnProperty(m[1])) + state.scopes.push('bodied'); + + var scope = currentScope(state); + + if (scope === 'bodied' && ch === '[') + state.scopes.pop(); + + if (ch === '[' || ch === '{' || ch === '(') + state.scopes.push(ch); + + scope = currentScope(state); + + if (scope === '[' && ch === ']' || + scope === '{' && ch === '}' || + scope === '(' && ch === ')') + state.scopes.pop(); + + if (ch === ';') { + while (scope === 'bodied') { + state.scopes.pop(); + scope = currentScope(state); + } + } + + // look for ordered rules + if (stream.match(/\d+ *#/, true, false)) { + return 'qualifier'; + } + + // look for numbers + if (stream.match(reFloatForm, true, false)) { + return 'number'; + } + + // look for placeholders + if (stream.match(rePattern, true, false)) { + return 'variable-3'; + } + + // match all braces separately + if (stream.match(/(?:\[|\]|{|}|\(|\))/, true, false)) { + return 'bracket'; + } + + // literals looking like function calls + if (stream.match(reFunctionLike, true, false)) { + stream.backUp(1); + return 'variable'; + } + + // all other identifiers + if (stream.match(reIdentifier, true, false)) { + return 'variable-2'; + } + + // operators; note that operators like @@ or /; are matched separately for each symbol. + if (stream.match(/(?:\\|\+|\-|\*|\/|,|;|\.|:|@|~|=|>|<|&|\||_|`|'|\^|\?|!|%|#)/, true, false)) { + return 'operator'; + } + + // everything else is an error + return 'error'; + } + + function tokenString(stream, state) { + var next, end = false, escaped = false; + while ((next = stream.next()) != null) { + if (next === '"' && !escaped) { + end = true; + break; + } + escaped = !escaped && next === '\\'; + } + if (end && !escaped) { + state.tokenize = tokenBase; + } + return 'string'; + }; + + function tokenComment(stream, state) { + var prev, next; + while((next = stream.next()) != null) { + if (prev === '*' && next === '/') { + state.tokenize = tokenBase; + break; + } + prev = next; + } + return 'comment'; + } + + function currentScope(state) { + var scope = null; + if (state.scopes.length > 0) + scope = state.scopes[state.scopes.length - 1]; + return scope; + } + + return { + startState: function() { + return { + tokenize: tokenBase, + scopes: [] + }; + }, + token: function(stream, state) { + if (stream.eatSpace()) return null; + return state.tokenize(stream, state); + }, + indent: function(state, textAfter) { + if (state.tokenize !== tokenBase && state.tokenize !== null) + return CodeMirror.Pass; + + var delta = 0; + if (textAfter === ']' || textAfter === '];' || + textAfter === '}' || textAfter === '};' || + textAfter === ');') + delta = -1; + + return (state.scopes.length + delta) * _config.indentUnit; + }, + electricChars: "{}[]();", + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//" + }; +}); + +CodeMirror.defineMIME('text/x-yacas', { + name: 'yacas' +}); + +}); diff --git a/docs/js/node_modules/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js b/docs/js/node_modules/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js new file mode 100644 index 000000000..87fdf80d0 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js @@ -0,0 +1,68 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function (mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../yaml/yaml")) + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../yaml/yaml"], mod) + else // Plain browser env + mod(CodeMirror) +})(function (CodeMirror) { + + var START = 0, FRONTMATTER = 1, BODY = 2 + + // a mixed mode for Markdown text with an optional YAML front matter + CodeMirror.defineMode("yaml-frontmatter", function (config, parserConfig) { + var yamlMode = CodeMirror.getMode(config, "yaml") + var innerMode = CodeMirror.getMode(config, parserConfig && parserConfig.base || "gfm") + + function curMode(state) { + return state.state == BODY ? innerMode : yamlMode + } + + return { + startState: function () { + return { + state: START, + inner: CodeMirror.startState(yamlMode) + } + }, + copyState: function (state) { + return { + state: state.state, + inner: CodeMirror.copyState(curMode(state), state.inner) + } + }, + token: function (stream, state) { + if (state.state == START) { + if (stream.match(/---/, false)) { + state.state = FRONTMATTER + return yamlMode.token(stream, state.inner) + } else { + state.state = BODY + state.inner = CodeMirror.startState(innerMode) + return innerMode.token(stream, state.inner) + } + } else if (state.state == FRONTMATTER) { + var end = stream.sol() && stream.match(/(---|\.\.\.)/, false) + var style = yamlMode.token(stream, state.inner) + if (end) { + state.state = BODY + state.inner = CodeMirror.startState(innerMode) + } + return style + } else { + return innerMode.token(stream, state.inner) + } + }, + innerMode: function (state) { + return {mode: curMode(state), state: state.inner} + }, + blankLine: function (state) { + var mode = curMode(state) + if (mode.blankLine) return mode.blankLine(state.inner) + } + } + }) +}); diff --git a/docs/js/node_modules/codemirror/mode/yaml/yaml.js b/docs/js/node_modules/codemirror/mode/yaml/yaml.js new file mode 100644 index 000000000..a29d7ea4a --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/yaml/yaml.js @@ -0,0 +1,120 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("yaml", function() { + + var cons = ['true', 'false', 'on', 'off', 'yes', 'no']; + var keywordRegex = new RegExp("\\b(("+cons.join(")|(")+"))$", 'i'); + + return { + token: function(stream, state) { + var ch = stream.peek(); + var esc = state.escaped; + state.escaped = false; + /* comments */ + if (ch == "#" && (stream.pos == 0 || /\s/.test(stream.string.charAt(stream.pos - 1)))) { + stream.skipToEnd(); + return "comment"; + } + + if (stream.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/)) + return "string"; + + if (state.literal && stream.indentation() > state.keyCol) { + stream.skipToEnd(); return "string"; + } else if (state.literal) { state.literal = false; } + if (stream.sol()) { + state.keyCol = 0; + state.pair = false; + state.pairStart = false; + /* document start */ + if(stream.match(/---/)) { return "def"; } + /* document end */ + if (stream.match(/\.\.\./)) { return "def"; } + /* array list item */ + if (stream.match(/\s*-\s+/)) { return 'meta'; } + } + /* inline pairs/lists */ + if (stream.match(/^(\{|\}|\[|\])/)) { + if (ch == '{') + state.inlinePairs++; + else if (ch == '}') + state.inlinePairs--; + else if (ch == '[') + state.inlineList++; + else + state.inlineList--; + return 'meta'; + } + + /* list seperator */ + if (state.inlineList > 0 && !esc && ch == ',') { + stream.next(); + return 'meta'; + } + /* pairs seperator */ + if (state.inlinePairs > 0 && !esc && ch == ',') { + state.keyCol = 0; + state.pair = false; + state.pairStart = false; + stream.next(); + return 'meta'; + } + + /* start of value of a pair */ + if (state.pairStart) { + /* block literals */ + if (stream.match(/^\s*(\||\>)\s*/)) { state.literal = true; return 'meta'; }; + /* references */ + if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) { return 'variable-2'; } + /* numbers */ + if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) { return 'number'; } + if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) { return 'number'; } + /* keywords */ + if (stream.match(keywordRegex)) { return 'keyword'; } + } + + /* pairs (associative arrays) -> key */ + if (!state.pair && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)) { + state.pair = true; + state.keyCol = stream.indentation(); + return "atom"; + } + if (state.pair && stream.match(/^:\s*/)) { state.pairStart = true; return 'meta'; } + + /* nothing found, continue */ + state.pairStart = false; + state.escaped = (ch == '\\'); + stream.next(); + return null; + }, + startState: function() { + return { + pair: false, + pairStart: false, + keyCol: 0, + inlinePairs: 0, + inlineList: 0, + literal: false, + escaped: false + }; + }, + lineComment: "#", + fold: "indent" + }; +}); + +CodeMirror.defineMIME("text/x-yaml", "yaml"); +CodeMirror.defineMIME("text/yaml", "yaml"); + +}); diff --git a/docs/js/node_modules/codemirror/mode/z80/z80.js b/docs/js/node_modules/codemirror/mode/z80/z80.js new file mode 100644 index 000000000..8cea4ff90 --- /dev/null +++ b/docs/js/node_modules/codemirror/mode/z80/z80.js @@ -0,0 +1,116 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode('z80', function(_config, parserConfig) { + var ez80 = parserConfig.ez80; + var keywords1, keywords2; + if (ez80) { + keywords1 = /^(exx?|(ld|cp)([di]r?)?|[lp]ea|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|[de]i|halt|im|in([di]mr?|ir?|irx|2r?)|ot(dmr?|[id]rx|imr?)|out(0?|[di]r?|[di]2r?)|tst(io)?|slp)(\.([sl]?i)?[sl])?\b/i; + keywords2 = /^(((call|j[pr]|rst|ret[in]?)(\.([sl]?i)?[sl])?)|(rs|st)mix)\b/i; + } else { + keywords1 = /^(exx?|(ld|cp|in)([di]r?)?|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|rst|[de]i|halt|im|ot[di]r|out[di]?)\b/i; + keywords2 = /^(call|j[pr]|ret[in]?|b_?(call|jump))\b/i; + } + + var variables1 = /^(af?|bc?|c|de?|e|hl?|l|i[xy]?|r|sp)\b/i; + var variables2 = /^(n?[zc]|p[oe]?|m)\b/i; + var errors = /^([hl][xy]|i[xy][hl]|slia|sll)\b/i; + var numbers = /^([\da-f]+h|[0-7]+o|[01]+b|\d+d?)\b/i; + + return { + startState: function() { + return { + context: 0 + }; + }, + token: function(stream, state) { + if (!stream.column()) + state.context = 0; + + if (stream.eatSpace()) + return null; + + var w; + + if (stream.eatWhile(/\w/)) { + if (ez80 && stream.eat('.')) { + stream.eatWhile(/\w/); + } + w = stream.current(); + + if (stream.indentation()) { + if ((state.context == 1 || state.context == 4) && variables1.test(w)) { + state.context = 4; + return 'var2'; + } + + if (state.context == 2 && variables2.test(w)) { + state.context = 4; + return 'var3'; + } + + if (keywords1.test(w)) { + state.context = 1; + return 'keyword'; + } else if (keywords2.test(w)) { + state.context = 2; + return 'keyword'; + } else if (state.context == 4 && numbers.test(w)) { + return 'number'; + } + + if (errors.test(w)) + return 'error'; + } else if (stream.match(numbers)) { + return 'number'; + } else { + return null; + } + } else if (stream.eat(';')) { + stream.skipToEnd(); + return 'comment'; + } else if (stream.eat('"')) { + while (w = stream.next()) { + if (w == '"') + break; + + if (w == '\\') + stream.next(); + } + return 'string'; + } else if (stream.eat('\'')) { + if (stream.match(/\\?.'/)) + return 'number'; + } else if (stream.eat('.') || stream.sol() && stream.eat('#')) { + state.context = 5; + + if (stream.eatWhile(/\w/)) + return 'def'; + } else if (stream.eat('$')) { + if (stream.eatWhile(/[\da-f]/i)) + return 'number'; + } else if (stream.eat('%')) { + if (stream.eatWhile(/[01]/)) + return 'number'; + } else { + stream.next(); + } + return null; + } + }; +}); + +CodeMirror.defineMIME("text/x-z80", "z80"); +CodeMirror.defineMIME("text/x-ez80", { name: "z80", ez80: true }); + +}); diff --git a/docs/js/node_modules/codemirror/package.json b/docs/js/node_modules/codemirror/package.json new file mode 100644 index 000000000..0b32a20fc --- /dev/null +++ b/docs/js/node_modules/codemirror/package.json @@ -0,0 +1,2585 @@ +{ + "_from": "codemirror@^5.37.0", + "_id": "codemirror@5.49.2", + "_inBundle": false, + "_integrity": "sha512-dwJ2HRPHm8w51WB5YTF9J7m6Z5dtkqbU9ntMZ1dqXyFB9IpjoUFDj80ahRVEoVanfIp6pfASJbOlbWdEf8FOzQ==", + "_location": "/codemirror", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": true, + "raw": "codemirror@^5.37.0", + "name": "codemirror", + "escapedName": "codemirror", + "rawSpec": "^5.37.0", + "saveSpec": null, + "fetchSpec": "^5.37.0" + }, + "_requiredBy": [ + "/" + ], + "_resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.49.2.tgz", + "_shasum": "c84fdaf11b19803f828b0c67060c7bc6d154ccad", + "_spec": "codemirror@^5.37.0", + "_where": "/Users/hectorip/Education/Eloquent-JavaScript-ES", + "author": { + "name": "Marijn Haverbeke", + "email": "marijnh@gmail.com", + "url": "http://marijnhaverbeke.nl" + }, + "bugs": { + "url": "http://github.com/codemirror/CodeMirror/issues" + }, + "bundleDependencies": false, + "contributors": [ + { + "name": "List of CodeMirror contributors. Updated before every release." + }, + { + "name": "4oo4" + }, + { + "name": "4r2r" + }, + { + "name": "Aaron Brooks" + }, + { + "name": "Abdelouahab" + }, + { + "name": "Abdussalam Abdurrahman" + }, + { + "name": "Abe Fettig" + }, + { + "name": "Abhishek Gahlot" + }, + { + "name": "Adam Ahmed" + }, + { + "name": "Adam King" + }, + { + "name": "Adam Particka" + }, + { + "name": "adanlobato" + }, + { + "name": "Adán Lobato" + }, + { + "name": "Aditya Toshniwal" + }, + { + "name": "Adrian Aichner" + }, + { + "name": "Adrian Heine" + }, + { + "name": "Adrien Bertrand" + }, + { + "name": "aeroson" + }, + { + "name": "Ahmad Amireh" + }, + { + "name": "Ahmad M. Zawawi" + }, + { + "name": "ahoward" + }, + { + "name": "Akeksandr Motsjonov" + }, + { + "name": "Alasdair Smith" + }, + { + "name": "AlbertHilb" + }, + { + "name": "Alberto González Palomo" + }, + { + "name": "Alberto Pose" + }, + { + "name": "Albert Xing" + }, + { + "name": "Alexander Pavlov" + }, + { + "name": "Alexander Schepanovski" + }, + { + "name": "Alexander Shvets" + }, + { + "name": "Alexander Solovyov" + }, + { + "name": "Alexandre Bique" + }, + { + "name": "alexey-k" + }, + { + "name": "Alex Piggott" + }, + { + "name": "Aliaksei Chapyzhenka" + }, + { + "name": "Allen Sarkisyan" + }, + { + "name": "Ami Fischman" + }, + { + "name": "Amin Shali" + }, + { + "name": "Amin Ullah Khan" + }, + { + "name": "amshali@google.com" + }, + { + "name": "Amsul" + }, + { + "name": "amuntean" + }, + { + "name": "Amy" + }, + { + "name": "Ananya Sen" + }, + { + "name": "anaran" + }, + { + "name": "AndersMad" + }, + { + "name": "Anders Nawroth" + }, + { + "name": "Anderson Mesquita" + }, + { + "name": "Anders Wåglund" + }, + { + "name": "Andrea G" + }, + { + "name": "Andreas Reischuck" + }, + { + "name": "Andres Taylor" + }, + { + "name": "Andre von Houck" + }, + { + "name": "Andrew Cheng" + }, + { + "name": "Andrew Dassonville" + }, + { + "name": "Andrey Fedorov" + }, + { + "name": "Andrey Klyuchnikov" + }, + { + "name": "Andrey Lushnikov" + }, + { + "name": "Andrey Shchekin" + }, + { + "name": "Andy Joslin" + }, + { + "name": "Andy Kimball" + }, + { + "name": "Andy Li" + }, + { + "name": "Angelo" + }, + { + "name": "angelozerr" + }, + { + "name": "angelo.zerr@gmail.com" + }, + { + "name": "Ankit" + }, + { + "name": "Ankit Ahuja" + }, + { + "name": "Ansel Santosa" + }, + { + "name": "Anthony Dugois" + }, + { + "name": "anthonygego" + }, + { + "name": "Anthony Gégo" + }, + { + "name": "Anthony Grimes" + }, + { + "name": "Anton Kovalyov" + }, + { + "name": "Apollo Zhu" + }, + { + "name": "AQNOUCH Mohammed" + }, + { + "name": "Aram Shatakhtsyan" + }, + { + "name": "areos" + }, + { + "name": "Arnab Bose" + }, + { + "name": "Arnoud Buzing" + }, + { + "name": "Arsène von Wyss" + }, + { + "name": "Arthur Müller" + }, + { + "name": "Arun Narasani" + }, + { + "name": "as3boyan" + }, + { + "name": "asolove" + }, + { + "name": "atelierbram" + }, + { + "name": "AtomicPages LLC" + }, + { + "name": "Atul Bhouraskar" + }, + { + "name": "Aurelian Oancea" + }, + { + "name": "Axel Lewenhaupt" + }, + { + "name": "Baptiste Augrain" + }, + { + "name": "Barret Rennie" + }, + { + "name": "Bartosz Dziewoński" + }, + { + "name": "Basarat Ali Syed" + }, + { + "name": "Bastian Müller" + }, + { + "name": "belhaj" + }, + { + "name": "Bem Jones-Bey" + }, + { + "name": "benbro" + }, + { + "name": "Beni Cherniavsky-Paskin" + }, + { + "name": "Benjamin DeCoste" + }, + { + "name": "Benjamin Young" + }, + { + "name": "Ben Keen" + }, + { + "name": "Ben Miller" + }, + { + "name": "Ben Mosher" + }, + { + "name": "Bernhard Sirlinger" + }, + { + "name": "Bert Chang" + }, + { + "name": "Bharad" + }, + { + "name": "BigBlueHat" + }, + { + "name": "Billy Moon" + }, + { + "name": "binny" + }, + { + "name": "Bjorn Hansen" + }, + { + "name": "B Krishna Chaitanya" + }, + { + "name": "Blaine G" + }, + { + "name": "blukat29" + }, + { + "name": "Bo" + }, + { + "name": "boomyjee" + }, + { + "name": "Bo Peng" + }, + { + "name": "borawjm" + }, + { + "name": "Brad Metcalf" + }, + { + "name": "Brandon Frohs" + }, + { + "name": "Brandon Wamboldt" + }, + { + "name": "Bret Little" + }, + { + "name": "Brett Zamir" + }, + { + "name": "Brian Grinstead" + }, + { + "name": "Brian Sletten" + }, + { + "name": "brrd" + }, + { + "name": "Bruce Mitchener" + }, + { + "name": "Bruno Logerfo" + }, + { + "name": "Bryan Gin-ge Chen" + }, + { + "name": "Bryan Massoth" + }, + { + "name": "Caitlin Potter" + }, + { + "name": "Calin Barbat" + }, + { + "name": "callodacity" + }, + { + "name": "Camilo Roca" + }, + { + "name": "Casey Klebba" + }, + { + "name": "César González Íñiguez" + }, + { + "name": "Chad Jolly" + }, + { + "name": "Chandra Sekhar Pydi" + }, + { + "name": "Charles Skelton" + }, + { + "name": "Cheah Chu Yeow" + }, + { + "name": "Chhekur" + }, + { + "name": "Chris Colborne" + }, + { + "name": "Chris Coyier" + }, + { + "name": "Chris Ford" + }, + { + "name": "Chris Granger" + }, + { + "name": "Chris Houseknecht" + }, + { + "name": "Chris Lohfink" + }, + { + "name": "Chris Morgan" + }, + { + "name": "Chris Reeves" + }, + { + "name": "Chris Smith" + }, + { + "name": "Christian Gruen" + }, + { + "name": "Christian Oyarzun" + }, + { + "name": "Christian Petrov" + }, + { + "name": "christopherblaser" + }, + { + "name": "Christopher Brown" + }, + { + "name": "Christopher Kramer" + }, + { + "name": "Christopher Mitchell" + }, + { + "name": "Christopher Pfohl" + }, + { + "name": "Christopher Wallis" + }, + { + "name": "Chunliang Lyu" + }, + { + "name": "ciaranj" + }, + { + "name": "clso" + }, + { + "name": "CodeAnimal" + }, + { + "name": "CodeBitt" + }, + { + "name": "coderaiser" + }, + { + "name": "Cole R Lawrence" + }, + { + "name": "ComFreek" + }, + { + "name": "Cristian Prieto" + }, + { + "name": "Curran Kelleher" + }, + { + "name": "Curtis Gagliardi" + }, + { + "name": "dagsta" + }, + { + "name": "daines" + }, + { + "name": "Dale Jung" + }, + { + "name": "Dan Bentley" + }, + { + "name": "Dan Heberden" + }, + { + "name": "Daniel, Dao Quang Minh" + }, + { + "name": "Daniele Di Sarli" + }, + { + "name": "Daniel Faust" + }, + { + "name": "Daniel Hanggi" + }, + { + "name": "Daniel Huigens" + }, + { + "name": "Daniel Kesler" + }, + { + "name": "Daniel KJ" + }, + { + "name": "Daniel Neel" + }, + { + "name": "Daniel Parnell" + }, + { + "name": "Daniel Thwaites" + }, + { + "name": "Danila Malyutin" + }, + { + "name": "Danny Yoo" + }, + { + "name": "darealshinji" + }, + { + "name": "Darius Roberts" + }, + { + "name": "databricks-david-lewis" + }, + { + "name": "Dave Brondsema" + }, + { + "name": "Dave MacLachlan" + }, + { + "name": "Dave Myers" + }, + { + "name": "David Barnett" + }, + { + "name": "David H. Bronke" + }, + { + "name": "David Mignot" + }, + { + "name": "David Pathakjee" + }, + { + "name": "David Santana" + }, + { + "name": "David Vázquez" + }, + { + "name": "David Whittington" + }, + { + "name": "deebugger" + }, + { + "name": "Deep Thought" + }, + { + "name": "Denis Ovsienko" + }, + { + "name": "Devin Abbott" + }, + { + "name": "Devon Carew" + }, + { + "name": "Dick Choi" + }, + { + "name": "Diego Fernandez" + }, + { + "name": "dignifiedquire" + }, + { + "name": "Dimage Sapelkin" + }, + { + "name": "dmaclach" + }, + { + "name": "Dmitry Kiselyov" + }, + { + "name": "domagoj412" + }, + { + "name": "Dominator008" + }, + { + "name": "Domizio Demichelis" + }, + { + "name": "Doug Blank" + }, + { + "name": "Doug Wikle" + }, + { + "name": "Drew Bratcher" + }, + { + "name": "Drew Hintz" + }, + { + "name": "Drew Khoury" + }, + { + "name": "Drini Cami" + }, + { + "name": "Dror BG" + }, + { + "name": "Duncan Lilley" + }, + { + "name": "duralog" + }, + { + "name": "dwelle" + }, + { + "name": "eborden" + }, + { + "name": "edoroshenko" + }, + { + "name": "edsharp" + }, + { + "name": "ekhaled" + }, + { + "name": "Elisée" + }, + { + "name": "Emmanuel Schanzer" + }, + { + "name": "Enam Mijbah Noor" + }, + { + "name": "Eric Allam" + }, + { + "name": "Eric Bogard" + }, + { + "name": "Erik Demaine" + }, + { + "name": "Erik Welander" + }, + { + "name": "eustas" + }, + { + "name": "Evan Minsk" + }, + { + "name": "Fabien Dubosson" + }, + { + "name": "Fabien O'Carroll" + }, + { + "name": "Fabio Zendhi Nagao" + }, + { + "name": "Faiza Alsaied" + }, + { + "name": "Fauntleroy" + }, + { + "name": "fbuchinger" + }, + { + "name": "feizhang365" + }, + { + "name": "Felipe Lalanne" + }, + { + "name": "Felix Raab" + }, + { + "name": "ficristo" + }, + { + "name": "Filip Noetzel" + }, + { + "name": "Filip Stollár" + }, + { + "name": "Filype Pereira" + }, + { + "name": "finalfantasia" + }, + { + "name": "flack" + }, + { + "name": "Florian Felten" + }, + { + "name": "Forbes Lindesay" + }, + { + "name": "ForbesLindesay" + }, + { + "name": "Ford_Lawnmower" + }, + { + "name": "Forrest Oliphant" + }, + { + "name": "Franco Catena" + }, + { + "name": "Frank Seifferth" + }, + { + "name": "Frank Wiegand" + }, + { + "name": "fraxx001" + }, + { + "name": "Fredrik Borg" + }, + { + "name": "FUJI Goro", + "url": "gfx" + }, + { + "name": "Gabriel Gheorghian" + }, + { + "name": "Gabriel Horner" + }, + { + "name": "Gabriel Nahmias" + }, + { + "name": "galambalazs" + }, + { + "name": "Gary Sheng" + }, + { + "name": "Gautam Mehta" + }, + { + "name": "Gavin Douglas" + }, + { + "name": "gekkoe" + }, + { + "name": "Geordie Hall" + }, + { + "name": "George Stephanis" + }, + { + "name": "geowarin" + }, + { + "name": "Gerard Braad" + }, + { + "name": "Gergely Hegykozi" + }, + { + "name": "Germain Chazot" + }, + { + "name": "Giovanni Calò" + }, + { + "name": "Glebov Boris" + }, + { + "name": "Glenn Jorde" + }, + { + "name": "Glenn Ruehle" + }, + { + "name": "goldsmcb" + }, + { + "name": "Golevka" + }, + { + "name": "Google LLC" + }, + { + "name": "Gordon Smith" + }, + { + "name": "Grant Skinner" + }, + { + "name": "greengiant" + }, + { + "name": "Gregory Koberger" + }, + { + "name": "Grzegorz Mazur" + }, + { + "name": "Guang Li" + }, + { + "name": "Guan Gui" + }, + { + "name": "Guillaume Massé" + }, + { + "name": "Guillaume Massé" + }, + { + "name": "guraga" + }, + { + "name": "Gustavo Rodrigues" + }, + { + "name": "Hakan Tunc" + }, + { + "name": "Hans Engel" + }, + { + "name": "Hanzhao Deng" + }, + { + "name": "Harald Schilly" + }, + { + "name": "Hardest" + }, + { + "name": "Harshvardhan Gupta" + }, + { + "name": "Hasan Karahan" + }, + { + "name": "Heanes" + }, + { + "name": "Hector Oswaldo Caballero" + }, + { + "name": "Hélio" + }, + { + "name": "Hendrik Wallbaum" + }, + { + "name": "Henrik Haugbølle" + }, + { + "name": "Herculano Campos" + }, + { + "name": "hidaiy" + }, + { + "name": "Hiroyuki Makino" + }, + { + "name": "hitsthings" + }, + { + "name": "Hocdoc" + }, + { + "name": "Hugues Malphettes" + }, + { + "name": "Ian Beck" + }, + { + "name": "Ian Davies" + }, + { + "name": "Ian Dickinson" + }, + { + "name": "Ian Rose" + }, + { + "name": "Ian Wehrman" + }, + { + "name": "Ian Wetherbee" + }, + { + "name": "Ice White" + }, + { + "name": "ICHIKAWA, Yuji" + }, + { + "name": "idleberg" + }, + { + "name": "ilvalle" + }, + { + "name": "Ilya Kharlamov" + }, + { + "name": "Ingo Richter" + }, + { + "name": "Irakli Gozalishvili" + }, + { + "name": "Ivan Kurnosov" + }, + { + "name": "Ivoah" + }, + { + "name": "Jacob Lee" + }, + { + "name": "Jaimin" + }, + { + "name": "Jake Peyser" + }, + { + "name": "Jakob Miland" + }, + { + "name": "Jakub Vrana" + }, + { + "name": "Jakub Vrána" + }, + { + "name": "James Campos" + }, + { + "name": "James Howard" + }, + { + "name": "James Thorne" + }, + { + "name": "Jamie Hill" + }, + { + "name": "Jamie Morris" + }, + { + "name": "Janice Leung" + }, + { + "name": "Jan Jongboom" + }, + { + "name": "jankeromnes" + }, + { + "name": "Jan Keromnes" + }, + { + "name": "Jan Odvarko" + }, + { + "name": "Jan Schär" + }, + { + "name": "Jan T. Sott" + }, + { + "name": "Jared Dean" + }, + { + "name": "Jared Forsyth" + }, + { + "name": "Jared Jacobs" + }, + { + "name": "Jason" + }, + { + "name": "Jason Barnabe" + }, + { + "name": "Jason Grout" + }, + { + "name": "Jason Heeris" + }, + { + "name": "Jason Johnston" + }, + { + "name": "Jason San Jose" + }, + { + "name": "Jason Siefken" + }, + { + "name": "Jayaprabhakar" + }, + { + "name": "Jaydeep Solanki" + }, + { + "name": "Jean Boussier" + }, + { + "name": "Jeff Blaisdell" + }, + { + "name": "Jeff Hanke" + }, + { + "name": "Jeff Jenkins" + }, + { + "name": "jeffkenton" + }, + { + "name": "Jeff Pickhardt" + }, + { + "name": "jem", + "url": "graphite" + }, + { + "name": "Jeremy Parmenter" + }, + { + "name": "Jim" + }, + { + "name": "Jim Avery" + }, + { + "name": "jkaplon" + }, + { + "name": "JobJob" + }, + { + "name": "jochenberger" + }, + { + "name": "Jochen Berger" + }, + { + "name": "Joel Einbinder" + }, + { + "name": "joelpinheiro" + }, + { + "name": "joewalsh" + }, + { + "name": "Johan Ask" + }, + { + "name": "John Connor" + }, + { + "name": "John-David Dalton" + }, + { + "name": "John Engler" + }, + { + "name": "John Lees-Miller" + }, + { + "name": "John Ryan" + }, + { + "name": "John Snelson" + }, + { + "name": "John Van Der Loo" + }, + { + "name": "Jon Ander Peñalba" + }, + { + "name": "Jonas Döbertin" + }, + { + "name": "Jonas Helfer" + }, + { + "name": "Jonathan Dierksen" + }, + { + "name": "Jonathan Hart" + }, + { + "name": "Jonathan Malmaud" + }, + { + "name": "Jon Gacnik" + }, + { + "name": "jongalloway" + }, + { + "name": "Jon Malmaud" + }, + { + "name": "Jon Sangster" + }, + { + "name": "Joo" + }, + { + "name": "Joost-Wim Boekesteijn" + }, + { + "name": "Joseph Pecoraro" + }, + { + "name": "Josh Barnes" + }, + { + "name": "Josh Cohen" + }, + { + "name": "Josh Soref" + }, + { + "name": "Joshua Newman" + }, + { + "name": "Josh Watzman" + }, + { + "name": "jots" + }, + { + "name": "Joy Zhong" + }, + { + "name": "jsoojeon" + }, + { + "name": "ju1ius" + }, + { + "name": "Juan Benavides Romero" + }, + { + "name": "Jucovschi Constantin" + }, + { + "name": "Juho Vuori" + }, + { + "name": "Julien CROUZET" + }, + { + "name": "Julien Rebetez" + }, + { + "name": "Justin Andresen" + }, + { + "name": "Justin Hileman" + }, + { + "name": "jwallers@gmail.com" + }, + { + "name": "kaniga" + }, + { + "name": "karevn" + }, + { + "name": "Karol" + }, + { + "name": "Kayur Patel" + }, + { + "name": "Kazuhito Hokamura" + }, + { + "name": "kcwiakala" + }, + { + "name": "Kees de Kooter" + }, + { + "name": "Kenan Christian Dimas" + }, + { + "name": "Ken Newman" + }, + { + "name": "ken restivo" + }, + { + "name": "Ken Rockot" + }, + { + "name": "Kevin Earls" + }, + { + "name": "Kevin Kwok" + }, + { + "name": "Kevin Muret" + }, + { + "name": "Kevin Sawicki" + }, + { + "name": "Kevin Ushey" + }, + { + "name": "Kier Darby" + }, + { + "name": "Klaus Silveira" + }, + { + "name": "Koh Zi Han, Cliff" + }, + { + "name": "komakino" + }, + { + "name": "Konstantin Lopuhin" + }, + { + "name": "koops" + }, + { + "name": "Kris Ciccarello" + }, + { + "name": "ks-ifware" + }, + { + "name": "kubelsmieci" + }, + { + "name": "KwanEsq" + }, + { + "name": "Kyle Kelley" + }, + { + "name": "KyleMcNutt" + }, + { + "name": "LaKing" + }, + { + "name": "Lanfei" + }, + { + "name": "Lanny" + }, + { + "name": "laobubu" + }, + { + "name": "Laszlo Vidacs" + }, + { + "name": "leaf corcoran" + }, + { + "name": "Lemmon" + }, + { + "name": "Leo Baschy" + }, + { + "name": "Leonid Khachaturov" + }, + { + "name": "Leon Sorokin" + }, + { + "name": "Leonya Khachaturov" + }, + { + "name": "Liam Newman" + }, + { + "name": "Libo Cannici" + }, + { + "name": "Lior Goldberg" + }, + { + "name": "Lior Shub" + }, + { + "name": "LloydMilligan" + }, + { + "name": "LM" + }, + { + "name": "lochel" + }, + { + "name": "Lonnie Abelbeck" + }, + { + "name": "Lorenzo Simionato" + }, + { + "name": "Lorenzo Stoakes" + }, + { + "name": "Louis Mauchet" + }, + { + "name": "Luca Fabbri" + }, + { + "name": "Luciano Longo" + }, + { + "name": "Luciano Santana" + }, + { + "name": "Lu Fangjian" + }, + { + "name": "Luke Browning" + }, + { + "name": "Luke Granger-Brown" + }, + { + "name": "Luke Stagner" + }, + { + "name": "lynschinzer" + }, + { + "name": "M1cha" + }, + { + "name": "Madhura Jayaratne" + }, + { + "name": "Maksim Lin" + }, + { + "name": "Maksym Taran" + }, + { + "name": "Malay Majithia" + }, + { + "name": "Manideep" + }, + { + "name": "Manuel Rego Casasnovas" + }, + { + "name": "Marat Dreizin" + }, + { + "name": "Marcel Gerber" + }, + { + "name": "Marcelo Camargo" + }, + { + "name": "Marco Aurélio" + }, + { + "name": "Marco Munizaga" + }, + { + "name": "Marcus Bointon" + }, + { + "name": "Marek Rudnicki" + }, + { + "name": "Marijn Haverbeke" + }, + { + "name": "Mário Gonçalves" + }, + { + "name": "Mario Pietsch" + }, + { + "name": "Mark Anderson" + }, + { + "name": "Mark Dalgleish" + }, + { + "name": "Mark Hamstra" + }, + { + "name": "Mark Lentczner" + }, + { + "name": "Marko Bonaci" + }, + { + "name": "Mark Peace" + }, + { + "name": "Markus Bordihn" + }, + { + "name": "Markus Olsson" + }, + { + "name": "Martin Balek" + }, + { + "name": "Martín Gaitán" + }, + { + "name": "Martin Hasoň" + }, + { + "name": "Martin Hunt" + }, + { + "name": "Martin Laine" + }, + { + "name": "Martin Zagora" + }, + { + "name": "Mason Malone" + }, + { + "name": "Mateusz Paprocki" + }, + { + "name": "Mathias Bynens" + }, + { + "name": "mats cronqvist" + }, + { + "name": "Matt Gaide" + }, + { + "name": "Matthew Bauer" + }, + { + "name": "Matthew Beale" + }, + { + "name": "matthewhayes" + }, + { + "name": "Matthew Rathbone" + }, + { + "name": "Matthew Suozzo" + }, + { + "name": "Matthias Bussonnier" + }, + { + "name": "Matthias BUSSONNIER" + }, + { + "name": "Mattia Astorino" + }, + { + "name": "Matt MacPherson" + }, + { + "name": "Matt McDonald" + }, + { + "name": "Matt Pass" + }, + { + "name": "Matt Sacks" + }, + { + "name": "mauricio" + }, + { + "name": "Maximilian Hils" + }, + { + "name": "Maxim Kraev" + }, + { + "name": "Max Kirsch" + }, + { + "name": "Max Schaefer" + }, + { + "name": "Max Wu" + }, + { + "name": "Max Xiantu" + }, + { + "name": "mbarkhau" + }, + { + "name": "McBrainy" + }, + { + "name": "mce2" + }, + { + "name": "melpon" + }, + { + "name": "meshuamam" + }, + { + "name": "Metatheos" + }, + { + "name": "Micah Dubinko" + }, + { + "name": "Michael" + }, + { + "name": "Michael Goderbauer" + }, + { + "name": "Michael Grey" + }, + { + "name": "Michael Kaminsky" + }, + { + "name": "Michael Lehenbauer" + }, + { + "name": "Michael Wadman" + }, + { + "name": "Michael Walker" + }, + { + "name": "Michael Zhou" + }, + { + "name": "Michal Čihař" + }, + { + "name": "Michal Dorner" + }, + { + "name": "Michal Kapiczynski" + }, + { + "name": "Mighty Guava" + }, + { + "name": "Miguel Castillo" + }, + { + "name": "mihailik" + }, + { + "name": "Mika Andrianarijaona" + }, + { + "name": "Mike" + }, + { + "name": "Mike Bostock" + }, + { + "name": "Mike Brevoort" + }, + { + "name": "Mike Diaz" + }, + { + "name": "Mike Ivanov" + }, + { + "name": "Mike Kadin" + }, + { + "name": "Mike Kobit" + }, + { + "name": "Milan Szekely" + }, + { + "name": "MinRK" + }, + { + "name": "Miraculix87" + }, + { + "name": "misfo" + }, + { + "name": "mkaminsky11" + }, + { + "name": "mloginov" + }, + { + "name": "Moritz Schubotz", + "url": "physikerwelt" + }, + { + "name": "Moritz Schwörer" + }, + { + "name": "Moshe Wajnberg" + }, + { + "name": "mps" + }, + { + "name": "ms" + }, + { + "name": "mtaran-google" + }, + { + "name": "Mu-An ✌️ Chiou" + }, + { + "name": "Mu-An Chiou" + }, + { + "name": "mzabuawala" + }, + { + "name": "Narciso Jaramillo" + }, + { + "name": "Nathan Williams" + }, + { + "name": "ndr" + }, + { + "name": "Neil Anderson" + }, + { + "name": "neon-dev" + }, + { + "name": "nerbert" + }, + { + "name": "NetworkNode" + }, + { + "name": "nextrevision" + }, + { + "name": "ngn" + }, + { + "name": "nguillaumin" + }, + { + "name": "Ng Zhi An" + }, + { + "name": "Nicholas Bollweg" + }, + { + "name": "Nicholas Bollweg", + "url": "Nick" + }, + { + "name": "NickKolok" + }, + { + "name": "Nick Kreeger" + }, + { + "name": "Nick Small" + }, + { + "name": "Nicolas Chevobbe" + }, + { + "name": "Nicolas Kick" + }, + { + "name": "Nicolò Ribaudo" + }, + { + "name": "Niels van Groningen" + }, + { + "name": "nightwing" + }, + { + "name": "Nikita Beloglazov" + }, + { + "name": "Nikita Vasilyev" + }, + { + "name": "Nikolaj Kappler" + }, + { + "name": "Nikolay Kostov" + }, + { + "name": "nilp0inter" + }, + { + "name": "Nils Knappmeier" + }, + { + "name": "Nisarg Jhaveri" + }, + { + "name": "nlwillia" + }, + { + "name": "noragrossman" + }, + { + "name": "Norman Rzepka" + }, + { + "name": "Nouzbe" + }, + { + "name": "Oleksandr Yakovenko" + }, + { + "name": "opl-" + }, + { + "name": "Oreoluwa Onatemowo" + }, + { + "name": "oscar.lofwenhamn" + }, + { + "name": "Oskar Segersvärd" + }, + { + "name": "overdodactyl" + }, + { + "name": "pablo" + }, + { + "name": "pabloferz" + }, + { + "name": "Pablo Zubieta" + }, + { + "name": "paddya" + }, + { + "name": "Page" + }, + { + "name": "paladox" + }, + { + "name": "Panupong Pasupat" + }, + { + "name": "paris" + }, + { + "name": "Paris" + }, + { + "name": "Paris Kasidiaris" + }, + { + "name": "Patil Arpith" + }, + { + "name": "Patrick Stoica" + }, + { + "name": "Patrick Strawderman" + }, + { + "name": "Paul Garvin" + }, + { + "name": "Paul Ivanov" + }, + { + "name": "Paul Masson" + }, + { + "name": "Pavel" + }, + { + "name": "Pavel Feldman" + }, + { + "name": "Pavel Petržela" + }, + { + "name": "Pavel Strashkin" + }, + { + "name": "Paweł Bartkiewicz" + }, + { + "name": "peteguhl" + }, + { + "name": "peter" + }, + { + "name": "Peter Flynn" + }, + { + "name": "peterkroon" + }, + { + "name": "Peter Kroon" + }, + { + "name": "Philipp A" + }, + { + "name": "Philipp Markovics" + }, + { + "name": "Philip Stadermann" + }, + { + "name": "Pi Delport" + }, + { + "name": "Pierre Gerold" + }, + { + "name": "Pieter Ouwerkerk" + }, + { + "name": "Pontus Melke" + }, + { + "name": "prasanthj" + }, + { + "name": "Prasanth J" + }, + { + "name": "Prayag Verma" + }, + { + "name": "prendota" + }, + { + "name": "Prendota" + }, + { + "name": "Qiang Li" + }, + { + "name": "Radek Piórkowski" + }, + { + "name": "Rahul" + }, + { + "name": "Rahul Anand" + }, + { + "name": "ramwin1" + }, + { + "name": "Randall Mason" + }, + { + "name": "Randy Burden" + }, + { + "name": "Randy Edmunds" + }, + { + "name": "Randy Luecke" + }, + { + "name": "Raphael Amorim" + }, + { + "name": "Rasmus Erik Voel Jensen" + }, + { + "name": "Rasmus Schultz" + }, + { + "name": "raymondf" + }, + { + "name": "Raymond Hill" + }, + { + "name": "ray ratchup" + }, + { + "name": "Ray Ratchup" + }, + { + "name": "Remi Nyborg" + }, + { + "name": "Renaud Durlin" + }, + { + "name": "Reynold Xin" + }, + { + "name": "Richard Denton" + }, + { + "name": "Richard van der Meer" + }, + { + "name": "Richard Z.H. Wang" + }, + { + "name": "Rishi Goomar" + }, + { + "name": "Robert Brignull" + }, + { + "name": "Robert Crossfield" + }, + { + "name": "Robert Martin" + }, + { + "name": "Roberto Abdelkader Martínez Pérez" + }, + { + "name": "robertop23" + }, + { + "name": "Robert Plummer" + }, + { + "name": "Rrandom" + }, + { + "name": "Rrrandom" + }, + { + "name": "Ruslan Osmanov" + }, + { + "name": "Ryan Pangrle" + }, + { + "name": "Ryan Petrello" + }, + { + "name": "Ryan Prior" + }, + { + "name": "ryu-sato" + }, + { + "name": "sabaca" + }, + { + "name": "Sam Lee" + }, + { + "name": "Sam Rawlins" + }, + { + "name": "Samuel Ainsworth" + }, + { + "name": "Sam Wilson" + }, + { + "name": "sandeepshetty" + }, + { + "name": "Sander AKA Redsandro" + }, + { + "name": "Sander Verweij" + }, + { + "name": "santec" + }, + { + "name": "Sarah McAlear and Wenlin Zhang" + }, + { + "name": "Sascha Peilicke" + }, + { + "name": "Sasha Varlamov" + }, + { + "name": "satamas" + }, + { + "name": "satchmorun" + }, + { + "name": "sathyamoorthi" + }, + { + "name": "Saul Costa" + }, + { + "name": "S. Chris Colbert" + }, + { + "name": "SCLINIC\\jdecker" + }, + { + "name": "Scott Aikin" + }, + { + "name": "Scott Feeney" + }, + { + "name": "Scott Goodhew" + }, + { + "name": "Seb35" + }, + { + "name": "Sebastian Wilzbach" + }, + { + "name": "Sebastian Zaha" + }, + { + "name": "Seren D" + }, + { + "name": "Sergey Goder" + }, + { + "name": "Sergey Tselovalnikov" + }, + { + "name": "Se-Won Kim" + }, + { + "name": "Shane Liesegang" + }, + { + "name": "shaund" + }, + { + "name": "shaun gilchrist" + }, + { + "name": "Shawn A" + }, + { + "name": "Shea Bunge" + }, + { + "name": "sheopory" + }, + { + "name": "Shil S" + }, + { + "name": "Shiv Deepak" + }, + { + "name": "Shmuel Englard" + }, + { + "name": "Shubham Jain" + }, + { + "name": "Siamak Mokhtari" + }, + { + "name": "silverwind" + }, + { + "name": "Simon Edwards" + }, + { + "name": "sinkuu" + }, + { + "name": "snasa" + }, + { + "name": "soliton4" + }, + { + "name": "sonson" + }, + { + "name": "Sorab Bisht" + }, + { + "name": "spastorelli" + }, + { + "name": "srajanpaliwal" + }, + { + "name": "Stanislav Oaserele" + }, + { + "name": "stan-z" + }, + { + "name": "Stas Kobzar" + }, + { + "name": "Stefan Borsje" + }, + { + "name": "Steffen Beyer" + }, + { + "name": "Steffen Bruchmann" + }, + { + "name": "Steffen Kowalski" + }, + { + "name": "Stephane Moore" + }, + { + "name": "Stephen Lavelle" + }, + { + "name": "Steve Champagne" + }, + { + "name": "Steve Hoover" + }, + { + "name": "Steve O'Hara" + }, + { + "name": "stockiNail" + }, + { + "name": "stoskov" + }, + { + "name": "Stryder Crown" + }, + { + "name": "Stu Kennedy" + }, + { + "name": "Sungho Kim" + }, + { + "name": "sverweij" + }, + { + "name": "Taha Jahangir" + }, + { + "name": "takamori" + }, + { + "name": "Tako Schotanus" + }, + { + "name": "Takuji Shimokawa" + }, + { + "name": "Takuya Matsuyama" + }, + { + "name": "Tarmil" + }, + { + "name": "TDaglis" + }, + { + "name": "tel" + }, + { + "name": "Tentone" + }, + { + "name": "tfjgeorge" + }, + { + "name": "Thaddee Tyl" + }, + { + "name": "thanasis" + }, + { + "name": "TheHowl" + }, + { + "name": "themrmax" + }, + { + "name": "think" + }, + { + "name": "Thomas Brouard" + }, + { + "name": "Thomas Dvornik" + }, + { + "name": "Thomas Kluyver" + }, + { + "name": "thomasmaclean" + }, + { + "name": "Thomas Schmid" + }, + { + "name": "Tim Alby" + }, + { + "name": "Tim Baumann" + }, + { + "name": "Timothy Farrell" + }, + { + "name": "Timothy Gu" + }, + { + "name": "Timothy Hatcher" + }, + { + "name": "Tobias Bertelsen" + }, + { + "name": "TobiasBg" + }, + { + "name": "Todd Berman" + }, + { + "name": "Todd Kennedy" + }, + { + "name": "Tomas-A" + }, + { + "name": "Tomas Varaneckas" + }, + { + "name": "Tom Erik Støwer" + }, + { + "name": "Tom Klancer" + }, + { + "name": "Tom MacWright" + }, + { + "name": "Tom McLaughlin" + }, + { + "name": "Tony Jian" + }, + { + "name": "tophf" + }, + { + "name": "Torgeir Thoresen" + }, + { + "name": "totalamd" + }, + { + "name": "Travis Heppe" + }, + { + "name": "Triangle717" + }, + { + "name": "Tristan Tarrant" + }, + { + "name": "TSUYUSATO Kitsune" + }, + { + "name": "Tugrul Elmas" + }, + { + "name": "twifkak" + }, + { + "name": "Tyler Long" + }, + { + "name": "Tyler Makaro" + }, + { + "name": "Vadim Dyachenko" + }, + { + "name": "Vadzim Ramanenka" + }, + { + "name": "Vaibhav Sagar" + }, + { + "name": "VapidWorx" + }, + { + "name": "Vestimir Markov" + }, + { + "name": "vf" + }, + { + "name": "Victor Bocharsky" + }, + { + "name": "Vincent Woo" + }, + { + "name": "Volker Mische" + }, + { + "name": "vtripolitakis" + }, + { + "name": "wdouglashall" + }, + { + "name": "Weiyan Shao" + }, + { + "name": "wenli" + }, + { + "name": "Wes Cossick" + }, + { + "name": "Wesley Wiser" + }, + { + "name": "Weston Ruter" + }, + { + "name": "Will Binns-Smith" + }, + { + "name": "Will Dean" + }, + { + "name": "William Desportes" + }, + { + "name": "William Jamieson" + }, + { + "name": "William Stein" + }, + { + "name": "Willy" + }, + { + "name": "Wojtek Ptak" + }, + { + "name": "wonderboyjon" + }, + { + "name": "Wu Cheng-Han" + }, + { + "name": "Xavier Mendez" + }, + { + "name": "Yang Guo" + }, + { + "name": "Yassin N. Hassan" + }, + { + "name": "YNH Webdev" + }, + { + "name": "yoongu" + }, + { + "name": "Yunchi Luo" + }, + { + "name": "Yuvi Panda" + }, + { + "name": "Yvonnick Esnault" + }, + { + "name": "Zac Anger" + }, + { + "name": "Zachary Dremann" + }, + { + "name": "Zeno Rocha" + }, + { + "name": "Zhang Hao" + }, + { + "name": "Ziv" + }, + { + "name": "zoobestik" + }, + { + "name": "zziuni" + }, + { + "name": "魏鹏刚" + } + ], + "deprecated": false, + "description": "Full-featured in-browser code editor", + "devDependencies": { + "blint": "^1", + "node-static": "0.7.11", + "phantomjs-prebuilt": "^2.1.12", + "rollup": "^0.66.2", + "rollup-plugin-buble": "^0.19.2", + "rollup-watch": "^4.3.1" + }, + "directories": { + "lib": "./lib" + }, + "homepage": "https://codemirror.net", + "jspm": { + "directories": {}, + "dependencies": {}, + "devDependencies": {} + }, + "keywords": [ + "JavaScript", + "CodeMirror", + "Editor" + ], + "license": "MIT", + "main": "lib/codemirror.js", + "name": "codemirror", + "repository": { + "type": "git", + "url": "git+https://github.com/codemirror/CodeMirror.git" + }, + "scripts": { + "build": "rollup -c", + "lint": "bin/lint", + "prepare": "npm run-script build", + "test": "node ./test/run.js", + "watch": "rollup -w -c" + }, + "style": "lib/codemirror.css", + "version": "5.49.2" +} diff --git a/docs/js/node_modules/codemirror/rollup.config.js b/docs/js/node_modules/codemirror/rollup.config.js new file mode 100644 index 000000000..fbb435717 --- /dev/null +++ b/docs/js/node_modules/codemirror/rollup.config.js @@ -0,0 +1,20 @@ +import buble from 'rollup-plugin-buble'; + +export default { + input: "src/codemirror.js", + output: { + banner: `// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// This is CodeMirror (https://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . +`, + format: "umd", + file: "lib/codemirror.js", + name: "CodeMirror" + }, + plugins: [ buble({namedFunctionExpressions: false}) ] +}; diff --git a/docs/js/node_modules/codemirror/src/codemirror.js b/docs/js/node_modules/codemirror/src/codemirror.js new file mode 100644 index 000000000..2a2f54e4c --- /dev/null +++ b/docs/js/node_modules/codemirror/src/codemirror.js @@ -0,0 +1,3 @@ +import { CodeMirror } from "./edit/main.js" + +export default CodeMirror diff --git a/docs/js/node_modules/codemirror/src/display/Display.js b/docs/js/node_modules/codemirror/src/display/Display.js new file mode 100644 index 000000000..d57f00bdd --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/Display.js @@ -0,0 +1,110 @@ +import { gecko, ie, ie_version, mobile, webkit } from "../util/browser.js" +import { elt, eltP } from "../util/dom.js" +import { scrollerGap } from "../util/misc.js" +import { getGutters, renderGutters } from "./gutters.js" + +// The display handles the DOM integration, both for input reading +// and content drawing. It holds references to DOM nodes and +// display-related state. + +export function Display(place, doc, input, options) { + let d = this + this.input = input + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler") + d.scrollbarFiller.setAttribute("cm-not-content", "true") + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler") + d.gutterFiller.setAttribute("cm-not-content", "true") + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = eltP("div", null, "CodeMirror-code") + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1") + d.cursorDiv = elt("div", null, "CodeMirror-cursors") + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure") + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure") + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none") + let lines = eltP("div", [d.lineSpace], "CodeMirror-lines") + // Moved around its parent to cover visible view. + d.mover = elt("div", [lines], null, "position: relative") + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer") + d.sizerWidth = null + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;") + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters") + d.lineGutter = null + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll") + d.scroller.setAttribute("tabIndex", "-1") + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror") + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0 } + if (!webkit && !(gecko && mobile)) d.scroller.draggable = true + + if (place) { + if (place.appendChild) place.appendChild(d.wrapper) + else place(d.wrapper) + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first + d.reportedViewFrom = d.reportedViewTo = doc.first + // Information about the rendered lines. + d.view = [] + d.renderedView = null + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null + // Empty space (in pixels) above the view + d.viewOffset = 0 + d.lastWrapHeight = d.lastWrapWidth = 0 + d.updateLineNumbers = null + + d.nativeBarWidth = d.barHeight = d.barWidth = 0 + d.scrollbarsClipped = false + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null + d.maxLineLength = 0 + d.maxLineChanged = false + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null + + // True when shift is held down. + d.shift = false + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null + + d.activeTouch = null + + d.gutterSpecs = getGutters(options.gutters, options.lineNumbers) + renderGutters(d) + + input.init(d) +} diff --git a/docs/js/node_modules/codemirror/src/display/focus.js b/docs/js/node_modules/codemirror/src/display/focus.js new file mode 100644 index 000000000..aa731b435 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/focus.js @@ -0,0 +1,47 @@ +import { restartBlink } from "./selection.js" +import { webkit } from "../util/browser.js" +import { addClass, rmClass } from "../util/dom.js" +import { signal } from "../util/event.js" + +export function ensureFocus(cm) { + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm) } +} + +export function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true + setTimeout(() => { if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false + onBlur(cm) + } }, 100) +} + +export function onFocus(cm, e) { + if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false + + if (cm.options.readOnly == "nocursor") return + if (!cm.state.focused) { + signal(cm, "focus", cm, e) + cm.state.focused = true + addClass(cm.display.wrapper, "CodeMirror-focused") + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset() + if (webkit) setTimeout(() => cm.display.input.reset(true), 20) // Issue #1730 + } + cm.display.input.receivedFocus() + } + restartBlink(cm) +} +export function onBlur(cm, e) { + if (cm.state.delayingBlurEvent) return + + if (cm.state.focused) { + signal(cm, "blur", cm, e) + cm.state.focused = false + rmClass(cm.display.wrapper, "CodeMirror-focused") + } + clearInterval(cm.display.blinker) + setTimeout(() => { if (!cm.state.focused) cm.display.shift = false }, 150) +} diff --git a/docs/js/node_modules/codemirror/src/display/gutters.js b/docs/js/node_modules/codemirror/src/display/gutters.js new file mode 100644 index 000000000..b27b6ce77 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/gutters.js @@ -0,0 +1,44 @@ +import { elt, removeChildren } from "../util/dom.js" +import { regChange } from "./view_tracking.js" +import { alignHorizontally } from "./line_numbers.js" +import { updateGutterSpace } from "./update_display.js" + +export function getGutters(gutters, lineNumbers) { + let result = [], sawLineNumbers = false + for (let i = 0; i < gutters.length; i++) { + let name = gutters[i], style = null + if (typeof name != "string") { style = name.style; name = name.className } + if (name == "CodeMirror-linenumbers") { + if (!lineNumbers) continue + else sawLineNumbers = true + } + result.push({className: name, style}) + } + if (lineNumbers && !sawLineNumbers) result.push({className: "CodeMirror-linenumbers", style: null}) + return result +} + +// Rebuild the gutter elements, ensure the margin to the left of the +// code matches their width. +export function renderGutters(display) { + let gutters = display.gutters, specs = display.gutterSpecs + removeChildren(gutters) + display.lineGutter = null + for (let i = 0; i < specs.length; ++i) { + let {className, style} = specs[i] + let gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + className)) + if (style) gElt.style.cssText = style + if (className == "CodeMirror-linenumbers") { + display.lineGutter = gElt + gElt.style.width = (display.lineNumWidth || 1) + "px" + } + } + gutters.style.display = specs.length ? "" : "none" + updateGutterSpace(display) +} + +export function updateGutters(cm) { + renderGutters(cm.display) + regChange(cm) + alignHorizontally(cm) +} diff --git a/docs/js/node_modules/codemirror/src/display/highlight_worker.js b/docs/js/node_modules/codemirror/src/display/highlight_worker.js new file mode 100644 index 000000000..606981571 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/highlight_worker.js @@ -0,0 +1,55 @@ +import { getContextBefore, highlightLine, processLine } from "../line/highlight.js" +import { copyState } from "../modes.js" +import { bind } from "../util/misc.js" + +import { runInOp } from "./operations.js" +import { regLineChange } from "./view_tracking.js" + +// HIGHLIGHT WORKER + +export function startWorker(cm, time) { + if (cm.doc.highlightFrontier < cm.display.viewTo) + cm.state.highlight.set(time, bind(highlightWorker, cm)) +} + +function highlightWorker(cm) { + let doc = cm.doc + if (doc.highlightFrontier >= cm.display.viewTo) return + let end = +new Date + cm.options.workTime + let context = getContextBefore(cm, doc.highlightFrontier) + let changedLines = [] + + doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), line => { + if (context.line >= cm.display.viewFrom) { // Visible + let oldStyles = line.styles + let resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null + let highlighted = highlightLine(cm, line, context, true) + if (resetState) context.state = resetState + line.styles = highlighted.styles + let oldCls = line.styleClasses, newCls = highlighted.classes + if (newCls) line.styleClasses = newCls + else if (oldCls) line.styleClasses = null + let ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass) + for (let i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i] + if (ischange) changedLines.push(context.line) + line.stateAfter = context.save() + context.nextLine() + } else { + if (line.text.length <= cm.options.maxHighlightLength) + processLine(cm, line.text, context) + line.stateAfter = context.line % 5 == 0 ? context.save() : null + context.nextLine() + } + if (+new Date > end) { + startWorker(cm, cm.options.workDelay) + return true + } + }) + doc.highlightFrontier = context.line + doc.modeFrontier = Math.max(doc.modeFrontier, context.line) + if (changedLines.length) runInOp(cm, () => { + for (let i = 0; i < changedLines.length; i++) + regLineChange(cm, changedLines[i], "text") + }) +} diff --git a/docs/js/node_modules/codemirror/src/display/line_numbers.js b/docs/js/node_modules/codemirror/src/display/line_numbers.js new file mode 100644 index 000000000..073cbade0 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/line_numbers.js @@ -0,0 +1,48 @@ +import { lineNumberFor } from "../line/utils_line.js" +import { compensateForHScroll } from "../measurement/position_measurement.js" +import { elt } from "../util/dom.js" + +import { updateGutterSpace } from "./update_display.js" + +// Re-align line numbers and gutter marks to compensate for +// horizontal scrolling. +export function alignHorizontally(cm) { + let display = cm.display, view = display.view + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return + let comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft + let gutterW = display.gutters.offsetWidth, left = comp + "px" + for (let i = 0; i < view.length; i++) if (!view[i].hidden) { + if (cm.options.fixedGutter) { + if (view[i].gutter) + view[i].gutter.style.left = left + if (view[i].gutterBackground) + view[i].gutterBackground.style.left = left + } + let align = view[i].alignable + if (align) for (let j = 0; j < align.length; j++) + align[j].style.left = left + } + if (cm.options.fixedGutter) + display.gutters.style.left = (comp + gutterW) + "px" +} + +// Used to ensure that the line number gutter is still the right +// size for the current document size. Returns true when an update +// is needed. +export function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) return false + let doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display + if (last.length != display.lineNumChars) { + let test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")) + let innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW + display.lineGutter.style.width = "" + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1 + display.lineNumWidth = display.lineNumInnerWidth + padding + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1 + display.lineGutter.style.width = display.lineNumWidth + "px" + updateGutterSpace(cm.display) + return true + } + return false +} diff --git a/docs/js/node_modules/codemirror/src/display/mode_state.js b/docs/js/node_modules/codemirror/src/display/mode_state.js new file mode 100644 index 000000000..5d8ebf250 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/mode_state.js @@ -0,0 +1,22 @@ +import { getMode } from "../modes.js" + +import { startWorker } from "./highlight_worker.js" +import { regChange } from "./view_tracking.js" + +// Used to get the editor into a consistent state again when options change. + +export function loadMode(cm) { + cm.doc.mode = getMode(cm.options, cm.doc.modeOption) + resetModeState(cm) +} + +export function resetModeState(cm) { + cm.doc.iter(line => { + if (line.stateAfter) line.stateAfter = null + if (line.styles) line.styles = null + }) + cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first + startWorker(cm, 100) + cm.state.modeGen++ + if (cm.curOp) regChange(cm) +} diff --git a/docs/js/node_modules/codemirror/src/display/operations.js b/docs/js/node_modules/codemirror/src/display/operations.js new file mode 100644 index 000000000..6f3c9d086 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/operations.js @@ -0,0 +1,205 @@ +import { clipPos } from "../line/pos.js" +import { findMaxLine } from "../line/spans.js" +import { displayWidth, measureChar, scrollGap } from "../measurement/position_measurement.js" +import { signal } from "../util/event.js" +import { activeElt } from "../util/dom.js" +import { finishOperation, pushOperation } from "../util/operation_group.js" + +import { ensureFocus } from "./focus.js" +import { measureForScrollbars, updateScrollbars } from "./scrollbars.js" +import { restartBlink } from "./selection.js" +import { maybeScrollWindow, scrollPosIntoView, setScrollLeft, setScrollTop } from "./scrolling.js" +import { DisplayUpdate, maybeClipScrollbars, postUpdateDisplay, setDocumentHeight, updateDisplayIfNeeded } from "./update_display.js" +import { updateHeightsInViewport } from "./update_lines.js" + +// Operations are used to wrap a series of changes to the editor +// state in such a way that each change won't have to update the +// cursor and display (which would be awkward, slow, and +// error-prone). Instead, display updates are batched and then all +// combined and executed at once. + +let nextOpId = 0 +// Start a new operation. +export function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: 0, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId // Unique ID + } + pushOperation(cm.curOp) +} + +// Finish an operation, updating the display and signalling delayed events +export function endOperation(cm) { + let op = cm.curOp + if (op) finishOperation(op, group => { + for (let i = 0; i < group.ops.length; i++) + group.ops[i].cm.curOp = null + endOperations(group) + }) +} + +// The DOM updates done when an operation finishes are batched so +// that the minimum number of relayouts are required. +function endOperations(group) { + let ops = group.ops + for (let i = 0; i < ops.length; i++) // Read DOM + endOperation_R1(ops[i]) + for (let i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W1(ops[i]) + for (let i = 0; i < ops.length; i++) // Read DOM + endOperation_R2(ops[i]) + for (let i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W2(ops[i]) + for (let i = 0; i < ops.length; i++) // Read DOM + endOperation_finish(ops[i]) +} + +function endOperation_R1(op) { + let cm = op.cm, display = cm.display + maybeClipScrollbars(cm) + if (op.updateMaxLine) findMaxLine(cm) + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate) +} + +function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update) +} + +function endOperation_R2(op) { + let cm = op.cm, display = cm.display + if (op.updatedDisplay) updateHeightsInViewport(cm) + + op.barMeasure = measureForScrollbars(cm) + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3 + cm.display.sizerWidth = op.adjustWidthTo + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth) + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)) + } + + if (op.updatedDisplay || op.selectionChanged) + op.preparedSelection = display.input.prepareSelection() +} + +function endOperation_W2(op) { + let cm = op.cm + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px" + if (op.maxScrollLeft < cm.doc.scrollLeft) + setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true) + cm.display.maxLineChanged = false + } + + let takeFocus = op.focus && op.focus == activeElt() + if (op.preparedSelection) + cm.display.input.showSelection(op.preparedSelection, takeFocus) + if (op.updatedDisplay || op.startHeight != cm.doc.height) + updateScrollbars(cm, op.barMeasure) + if (op.updatedDisplay) + setDocumentHeight(cm, op.barMeasure) + + if (op.selectionChanged) restartBlink(cm) + + if (cm.state.focused && op.updateInput) + cm.display.input.reset(op.typing) + if (takeFocus) ensureFocus(op.cm) +} + +function endOperation_finish(op) { + let cm = op.cm, display = cm.display, doc = cm.doc + + if (op.updatedDisplay) postUpdateDisplay(cm, op.update) + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + display.wheelStartX = display.wheelStartY = null + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null) setScrollTop(cm, op.scrollTop, op.forceScroll) + + if (op.scrollLeft != null) setScrollLeft(cm, op.scrollLeft, true, true) + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + let rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin) + maybeScrollWindow(cm, rect) + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + let hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers + if (hidden) for (let i = 0; i < hidden.length; ++i) + if (!hidden[i].lines.length) signal(hidden[i], "hide") + if (unhidden) for (let i = 0; i < unhidden.length; ++i) + if (unhidden[i].lines.length) signal(unhidden[i], "unhide") + + if (display.wrapper.offsetHeight) + doc.scrollTop = cm.display.scroller.scrollTop + + // Fire change events, and delayed event handlers + if (op.changeObjs) + signal(cm, "changes", cm, op.changeObjs) + if (op.update) + op.update.finish() +} + +// Run the given function in an operation +export function runInOp(cm, f) { + if (cm.curOp) return f() + startOperation(cm) + try { return f() } + finally { endOperation(cm) } +} +// Wraps a function in an operation. Returns the wrapped function. +export function operation(cm, f) { + return function() { + if (cm.curOp) return f.apply(cm, arguments) + startOperation(cm) + try { return f.apply(cm, arguments) } + finally { endOperation(cm) } + } +} +// Used to add methods to editor and doc instances, wrapping them in +// operations. +export function methodOp(f) { + return function() { + if (this.curOp) return f.apply(this, arguments) + startOperation(this) + try { return f.apply(this, arguments) } + finally { endOperation(this) } + } +} +export function docMethodOp(f) { + return function() { + let cm = this.cm + if (!cm || cm.curOp) return f.apply(this, arguments) + startOperation(cm) + try { return f.apply(this, arguments) } + finally { endOperation(cm) } + } +} diff --git a/docs/js/node_modules/codemirror/src/display/scroll_events.js b/docs/js/node_modules/codemirror/src/display/scroll_events.js new file mode 100644 index 000000000..fbed42663 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/scroll_events.js @@ -0,0 +1,115 @@ +import { chrome, gecko, ie, mac, presto, safari, webkit } from "../util/browser.js" +import { e_preventDefault } from "../util/event.js" + +import { updateDisplaySimple } from "./update_display.js" +import { setScrollLeft, updateScrollTop } from "./scrolling.js" + +// Since the delta values reported on mouse wheel events are +// unstandardized between browsers and even browser versions, and +// generally horribly unpredictable, this code starts by measuring +// the scroll effect that the first few mouse wheel events have, +// and, from that, detects the way it can convert deltas to pixel +// offsets afterwards. +// +// The reason we want to know the amount a wheel event will scroll +// is that it gives us a chance to update the display before the +// actual scrolling happens, reducing flickering. + +let wheelSamples = 0, wheelPixelsPerUnit = null +// Fill in a browser-detected starting value on browsers where we +// know one. These don't have to be accurate -- the result of them +// being wrong would just be a slight flicker on the first wheel +// scroll (if it is large enough). +if (ie) wheelPixelsPerUnit = -.53 +else if (gecko) wheelPixelsPerUnit = 15 +else if (chrome) wheelPixelsPerUnit = -.7 +else if (safari) wheelPixelsPerUnit = -1/3 + +function wheelEventDelta(e) { + let dx = e.wheelDeltaX, dy = e.wheelDeltaY + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail + else if (dy == null) dy = e.wheelDelta + return {x: dx, y: dy} +} +export function wheelEventPixels(e) { + let delta = wheelEventDelta(e) + delta.x *= wheelPixelsPerUnit + delta.y *= wheelPixelsPerUnit + return delta +} + +export function onScrollWheel(cm, e) { + let delta = wheelEventDelta(e), dx = delta.x, dy = delta.y + + let display = cm.display, scroll = display.scroller + // Quit if there's nothing to scroll here + let canScrollX = scroll.scrollWidth > scroll.clientWidth + let canScrollY = scroll.scrollHeight > scroll.clientHeight + if (!(dx && canScrollX || dy && canScrollY)) return + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (let cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (let i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur + break outer + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy && canScrollY) + updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)) + setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)) + // Only prevent default scrolling if vertical scrolling is + // actually possible. Otherwise, it causes vertical scroll + // jitter on OSX trackpads when deltaX is small and deltaY + // is large (issue #3579) + if (!dy || (dy && canScrollY)) + e_preventDefault(e) + display.wheelStartX = null // Abort measurement, if in progress + return + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + let pixels = dy * wheelPixelsPerUnit + let top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight + if (pixels < 0) top = Math.max(0, top + pixels - 50) + else bot = Math.min(cm.doc.height, bot + pixels + 50) + updateDisplaySimple(cm, {top: top, bottom: bot}) + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop + display.wheelDX = dx; display.wheelDY = dy + setTimeout(() => { + if (display.wheelStartX == null) return + let movedX = scroll.scrollLeft - display.wheelStartX + let movedY = scroll.scrollTop - display.wheelStartY + let sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX) + display.wheelStartX = display.wheelStartY = null + if (!sample) return + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1) + ++wheelSamples + }, 200) + } else { + display.wheelDX += dx; display.wheelDY += dy + } + } +} diff --git a/docs/js/node_modules/codemirror/src/display/scrollbars.js b/docs/js/node_modules/codemirror/src/display/scrollbars.js new file mode 100644 index 000000000..18ac121a9 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/scrollbars.js @@ -0,0 +1,193 @@ +import { addClass, elt, rmClass } from "../util/dom.js" +import { on } from "../util/event.js" +import { scrollGap, paddingVert } from "../measurement/position_measurement.js" +import { ie, ie_version, mac, mac_geMountainLion } from "../util/browser.js" +import { updateHeightsInViewport } from "./update_lines.js" +import { Delayed } from "../util/misc.js" + +import { setScrollLeft, updateScrollTop } from "./scrolling.js" + +// SCROLLBARS + +// Prepare DOM reads needed to update the scrollbars. Done in one +// shot to minimize update/measure roundtrips. +export function measureForScrollbars(cm) { + let d = cm.display, gutterW = d.gutters.offsetWidth + let docH = Math.round(cm.doc.height + paddingVert(cm.display)) + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + } +} + +class NativeScrollbars { + constructor(place, scroll, cm) { + this.cm = cm + let vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar") + let horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar") + vert.tabIndex = horiz.tabIndex = -1 + place(vert); place(horiz) + + on(vert, "scroll", () => { + if (vert.clientHeight) scroll(vert.scrollTop, "vertical") + }) + on(horiz, "scroll", () => { + if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal") + }) + + this.checkedZeroWidth = false + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px" + } + + update(measure) { + let needsH = measure.scrollWidth > measure.clientWidth + 1 + let needsV = measure.scrollHeight > measure.clientHeight + 1 + let sWidth = measure.nativeBarWidth + + if (needsV) { + this.vert.style.display = "block" + this.vert.style.bottom = needsH ? sWidth + "px" : "0" + let totalHeight = measure.viewHeight - (needsH ? sWidth : 0) + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px" + } else { + this.vert.style.display = "" + this.vert.firstChild.style.height = "0" + } + + if (needsH) { + this.horiz.style.display = "block" + this.horiz.style.right = needsV ? sWidth + "px" : "0" + this.horiz.style.left = measure.barLeft + "px" + let totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0) + this.horiz.firstChild.style.width = + Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px" + } else { + this.horiz.style.display = "" + this.horiz.firstChild.style.width = "0" + } + + if (!this.checkedZeroWidth && measure.clientHeight > 0) { + if (sWidth == 0) this.zeroWidthHack() + this.checkedZeroWidth = true + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} + } + + setScrollLeft(pos) { + if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos + if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz") + } + + setScrollTop(pos) { + if (this.vert.scrollTop != pos) this.vert.scrollTop = pos + if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert, "vert") + } + + zeroWidthHack() { + let w = mac && !mac_geMountainLion ? "12px" : "18px" + this.horiz.style.height = this.vert.style.width = w + this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none" + this.disableHoriz = new Delayed + this.disableVert = new Delayed + } + + enableZeroWidthBar(bar, delay, type) { + bar.style.pointerEvents = "auto" + function maybeDisable() { + // To find out whether the scrollbar is still visible, we + // check whether the element under the pixel in the bottom + // right corner of the scrollbar box is the scrollbar box + // itself (when the bar is still visible) or its filler child + // (when the bar is hidden). If it is still visible, we keep + // it enabled, if it's hidden, we disable pointer events. + let box = bar.getBoundingClientRect() + let elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) + : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1) + if (elt != bar) bar.style.pointerEvents = "none" + else delay.set(1000, maybeDisable) + } + delay.set(1000, maybeDisable) + } + + clear() { + let parent = this.horiz.parentNode + parent.removeChild(this.horiz) + parent.removeChild(this.vert) + } +} + +class NullScrollbars { + update() { return {bottom: 0, right: 0} } + setScrollLeft() {} + setScrollTop() {} + clear() {} +} + +export function updateScrollbars(cm, measure) { + if (!measure) measure = measureForScrollbars(cm) + let startWidth = cm.display.barWidth, startHeight = cm.display.barHeight + updateScrollbarsInner(cm, measure) + for (let i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + updateHeightsInViewport(cm) + updateScrollbarsInner(cm, measureForScrollbars(cm)) + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight + } +} + +// Re-synchronize the fake scrollbars with the actual size of the +// content. +function updateScrollbarsInner(cm, measure) { + let d = cm.display + let sizes = d.scrollbars.update(measure) + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px" + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px" + d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block" + d.scrollbarFiller.style.height = sizes.bottom + "px" + d.scrollbarFiller.style.width = sizes.right + "px" + } else d.scrollbarFiller.style.display = "" + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block" + d.gutterFiller.style.height = sizes.bottom + "px" + d.gutterFiller.style.width = measure.gutterWidth + "px" + } else d.gutterFiller.style.display = "" +} + +export let scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars} + +export function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear() + if (cm.display.scrollbars.addClass) + rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) + } + + cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](node => { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller) + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", () => { + if (cm.state.focused) setTimeout(() => cm.display.input.focus(), 0) + }) + node.setAttribute("cm-not-content", "true") + }, (pos, axis) => { + if (axis == "horizontal") setScrollLeft(cm, pos) + else updateScrollTop(cm, pos) + }, cm) + if (cm.display.scrollbars.addClass) + addClass(cm.display.wrapper, cm.display.scrollbars.addClass) +} diff --git a/docs/js/node_modules/codemirror/src/display/scrolling.js b/docs/js/node_modules/codemirror/src/display/scrolling.js new file mode 100644 index 000000000..26ec993b0 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/scrolling.js @@ -0,0 +1,184 @@ +import { Pos } from "../line/pos.js" +import { cursorCoords, displayHeight, displayWidth, estimateCoords, paddingTop, paddingVert, scrollGap, textHeight } from "../measurement/position_measurement.js" +import { gecko, phantom } from "../util/browser.js" +import { elt } from "../util/dom.js" +import { signalDOMEvent } from "../util/event.js" + +import { startWorker } from "./highlight_worker.js" +import { alignHorizontally } from "./line_numbers.js" +import { updateDisplaySimple } from "./update_display.js" + +// SCROLLING THINGS INTO VIEW + +// If an editor sits on the top or bottom of the window, partially +// scrolled out of view, this ensures that the cursor is visible. +export function maybeScrollWindow(cm, rect) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) return + + let display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null + if (rect.top + box.top < 0) doScroll = true + else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false + if (doScroll != null && !phantom) { + let scrollNode = elt("div", "\u200b", null, `position: absolute; + top: ${rect.top - display.viewOffset - paddingTop(cm.display)}px; + height: ${rect.bottom - rect.top + scrollGap(cm) + display.barHeight}px; + left: ${rect.left}px; width: ${Math.max(2, rect.right - rect.left)}px;`) + cm.display.lineSpace.appendChild(scrollNode) + scrollNode.scrollIntoView(doScroll) + cm.display.lineSpace.removeChild(scrollNode) + } +} + +// Scroll a given position into view (immediately), verifying that +// it actually became visible (as line heights are accurately +// measured, the position of something may 'drift' during drawing). +export function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) margin = 0 + let rect + if (!cm.options.lineWrapping && pos == end) { + // Set pos and end to the cursor positions around the character pos sticks to + // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch + // If pos == Pos(_, 0, "before"), pos and end are unchanged + pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos + end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos + } + for (let limit = 0; limit < 5; limit++) { + let changed = false + let coords = cursorCoords(cm, pos) + let endCoords = !end || end == pos ? coords : cursorCoords(cm, end) + rect = {left: Math.min(coords.left, endCoords.left), + top: Math.min(coords.top, endCoords.top) - margin, + right: Math.max(coords.left, endCoords.left), + bottom: Math.max(coords.bottom, endCoords.bottom) + margin} + let scrollPos = calculateScrollPos(cm, rect) + let startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft + if (scrollPos.scrollTop != null) { + updateScrollTop(cm, scrollPos.scrollTop) + if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft) + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true + } + if (!changed) break + } + return rect +} + +// Scroll a given set of coordinates into view (immediately). +export function scrollIntoView(cm, rect) { + let scrollPos = calculateScrollPos(cm, rect) + if (scrollPos.scrollTop != null) updateScrollTop(cm, scrollPos.scrollTop) + if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft) +} + +// Calculate a new scroll position needed to scroll the given +// rectangle into view. Returns an object with scrollTop and +// scrollLeft properties. When these are undefined, the +// vertical/horizontal position does not need to be adjusted. +function calculateScrollPos(cm, rect) { + let display = cm.display, snapMargin = textHeight(cm.display) + if (rect.top < 0) rect.top = 0 + let screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop + let screen = displayHeight(cm), result = {} + if (rect.bottom - rect.top > screen) rect.bottom = rect.top + screen + let docBottom = cm.doc.height + paddingVert(display) + let atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin + if (rect.top < screentop) { + result.scrollTop = atTop ? 0 : rect.top + } else if (rect.bottom > screentop + screen) { + let newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen) + if (newTop != screentop) result.scrollTop = newTop + } + + let screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft + let screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0) + let tooWide = rect.right - rect.left > screenw + if (tooWide) rect.right = rect.left + screenw + if (rect.left < 10) + result.scrollLeft = 0 + else if (rect.left < screenleft) + result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)) + else if (rect.right > screenw + screenleft - 3) + result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw + return result +} + +// Store a relative adjustment to the scroll position in the current +// operation (to be applied when the operation finishes). +export function addToScrollTop(cm, top) { + if (top == null) return + resolveScrollToPos(cm) + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top +} + +// Make sure that at the end of the operation the current cursor is +// shown. +export function ensureCursorVisible(cm) { + resolveScrollToPos(cm) + let cur = cm.getCursor() + cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin} +} + +export function scrollToCoords(cm, x, y) { + if (x != null || y != null) resolveScrollToPos(cm) + if (x != null) cm.curOp.scrollLeft = x + if (y != null) cm.curOp.scrollTop = y +} + +export function scrollToRange(cm, range) { + resolveScrollToPos(cm) + cm.curOp.scrollToPos = range +} + +// When an operation has its scrollToPos property set, and another +// scroll action is applied before the end of the operation, this +// 'simulates' scrolling that position into view in a cheap way, so +// that the effect of intermediate scroll commands is not ignored. +function resolveScrollToPos(cm) { + let range = cm.curOp.scrollToPos + if (range) { + cm.curOp.scrollToPos = null + let from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to) + scrollToCoordsRange(cm, from, to, range.margin) + } +} + +export function scrollToCoordsRange(cm, from, to, margin) { + let sPos = calculateScrollPos(cm, { + left: Math.min(from.left, to.left), + top: Math.min(from.top, to.top) - margin, + right: Math.max(from.right, to.right), + bottom: Math.max(from.bottom, to.bottom) + margin + }) + scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop) +} + +// Sync the scrollable area and scrollbars, ensure the viewport +// covers the visible area. +export function updateScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) return + if (!gecko) updateDisplaySimple(cm, {top: val}) + setScrollTop(cm, val, true) + if (gecko) updateDisplaySimple(cm) + startWorker(cm, 100) +} + +export function setScrollTop(cm, val, forceScroll) { + val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val) + if (cm.display.scroller.scrollTop == val && !forceScroll) return + cm.doc.scrollTop = val + cm.display.scrollbars.setScrollTop(val) + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val +} + +// Sync scroller and scrollbar, ensure the gutter elements are +// aligned. +export function setScrollLeft(cm, val, isScroller, forceScroll) { + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth) + if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) return + cm.doc.scrollLeft = val + alignHorizontally(cm) + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val + cm.display.scrollbars.setScrollLeft(val) +} diff --git a/docs/js/node_modules/codemirror/src/display/selection.js b/docs/js/node_modules/codemirror/src/display/selection.js new file mode 100644 index 000000000..c658c0a27 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/selection.js @@ -0,0 +1,158 @@ +import { Pos } from "../line/pos.js" +import { visualLine } from "../line/spans.js" +import { getLine } from "../line/utils_line.js" +import { charCoords, cursorCoords, displayWidth, paddingH, wrappedLineExtentChar } from "../measurement/position_measurement.js" +import { getOrder, iterateBidiSections } from "../util/bidi.js" +import { elt } from "../util/dom.js" + +export function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()) +} + +export function prepareSelection(cm, primary = true) { + let doc = cm.doc, result = {} + let curFragment = result.cursors = document.createDocumentFragment() + let selFragment = result.selection = document.createDocumentFragment() + + for (let i = 0; i < doc.sel.ranges.length; i++) { + if (!primary && i == doc.sel.primIndex) continue + let range = doc.sel.ranges[i] + if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue + let collapsed = range.empty() + if (collapsed || cm.options.showCursorWhenSelecting) + drawSelectionCursor(cm, range.head, curFragment) + if (!collapsed) + drawSelectionRange(cm, range, selFragment) + } + return result +} + +// Draws a cursor for the given range +export function drawSelectionCursor(cm, head, output) { + let pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine) + + let cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")) + cursor.style.left = pos.left + "px" + cursor.style.top = pos.top + "px" + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px" + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + let otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")) + otherCursor.style.display = "" + otherCursor.style.left = pos.other.left + "px" + otherCursor.style.top = pos.other.top + "px" + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px" + } +} + +function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } + +// Draws the given range as a highlighted selection +function drawSelectionRange(cm, range, output) { + let display = cm.display, doc = cm.doc + let fragment = document.createDocumentFragment() + let padding = paddingH(cm.display), leftSide = padding.left + let rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right + let docLTR = doc.direction == "ltr" + + function add(left, top, width, bottom) { + if (top < 0) top = 0 + top = Math.round(top) + bottom = Math.round(bottom) + fragment.appendChild(elt("div", null, "CodeMirror-selected", `position: absolute; left: ${left}px; + top: ${top}px; width: ${width == null ? rightSide - left : width}px; + height: ${bottom - top}px`)) + } + + function drawForLine(line, fromArg, toArg) { + let lineObj = getLine(doc, line) + let lineLen = lineObj.text.length + let start, end + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias) + } + + function wrapX(pos, dir, side) { + let extent = wrappedLineExtentChar(cm, lineObj, null, pos) + let prop = (dir == "ltr") == (side == "after") ? "left" : "right" + let ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1) + return coords(ch, prop)[prop] + } + + let order = getOrder(lineObj, doc.direction) + iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir, i) => { + let ltr = dir == "ltr" + let fromPos = coords(from, ltr ? "left" : "right") + let toPos = coords(to - 1, ltr ? "right" : "left") + + let openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen + let first = i == 0, last = !order || i == order.length - 1 + if (toPos.top - fromPos.top <= 3) { // Single line + let openLeft = (docLTR ? openStart : openEnd) && first + let openRight = (docLTR ? openEnd : openStart) && last + let left = openLeft ? leftSide : (ltr ? fromPos : toPos).left + let right = openRight ? rightSide : (ltr ? toPos : fromPos).right + add(left, fromPos.top, right - left, fromPos.bottom) + } else { // Multiple lines + let topLeft, topRight, botLeft, botRight + if (ltr) { + topLeft = docLTR && openStart && first ? leftSide : fromPos.left + topRight = docLTR ? rightSide : wrapX(from, dir, "before") + botLeft = docLTR ? leftSide : wrapX(to, dir, "after") + botRight = docLTR && openEnd && last ? rightSide : toPos.right + } else { + topLeft = !docLTR ? leftSide : wrapX(from, dir, "before") + topRight = !docLTR && openStart && first ? rightSide : fromPos.right + botLeft = !docLTR && openEnd && last ? leftSide : toPos.left + botRight = !docLTR ? rightSide : wrapX(to, dir, "after") + } + add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom) + if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top) + add(botLeft, toPos.top, botRight - botLeft, toPos.bottom) + } + + if (!start || cmpCoords(fromPos, start) < 0) start = fromPos + if (cmpCoords(toPos, start) < 0) start = toPos + if (!end || cmpCoords(fromPos, end) < 0) end = fromPos + if (cmpCoords(toPos, end) < 0) end = toPos + }) + return {start: start, end: end} + } + + let sFrom = range.from(), sTo = range.to() + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch) + } else { + let fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line) + let singleVLine = visualLine(fromLine) == visualLine(toLine) + let leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end + let rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom) + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom) + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom) + } + } + if (leftEnd.bottom < rightStart.top) + add(leftSide, leftEnd.bottom, null, rightStart.top) + } + + output.appendChild(fragment) +} + +// Cursor-blinking +export function restartBlink(cm) { + if (!cm.state.focused) return + let display = cm.display + clearInterval(display.blinker) + let on = true + display.cursorDiv.style.visibility = "" + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(() => display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden", + cm.options.cursorBlinkRate) + else if (cm.options.cursorBlinkRate < 0) + display.cursorDiv.style.visibility = "hidden" +} diff --git a/docs/js/node_modules/codemirror/src/display/update_display.js b/docs/js/node_modules/codemirror/src/display/update_display.js new file mode 100644 index 000000000..20798f590 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/update_display.js @@ -0,0 +1,260 @@ +import { sawCollapsedSpans } from "../line/saw_special_spans.js" +import { heightAtLine, visualLineEndNo, visualLineNo } from "../line/spans.js" +import { getLine, lineNumberFor } from "../line/utils_line.js" +import { displayHeight, displayWidth, getDimensions, paddingVert, scrollGap } from "../measurement/position_measurement.js" +import { mac, webkit } from "../util/browser.js" +import { activeElt, removeChildren, contains } from "../util/dom.js" +import { hasHandler, signal } from "../util/event.js" +import { indexOf } from "../util/misc.js" + +import { buildLineElement, updateLineForChanges } from "./update_line.js" +import { startWorker } from "./highlight_worker.js" +import { maybeUpdateLineNumberWidth } from "./line_numbers.js" +import { measureForScrollbars, updateScrollbars } from "./scrollbars.js" +import { updateSelection } from "./selection.js" +import { updateHeightsInViewport, visibleLines } from "./update_lines.js" +import { adjustView, countDirtyView, resetView } from "./view_tracking.js" + +// DISPLAY DRAWING + +export class DisplayUpdate { + constructor(cm, viewport, force) { + let display = cm.display + + this.viewport = viewport + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport) + this.editorIsHidden = !display.wrapper.offsetWidth + this.wrapperHeight = display.wrapper.clientHeight + this.wrapperWidth = display.wrapper.clientWidth + this.oldDisplayWidth = displayWidth(cm) + this.force = force + this.dims = getDimensions(cm) + this.events = [] + } + + signal(emitter, type) { + if (hasHandler(emitter, type)) + this.events.push(arguments) + } + finish() { + for (let i = 0; i < this.events.length; i++) + signal.apply(null, this.events[i]) + } +} + +export function maybeClipScrollbars(cm) { + let display = cm.display + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth + display.heightForcer.style.height = scrollGap(cm) + "px" + display.sizer.style.marginBottom = -display.nativeBarWidth + "px" + display.sizer.style.borderRightWidth = scrollGap(cm) + "px" + display.scrollbarsClipped = true + } +} + +function selectionSnapshot(cm) { + if (cm.hasFocus()) return null + let active = activeElt() + if (!active || !contains(cm.display.lineDiv, active)) return null + let result = {activeElt: active} + if (window.getSelection) { + let sel = window.getSelection() + if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { + result.anchorNode = sel.anchorNode + result.anchorOffset = sel.anchorOffset + result.focusNode = sel.focusNode + result.focusOffset = sel.focusOffset + } + } + return result +} + +function restoreSelection(snapshot) { + if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) return + snapshot.activeElt.focus() + if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { + let sel = window.getSelection(), range = document.createRange() + range.setEnd(snapshot.anchorNode, snapshot.anchorOffset) + range.collapse(false) + sel.removeAllRanges() + sel.addRange(range) + sel.extend(snapshot.focusNode, snapshot.focusOffset) + } +} + +// Does the actual updating of the line display. Bails out +// (returning false) when there is nothing to be done and forced is +// false. +export function updateDisplayIfNeeded(cm, update) { + let display = cm.display, doc = cm.doc + + if (update.editorIsHidden) { + resetView(cm) + return false + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + return false + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm) + update.dims = getDimensions(cm) + } + + // Compute a suitable new viewport (from & to) + let end = doc.first + doc.size + let from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first) + let to = Math.min(end, update.visible.to + cm.options.viewportMargin) + if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom) + if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo) + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from) + to = visualLineEndNo(cm.doc, to) + } + + let different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth + adjustView(cm, from, to) + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)) + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px" + + let toUpdate = countDirtyView(cm) + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + return false + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + let selSnapshot = selectionSnapshot(cm) + if (toUpdate > 4) display.lineDiv.style.display = "none" + patchDisplay(cm, display.updateLineNumbers, update.dims) + if (toUpdate > 4) display.lineDiv.style.display = "" + display.renderedView = display.view + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + restoreSelection(selSnapshot) + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv) + removeChildren(display.selectionDiv) + display.gutters.style.height = display.sizer.style.minHeight = 0 + + if (different) { + display.lastWrapHeight = update.wrapperHeight + display.lastWrapWidth = update.wrapperWidth + startWorker(cm, 400) + } + + display.updateLineNumbers = null + + return true +} + +export function postUpdateDisplay(cm, update) { + let viewport = update.viewport + + for (let first = true;; first = false) { + if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)} + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport) + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + break + } + if (!updateDisplayIfNeeded(cm, update)) break + updateHeightsInViewport(cm) + let barMeasure = measureForScrollbars(cm) + updateSelection(cm) + updateScrollbars(cm, barMeasure) + setDocumentHeight(cm, barMeasure) + update.force = false + } + + update.signal(cm, "update", cm) + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo) + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo + } +} + +export function updateDisplaySimple(cm, viewport) { + let update = new DisplayUpdate(cm, viewport) + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm) + postUpdateDisplay(cm, update) + let barMeasure = measureForScrollbars(cm) + updateSelection(cm) + updateScrollbars(cm, barMeasure) + setDocumentHeight(cm, barMeasure) + update.finish() + } +} + +// Sync the actual display DOM structure with display.view, removing +// nodes for lines that are no longer in view, and creating the ones +// that are not there yet, and updating the ones that are out of +// date. +function patchDisplay(cm, updateNumbersFrom, dims) { + let display = cm.display, lineNumbers = cm.options.lineNumbers + let container = display.lineDiv, cur = container.firstChild + + function rm(node) { + let next = node.nextSibling + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + node.style.display = "none" + else + node.parentNode.removeChild(node) + return next + } + + let view = display.view, lineN = display.viewFrom + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (let i = 0; i < view.length; i++) { + let lineView = view[i] + if (lineView.hidden) { + } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + let node = buildLineElement(cm, lineView, lineN, dims) + container.insertBefore(node, cur) + } else { // Already drawn + while (cur != lineView.node) cur = rm(cur) + let updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false + updateLineForChanges(cm, lineView, lineN, dims) + } + if (updateNumber) { + removeChildren(lineView.lineNumber) + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))) + } + cur = lineView.node.nextSibling + } + lineN += lineView.size + } + while (cur) cur = rm(cur) +} + +export function updateGutterSpace(display) { + let width = display.gutters.offsetWidth + display.sizer.style.marginLeft = width + "px" +} + +export function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px" + cm.display.heightForcer.style.top = measure.docHeight + "px" + cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px" +} diff --git a/docs/js/node_modules/codemirror/src/display/update_line.js b/docs/js/node_modules/codemirror/src/display/update_line.js new file mode 100644 index 000000000..a436973ea --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/update_line.js @@ -0,0 +1,188 @@ +import { buildLineContent } from "../line/line_data.js" +import { lineNumberFor } from "../line/utils_line.js" +import { ie, ie_version } from "../util/browser.js" +import { elt } from "../util/dom.js" +import { signalLater } from "../util/operation_group.js" + +// When an aspect of a line changes, a string is added to +// lineView.changes. This updates the relevant part of the line's +// DOM structure. +export function updateLineForChanges(cm, lineView, lineN, dims) { + for (let j = 0; j < lineView.changes.length; j++) { + let type = lineView.changes[j] + if (type == "text") updateLineText(cm, lineView) + else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims) + else if (type == "class") updateLineClasses(cm, lineView) + else if (type == "widget") updateLineWidgets(cm, lineView, dims) + } + lineView.changes = null +} + +// Lines with gutter elements, widgets or a background class need to +// be wrapped, and have the extra elements added to the wrapper div +function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative") + if (lineView.text.parentNode) + lineView.text.parentNode.replaceChild(lineView.node, lineView.text) + lineView.node.appendChild(lineView.text) + if (ie && ie_version < 8) lineView.node.style.zIndex = 2 + } + return lineView.node +} + +function updateLineBackground(cm, lineView) { + let cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass + if (cls) cls += " CodeMirror-linebackground" + if (lineView.background) { + if (cls) lineView.background.className = cls + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null } + } else if (cls) { + let wrap = ensureLineWrapped(lineView) + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild) + cm.display.input.setUneditable(lineView.background) + } +} + +// Wrapper around buildLineContent which will reuse the structure +// in display.externalMeasured when possible. +function getLineContent(cm, lineView) { + let ext = cm.display.externalMeasured + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null + lineView.measure = ext.measure + return ext.built + } + return buildLineContent(cm, lineView) +} + +// Redraw the line's text. Interacts with the background and text +// classes because the mode may output tokens that influence these +// classes. +function updateLineText(cm, lineView) { + let cls = lineView.text.className + let built = getLineContent(cm, lineView) + if (lineView.text == lineView.node) lineView.node = built.pre + lineView.text.parentNode.replaceChild(built.pre, lineView.text) + lineView.text = built.pre + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass + lineView.textClass = built.textClass + updateLineClasses(cm, lineView) + } else if (cls) { + lineView.text.className = cls + } +} + +function updateLineClasses(cm, lineView) { + updateLineBackground(cm, lineView) + if (lineView.line.wrapClass) + ensureLineWrapped(lineView).className = lineView.line.wrapClass + else if (lineView.node != lineView.text) + lineView.node.className = "" + let textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass + lineView.text.className = textClass || "" +} + +function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter) + lineView.gutter = null + } + if (lineView.gutterBackground) { + lineView.node.removeChild(lineView.gutterBackground) + lineView.gutterBackground = null + } + if (lineView.line.gutterClass) { + let wrap = ensureLineWrapped(lineView) + lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, + `left: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px; width: ${dims.gutterTotalWidth}px`) + cm.display.input.setUneditable(lineView.gutterBackground) + wrap.insertBefore(lineView.gutterBackground, lineView.text) + } + let markers = lineView.line.gutterMarkers + if (cm.options.lineNumbers || markers) { + let wrap = ensureLineWrapped(lineView) + let gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", `left: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px`) + cm.display.input.setUneditable(gutterWrap) + wrap.insertBefore(gutterWrap, lineView.text) + if (lineView.line.gutterClass) + gutterWrap.className += " " + lineView.line.gutterClass + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + `left: ${dims.gutterLeft["CodeMirror-linenumbers"]}px; width: ${cm.display.lineNumInnerWidth}px`)) + if (markers) for (let k = 0; k < cm.display.gutterSpecs.length; ++k) { + let id = cm.display.gutterSpecs[k].className, found = markers.hasOwnProperty(id) && markers[id] + if (found) + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", + `left: ${dims.gutterLeft[id]}px; width: ${dims.gutterWidth[id]}px`)) + } + } +} + +function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) lineView.alignable = null + for (let node = lineView.node.firstChild, next; node; node = next) { + next = node.nextSibling + if (node.className == "CodeMirror-linewidget") + lineView.node.removeChild(node) + } + insertLineWidgets(cm, lineView, dims) +} + +// Build a line's DOM representation from scratch +export function buildLineElement(cm, lineView, lineN, dims) { + let built = getLineContent(cm, lineView) + lineView.text = lineView.node = built.pre + if (built.bgClass) lineView.bgClass = built.bgClass + if (built.textClass) lineView.textClass = built.textClass + + updateLineClasses(cm, lineView) + updateLineGutter(cm, lineView, lineN, dims) + insertLineWidgets(cm, lineView, dims) + return lineView.node +} + +// A lineView may contain multiple logical lines (when merged by +// collapsed spans). The widgets for all of them need to be drawn. +function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true) + if (lineView.rest) for (let i = 0; i < lineView.rest.length; i++) + insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false) +} + +function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) return + let wrap = ensureLineWrapped(lineView) + for (let i = 0, ws = line.widgets; i < ws.length; ++i) { + let widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget") + if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true") + positionLineWidget(widget, node, lineView, dims) + cm.display.input.setUneditable(node) + if (allowAbove && widget.above) + wrap.insertBefore(node, lineView.gutter || lineView.text) + else + wrap.appendChild(node) + signalLater(widget, "redraw") + } +} + +function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + ;(lineView.alignable || (lineView.alignable = [])).push(node) + let width = dims.wrapperWidth + node.style.left = dims.fixedPos + "px" + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth + node.style.paddingLeft = dims.gutterTotalWidth + "px" + } + node.style.width = width + "px" + } + if (widget.coverGutter) { + node.style.zIndex = 5 + node.style.position = "relative" + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px" + } +} diff --git a/docs/js/node_modules/codemirror/src/display/update_lines.js b/docs/js/node_modules/codemirror/src/display/update_lines.js new file mode 100644 index 000000000..60c367e4d --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/update_lines.js @@ -0,0 +1,76 @@ +import { heightAtLine } from "../line/spans.js" +import { getLine, lineAtHeight, updateLineHeight } from "../line/utils_line.js" +import { paddingTop, charWidth } from "../measurement/position_measurement.js" +import { ie, ie_version } from "../util/browser.js" + +// Read the actual heights of the rendered lines, and update their +// stored heights to match. +export function updateHeightsInViewport(cm) { + let display = cm.display + let prevBottom = display.lineDiv.offsetTop + for (let i = 0; i < display.view.length; i++) { + let cur = display.view[i], wrapping = cm.options.lineWrapping + let height, width = 0 + if (cur.hidden) continue + if (ie && ie_version < 8) { + let bot = cur.node.offsetTop + cur.node.offsetHeight + height = bot - prevBottom + prevBottom = bot + } else { + let box = cur.node.getBoundingClientRect() + height = box.bottom - box.top + // Check that lines don't extend past the right of the current + // editor width + if (!wrapping && cur.text.firstChild) + width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1 + } + let diff = cur.line.height - height + if (diff > .005 || diff < -.005) { + updateLineHeight(cur.line, height) + updateWidgetHeight(cur.line) + if (cur.rest) for (let j = 0; j < cur.rest.length; j++) + updateWidgetHeight(cur.rest[j]) + } + if (width > cm.display.sizerWidth) { + let chWidth = Math.ceil(width / charWidth(cm.display)) + if (chWidth > cm.display.maxLineLength) { + cm.display.maxLineLength = chWidth + cm.display.maxLine = cur.line + cm.display.maxLineChanged = true + } + } + } +} + +// Read and store the height of line widgets associated with the +// given line. +function updateWidgetHeight(line) { + if (line.widgets) for (let i = 0; i < line.widgets.length; ++i) { + let w = line.widgets[i], parent = w.node.parentNode + if (parent) w.height = parent.offsetHeight + } +} + +// Compute the lines that are visible in a given viewport (defaults +// the the current scroll position). viewport may contain top, +// height, and ensure (see op.scrollToPos) properties. +export function visibleLines(display, doc, viewport) { + let top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop + top = Math.floor(top - paddingTop(display)) + let bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight + + let from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom) + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + let ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line + if (ensureFrom < from) { + from = ensureFrom + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight) + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight) + to = ensureTo + } + } + return {from: from, to: Math.max(to, from + 1)} +} diff --git a/docs/js/node_modules/codemirror/src/display/view_tracking.js b/docs/js/node_modules/codemirror/src/display/view_tracking.js new file mode 100644 index 000000000..41464f235 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/display/view_tracking.js @@ -0,0 +1,153 @@ +import { buildViewArray } from "../line/line_data.js" +import { sawCollapsedSpans } from "../line/saw_special_spans.js" +import { visualLineEndNo, visualLineNo } from "../line/spans.js" +import { findViewIndex } from "../measurement/position_measurement.js" +import { indexOf } from "../util/misc.js" + +// Updates the display.view data structure for a given change to the +// document. From and to are in pre-change coordinates. Lendiff is +// the amount of lines added or subtracted by the change. This is +// used for changes that span multiple lines, or change the way +// lines are divided into visual lines. regLineChange (below) +// registers single-line changes. +export function regChange(cm, from, to, lendiff) { + if (from == null) from = cm.doc.first + if (to == null) to = cm.doc.first + cm.doc.size + if (!lendiff) lendiff = 0 + + let display = cm.display + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + display.updateLineNumbers = from + + cm.curOp.viewChanged = true + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + resetView(cm) + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm) + } else { + display.viewFrom += lendiff + display.viewTo += lendiff + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm) + } else if (from <= display.viewFrom) { // Top overlap + let cut = viewCuttingPoint(cm, to, to + lendiff, 1) + if (cut) { + display.view = display.view.slice(cut.index) + display.viewFrom = cut.lineN + display.viewTo += lendiff + } else { + resetView(cm) + } + } else if (to >= display.viewTo) { // Bottom overlap + let cut = viewCuttingPoint(cm, from, from, -1) + if (cut) { + display.view = display.view.slice(0, cut.index) + display.viewTo = cut.lineN + } else { + resetView(cm) + } + } else { // Gap in the middle + let cutTop = viewCuttingPoint(cm, from, from, -1) + let cutBot = viewCuttingPoint(cm, to, to + lendiff, 1) + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)) + display.viewTo += lendiff + } else { + resetView(cm) + } + } + + let ext = display.externalMeasured + if (ext) { + if (to < ext.lineN) + ext.lineN += lendiff + else if (from < ext.lineN + ext.size) + display.externalMeasured = null + } +} + +// Register a change to a single line. Type must be one of "text", +// "gutter", "class", "widget" +export function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true + let display = cm.display, ext = cm.display.externalMeasured + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + display.externalMeasured = null + + if (line < display.viewFrom || line >= display.viewTo) return + let lineView = display.view[findViewIndex(cm, line)] + if (lineView.node == null) return + let arr = lineView.changes || (lineView.changes = []) + if (indexOf(arr, type) == -1) arr.push(type) +} + +// Clear the view. +export function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first + cm.display.view = [] + cm.display.viewOffset = 0 +} + +function viewCuttingPoint(cm, oldN, newN, dir) { + let index = findViewIndex(cm, oldN), diff, view = cm.display.view + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + return {index: index, lineN: newN} + let n = cm.display.viewFrom + for (let i = 0; i < index; i++) + n += view[i].size + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) return null + diff = (n + view[index].size) - oldN + index++ + } else { + diff = n - oldN + } + oldN += diff; newN += diff + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) return null + newN += dir * view[index - (dir < 0 ? 1 : 0)].size + index += dir + } + return {index: index, lineN: newN} +} + +// Force the view to cover a given range, adding empty view element +// or clipping off existing ones as needed. +export function adjustView(cm, from, to) { + let display = cm.display, view = display.view + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to) + display.viewFrom = from + } else { + if (display.viewFrom > from) + display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view) + else if (display.viewFrom < from) + display.view = display.view.slice(findViewIndex(cm, from)) + display.viewFrom = from + if (display.viewTo < to) + display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)) + else if (display.viewTo > to) + display.view = display.view.slice(0, findViewIndex(cm, to)) + } + display.viewTo = to +} + +// Count the number of lines in the view whose DOM representation is +// out of date (or nonexistent). +export function countDirtyView(cm) { + let view = cm.display.view, dirty = 0 + for (let i = 0; i < view.length; i++) { + let lineView = view[i] + if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty + } + return dirty +} diff --git a/docs/js/node_modules/codemirror/src/edit/CodeMirror.js b/docs/js/node_modules/codemirror/src/edit/CodeMirror.js new file mode 100644 index 000000000..9188e1b25 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/CodeMirror.js @@ -0,0 +1,212 @@ +import { Display } from "../display/Display.js" +import { onFocus, onBlur } from "../display/focus.js" +import { maybeUpdateLineNumberWidth } from "../display/line_numbers.js" +import { endOperation, operation, startOperation } from "../display/operations.js" +import { initScrollbars } from "../display/scrollbars.js" +import { onScrollWheel } from "../display/scroll_events.js" +import { setScrollLeft, updateScrollTop } from "../display/scrolling.js" +import { clipPos, Pos } from "../line/pos.js" +import { posFromMouse } from "../measurement/position_measurement.js" +import { eventInWidget } from "../measurement/widgets.js" +import Doc from "../model/Doc.js" +import { attachDoc } from "../model/document_data.js" +import { Range } from "../model/selection.js" +import { extendSelection } from "../model/selection_updates.js" +import { ie, ie_version, mobile, webkit } from "../util/browser.js" +import { e_preventDefault, e_stop, on, signal, signalDOMEvent } from "../util/event.js" +import { bind, copyObj, Delayed } from "../util/misc.js" + +import { clearDragCursor, onDragOver, onDragStart, onDrop } from "./drop_events.js" +import { ensureGlobalHandlers } from "./global_events.js" +import { onKeyDown, onKeyPress, onKeyUp } from "./key_events.js" +import { clickInGutter, onContextMenu, onMouseDown } from "./mouse_events.js" +import { themeChanged } from "./utils.js" +import { defaults, optionHandlers, Init } from "./options.js" + +// A CodeMirror instance represents an editor. This is the object +// that user code is usually dealing with. + +export function CodeMirror(place, options) { + if (!(this instanceof CodeMirror)) return new CodeMirror(place, options) + + this.options = options = options ? copyObj(options) : {} + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false) + + let doc = options.value + if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction) + else if (options.mode) doc.modeOption = options.mode + this.doc = doc + + let input = new CodeMirror.inputStyles[options.inputStyle](this) + let display = this.display = new Display(place, doc, input, options) + display.wrapper.CodeMirror = this + themeChanged(this) + if (options.lineWrapping) + this.display.wrapper.className += " CodeMirror-wrap" + initScrollbars(this) + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll + selectingText: false, + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + } + + if (options.autofocus && !mobile) display.input.focus() + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) setTimeout(() => this.display.input.reset(true), 20) + + registerEventHandlers(this) + ensureGlobalHandlers() + + startOperation(this) + this.curOp.forceUpdate = true + attachDoc(this, doc) + + if ((options.autofocus && !mobile) || this.hasFocus()) + setTimeout(bind(onFocus, this), 20) + else + onBlur(this) + + for (let opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) + optionHandlers[opt](this, options[opt], Init) + maybeUpdateLineNumberWidth(this) + if (options.finishInit) options.finishInit(this) + for (let i = 0; i < initHooks.length; ++i) initHooks[i](this) + endOperation(this) + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + display.lineDiv.style.textRendering = "auto" +} + +// The default configuration options. +CodeMirror.defaults = defaults +// Functions to run when options are changed. +CodeMirror.optionHandlers = optionHandlers + +export default CodeMirror + +// Attach the necessary event handlers when initializing the editor +function registerEventHandlers(cm) { + let d = cm.display + on(d.scroller, "mousedown", operation(cm, onMouseDown)) + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + on(d.scroller, "dblclick", operation(cm, e => { + if (signalDOMEvent(cm, e)) return + let pos = posFromMouse(cm, e) + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return + e_preventDefault(e) + let word = cm.findWordAt(pos) + extendSelection(cm.doc, word.anchor, word.head) + })) + else + on(d.scroller, "dblclick", e => signalDOMEvent(cm, e) || e_preventDefault(e)) + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + on(d.scroller, "contextmenu", e => onContextMenu(cm, e)) + + // Used to suppress mouse event handling when a touch happens + let touchFinished, prevTouch = {end: 0} + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(() => d.activeTouch = null, 1000) + prevTouch = d.activeTouch + prevTouch.end = +new Date + } + } + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) return false + let touch = e.touches[0] + return touch.radiusX <= 1 && touch.radiusY <= 1 + } + function farAway(touch, other) { + if (other.left == null) return true + let dx = other.left - touch.left, dy = other.top - touch.top + return dx * dx + dy * dy > 20 * 20 + } + on(d.scroller, "touchstart", e => { + if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { + d.input.ensurePolled() + clearTimeout(touchFinished) + let now = +new Date + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null} + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX + d.activeTouch.top = e.touches[0].pageY + } + } + }) + on(d.scroller, "touchmove", () => { + if (d.activeTouch) d.activeTouch.moved = true + }) + on(d.scroller, "touchend", e => { + let touch = d.activeTouch + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + let pos = cm.coordsChar(d.activeTouch, "page"), range + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + range = new Range(pos, pos) + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + range = cm.findWordAt(pos) + else // Triple tap + range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) + cm.setSelection(range.anchor, range.head) + cm.focus() + e_preventDefault(e) + } + finishTouch() + }) + on(d.scroller, "touchcancel", finishTouch) + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", () => { + if (d.scroller.clientHeight) { + updateScrollTop(cm, d.scroller.scrollTop) + setScrollLeft(cm, d.scroller.scrollLeft, true) + signal(cm, "scroll", cm) + } + }) + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", e => onScrollWheel(cm, e)) + on(d.scroller, "DOMMouseScroll", e => onScrollWheel(cm, e)) + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", () => d.wrapper.scrollTop = d.wrapper.scrollLeft = 0) + + d.dragFunctions = { + enter: e => {if (!signalDOMEvent(cm, e)) e_stop(e)}, + over: e => {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }}, + start: e => onDragStart(cm, e), + drop: operation(cm, onDrop), + leave: e => {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }} + } + + let inp = d.input.getField() + on(inp, "keyup", e => onKeyUp.call(cm, e)) + on(inp, "keydown", operation(cm, onKeyDown)) + on(inp, "keypress", operation(cm, onKeyPress)) + on(inp, "focus", e => onFocus(cm, e)) + on(inp, "blur", e => onBlur(cm, e)) +} + +let initHooks = [] +CodeMirror.defineInitHook = f => initHooks.push(f) diff --git a/docs/js/node_modules/codemirror/src/edit/commands.js b/docs/js/node_modules/codemirror/src/edit/commands.js new file mode 100644 index 000000000..3916b129f --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/commands.js @@ -0,0 +1,178 @@ +import { deleteNearSelection } from "./deleteNearSelection.js" +import { runInOp } from "../display/operations.js" +import { ensureCursorVisible } from "../display/scrolling.js" +import { endOfLine } from "../input/movement.js" +import { clipPos, Pos } from "../line/pos.js" +import { visualLine, visualLineEnd } from "../line/spans.js" +import { getLine, lineNo } from "../line/utils_line.js" +import { Range } from "../model/selection.js" +import { selectAll } from "../model/selection_updates.js" +import { countColumn, sel_dontScroll, sel_move, spaceStr } from "../util/misc.js" +import { getOrder } from "../util/bidi.js" + +// Commands are parameter-less actions that can be performed on an +// editor, mostly used for keybindings. +export let commands = { + selectAll: selectAll, + singleSelection: cm => cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll), + killLine: cm => deleteNearSelection(cm, range => { + if (range.empty()) { + let len = getLine(cm.doc, range.head.line).text.length + if (range.head.ch == len && range.head.line < cm.lastLine()) + return {from: range.head, to: Pos(range.head.line + 1, 0)} + else + return {from: range.head, to: Pos(range.head.line, len)} + } else { + return {from: range.from(), to: range.to()} + } + }), + deleteLine: cm => deleteNearSelection(cm, range => ({ + from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) + })), + delLineLeft: cm => deleteNearSelection(cm, range => ({ + from: Pos(range.from().line, 0), to: range.from() + })), + delWrappedLineLeft: cm => deleteNearSelection(cm, range => { + let top = cm.charCoords(range.head, "div").top + 5 + let leftPos = cm.coordsChar({left: 0, top: top}, "div") + return {from: leftPos, to: range.from()} + }), + delWrappedLineRight: cm => deleteNearSelection(cm, range => { + let top = cm.charCoords(range.head, "div").top + 5 + let rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") + return {from: range.from(), to: rightPos } + }), + undo: cm => cm.undo(), + redo: cm => cm.redo(), + undoSelection: cm => cm.undoSelection(), + redoSelection: cm => cm.redoSelection(), + goDocStart: cm => cm.extendSelection(Pos(cm.firstLine(), 0)), + goDocEnd: cm => cm.extendSelection(Pos(cm.lastLine())), + goLineStart: cm => cm.extendSelectionsBy(range => lineStart(cm, range.head.line), + {origin: "+move", bias: 1} + ), + goLineStartSmart: cm => cm.extendSelectionsBy(range => lineStartSmart(cm, range.head), + {origin: "+move", bias: 1} + ), + goLineEnd: cm => cm.extendSelectionsBy(range => lineEnd(cm, range.head.line), + {origin: "+move", bias: -1} + ), + goLineRight: cm => cm.extendSelectionsBy(range => { + let top = cm.cursorCoords(range.head, "div").top + 5 + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") + }, sel_move), + goLineLeft: cm => cm.extendSelectionsBy(range => { + let top = cm.cursorCoords(range.head, "div").top + 5 + return cm.coordsChar({left: 0, top: top}, "div") + }, sel_move), + goLineLeftSmart: cm => cm.extendSelectionsBy(range => { + let top = cm.cursorCoords(range.head, "div").top + 5 + let pos = cm.coordsChar({left: 0, top: top}, "div") + if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head) + return pos + }, sel_move), + goLineUp: cm => cm.moveV(-1, "line"), + goLineDown: cm => cm.moveV(1, "line"), + goPageUp: cm => cm.moveV(-1, "page"), + goPageDown: cm => cm.moveV(1, "page"), + goCharLeft: cm => cm.moveH(-1, "char"), + goCharRight: cm => cm.moveH(1, "char"), + goColumnLeft: cm => cm.moveH(-1, "column"), + goColumnRight: cm => cm.moveH(1, "column"), + goWordLeft: cm => cm.moveH(-1, "word"), + goGroupRight: cm => cm.moveH(1, "group"), + goGroupLeft: cm => cm.moveH(-1, "group"), + goWordRight: cm => cm.moveH(1, "word"), + delCharBefore: cm => cm.deleteH(-1, "char"), + delCharAfter: cm => cm.deleteH(1, "char"), + delWordBefore: cm => cm.deleteH(-1, "word"), + delWordAfter: cm => cm.deleteH(1, "word"), + delGroupBefore: cm => cm.deleteH(-1, "group"), + delGroupAfter: cm => cm.deleteH(1, "group"), + indentAuto: cm => cm.indentSelection("smart"), + indentMore: cm => cm.indentSelection("add"), + indentLess: cm => cm.indentSelection("subtract"), + insertTab: cm => cm.replaceSelection("\t"), + insertSoftTab: cm => { + let spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize + for (let i = 0; i < ranges.length; i++) { + let pos = ranges[i].from() + let col = countColumn(cm.getLine(pos.line), pos.ch, tabSize) + spaces.push(spaceStr(tabSize - col % tabSize)) + } + cm.replaceSelections(spaces) + }, + defaultTab: cm => { + if (cm.somethingSelected()) cm.indentSelection("add") + else cm.execCommand("insertTab") + }, + // Swap the two chars left and right of each selection's head. + // Move cursor behind the two swapped characters afterwards. + // + // Doesn't consider line feeds a character. + // Doesn't scan more than one line above to find a character. + // Doesn't do anything on an empty line. + // Doesn't do anything with non-empty selections. + transposeChars: cm => runInOp(cm, () => { + let ranges = cm.listSelections(), newSel = [] + for (let i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) continue + let cur = ranges[i].head, line = getLine(cm.doc, cur.line).text + if (line) { + if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1) + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1) + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose") + } else if (cur.line > cm.doc.first) { + let prev = getLine(cm.doc, cur.line - 1).text + if (prev) { + cur = new Pos(cur.line, 1) + cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), cur, "+transpose") + } + } + } + newSel.push(new Range(cur, cur)) + } + cm.setSelections(newSel) + }), + newlineAndIndent: cm => runInOp(cm, () => { + let sels = cm.listSelections() + for (let i = sels.length - 1; i >= 0; i--) + cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input") + sels = cm.listSelections() + for (let i = 0; i < sels.length; i++) + cm.indentLine(sels[i].from().line, null, true) + ensureCursorVisible(cm) + }), + openLine: cm => cm.replaceSelection("\n", "start"), + toggleOverwrite: cm => cm.toggleOverwrite() +} + + +function lineStart(cm, lineN) { + let line = getLine(cm.doc, lineN) + let visual = visualLine(line) + if (visual != line) lineN = lineNo(visual) + return endOfLine(true, cm, visual, lineN, 1) +} +function lineEnd(cm, lineN) { + let line = getLine(cm.doc, lineN) + let visual = visualLineEnd(line) + if (visual != line) lineN = lineNo(visual) + return endOfLine(true, cm, line, lineN, -1) +} +function lineStartSmart(cm, pos) { + let start = lineStart(cm, pos.line) + let line = getLine(cm.doc, start.line) + let order = getOrder(line, cm.doc.direction) + if (!order || order[0].level == 0) { + let firstNonWS = Math.max(0, line.text.search(/\S/)) + let inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch + return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) + } + return start +} diff --git a/docs/js/node_modules/codemirror/src/edit/deleteNearSelection.js b/docs/js/node_modules/codemirror/src/edit/deleteNearSelection.js new file mode 100644 index 000000000..82e331a5f --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/deleteNearSelection.js @@ -0,0 +1,30 @@ +import { runInOp } from "../display/operations.js" +import { ensureCursorVisible } from "../display/scrolling.js" +import { cmp } from "../line/pos.js" +import { replaceRange } from "../model/changes.js" +import { lst } from "../util/misc.js" + +// Helper for deleting text near the selection(s), used to implement +// backspace, delete, and similar functionality. +export function deleteNearSelection(cm, compute) { + let ranges = cm.doc.sel.ranges, kill = [] + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (let i = 0; i < ranges.length; i++) { + let toKill = compute(ranges[i]) + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + let replaced = kill.pop() + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from + break + } + } + kill.push(toKill) + } + // Next, remove those actual ranges. + runInOp(cm, () => { + for (let i = kill.length - 1; i >= 0; i--) + replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete") + ensureCursorVisible(cm) + }) +} diff --git a/docs/js/node_modules/codemirror/src/edit/drop_events.js b/docs/js/node_modules/codemirror/src/edit/drop_events.js new file mode 100644 index 000000000..12c760f0d --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/drop_events.js @@ -0,0 +1,119 @@ +import { drawSelectionCursor } from "../display/selection.js" +import { operation } from "../display/operations.js" +import { clipPos } from "../line/pos.js" +import { posFromMouse } from "../measurement/position_measurement.js" +import { eventInWidget } from "../measurement/widgets.js" +import { makeChange, replaceRange } from "../model/changes.js" +import { changeEnd } from "../model/change_measurement.js" +import { simpleSelection } from "../model/selection.js" +import { setSelectionNoUndo, setSelectionReplaceHistory } from "../model/selection_updates.js" +import { ie, presto, safari } from "../util/browser.js" +import { elt, removeChildrenAndAdd } from "../util/dom.js" +import { e_preventDefault, e_stop, signalDOMEvent } from "../util/event.js" +import { indexOf } from "../util/misc.js" + +// Kludge to work around strange IE behavior where it'll sometimes +// re-fire a series of drag-related events right after the drop (#1551) +let lastDrop = 0 + +export function onDrop(e) { + let cm = this + clearDragCursor(cm) + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + return + e_preventDefault(e) + if (ie) lastDrop = +new Date + let pos = posFromMouse(cm, e, true), files = e.dataTransfer.files + if (!pos || cm.isReadOnly()) return + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + let n = files.length, text = Array(n), read = 0 + let loadFile = (file, i) => { + if (cm.options.allowDropFileTypes && + indexOf(cm.options.allowDropFileTypes, file.type) == -1) + return + + let reader = new FileReader + reader.onload = operation(cm, () => { + let content = reader.result + if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = "" + text[i] = content + if (++read == n) { + pos = clipPos(cm.doc, pos) + let change = {from: pos, to: pos, + text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), + origin: "paste"} + makeChange(cm.doc, change) + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))) + } + }) + reader.readAsText(file) + } + for (let i = 0; i < n; ++i) loadFile(files[i], i) + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e) + // Ensure the editor is re-focused + setTimeout(() => cm.display.input.focus(), 20) + return + } + try { + let text = e.dataTransfer.getData("Text") + if (text) { + let selected + if (cm.state.draggingText && !cm.state.draggingText.copy) + selected = cm.listSelections() + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)) + if (selected) for (let i = 0; i < selected.length; ++i) + replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag") + cm.replaceSelection(text, "around", "paste") + cm.display.input.focus() + } + } + catch(e){} + } +} + +export function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return + + e.dataTransfer.setData("Text", cm.getSelection()) + e.dataTransfer.effectAllowed = "copyMove" + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + let img = elt("img", null, null, "position: fixed; left: 0; top: 0;") + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + if (presto) { + img.width = img.height = 1 + cm.display.wrapper.appendChild(img) + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop + } + e.dataTransfer.setDragImage(img, 0, 0) + if (presto) img.parentNode.removeChild(img) + } +} + +export function onDragOver(cm, e) { + let pos = posFromMouse(cm, e) + if (!pos) return + let frag = document.createDocumentFragment() + drawSelectionCursor(cm, pos, frag) + if (!cm.display.dragCursor) { + cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors") + cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv) + } + removeChildrenAndAdd(cm.display.dragCursor, frag) +} + +export function clearDragCursor(cm) { + if (cm.display.dragCursor) { + cm.display.lineSpace.removeChild(cm.display.dragCursor) + cm.display.dragCursor = null + } +} diff --git a/docs/js/node_modules/codemirror/src/edit/fromTextArea.js b/docs/js/node_modules/codemirror/src/edit/fromTextArea.js new file mode 100644 index 000000000..35024c5e2 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/fromTextArea.js @@ -0,0 +1,61 @@ +import { CodeMirror } from "./CodeMirror.js" +import { activeElt } from "../util/dom.js" +import { off, on } from "../util/event.js" +import { copyObj } from "../util/misc.js" + +export function fromTextArea(textarea, options) { + options = options ? copyObj(options) : {} + options.value = textarea.value + if (!options.tabindex && textarea.tabIndex) + options.tabindex = textarea.tabIndex + if (!options.placeholder && textarea.placeholder) + options.placeholder = textarea.placeholder + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + let hasFocus = activeElt() + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body + } + + function save() {textarea.value = cm.getValue()} + + let realSubmit + if (textarea.form) { + on(textarea.form, "submit", save) + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + let form = textarea.form + realSubmit = form.submit + try { + let wrappedSubmit = form.submit = () => { + save() + form.submit = realSubmit + form.submit() + form.submit = wrappedSubmit + } + } catch(e) {} + } + } + + options.finishInit = cm => { + cm.save = save + cm.getTextArea = () => textarea + cm.toTextArea = () => { + cm.toTextArea = isNaN // Prevent this from being ran twice + save() + textarea.parentNode.removeChild(cm.getWrapperElement()) + textarea.style.display = "" + if (textarea.form) { + off(textarea.form, "submit", save) + if (!options.leaveSubmitMethodAlone && typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit + } + } + } + + textarea.style.display = "none" + let cm = CodeMirror(node => textarea.parentNode.insertBefore(node, textarea.nextSibling), + options) + return cm +} diff --git a/docs/js/node_modules/codemirror/src/edit/global_events.js b/docs/js/node_modules/codemirror/src/edit/global_events.js new file mode 100644 index 000000000..d03da2d08 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/global_events.js @@ -0,0 +1,45 @@ +import { onBlur } from "../display/focus.js" +import { on } from "../util/event.js" + +// These must be handled carefully, because naively registering a +// handler for each editor will cause the editors to never be +// garbage collected. + +function forEachCodeMirror(f) { + if (!document.getElementsByClassName) return + let byClass = document.getElementsByClassName("CodeMirror"), editors = [] + for (let i = 0; i < byClass.length; i++) { + let cm = byClass[i].CodeMirror + if (cm) editors.push(cm) + } + if (editors.length) editors[0].operation(() => { + for (let i = 0; i < editors.length; i++) f(editors[i]) + }) +} + +let globalsRegistered = false +export function ensureGlobalHandlers() { + if (globalsRegistered) return + registerGlobalHandlers() + globalsRegistered = true +} +function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + let resizeTimer + on(window, "resize", () => { + if (resizeTimer == null) resizeTimer = setTimeout(() => { + resizeTimer = null + forEachCodeMirror(onResize) + }, 100) + }) + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", () => forEachCodeMirror(onBlur)) +} +// Called when the window resizes +function onResize(cm) { + let d = cm.display + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null + d.scrollbarsClipped = false + cm.setSize() +} diff --git a/docs/js/node_modules/codemirror/src/edit/key_events.js b/docs/js/node_modules/codemirror/src/edit/key_events.js new file mode 100644 index 000000000..f0521d070 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/key_events.js @@ -0,0 +1,159 @@ +import { signalLater } from "../util/operation_group.js" +import { restartBlink } from "../display/selection.js" +import { isModifierKey, keyName, lookupKey } from "../input/keymap.js" +import { eventInWidget } from "../measurement/widgets.js" +import { ie, ie_version, mac, presto } from "../util/browser.js" +import { activeElt, addClass, rmClass } from "../util/dom.js" +import { e_preventDefault, off, on, signalDOMEvent } from "../util/event.js" +import { hasCopyEvent } from "../util/feature_detection.js" +import { Delayed, Pass } from "../util/misc.js" + +import { commands } from "./commands.js" + +// Run a handler that was bound to a key. +function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound] + if (!bound) return false + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled() + let prevShift = cm.display.shift, done = false + try { + if (cm.isReadOnly()) cm.state.suppressEdits = true + if (dropShift) cm.display.shift = false + done = bound(cm) != Pass + } finally { + cm.display.shift = prevShift + cm.state.suppressEdits = false + } + return done +} + +function lookupKeyForEditor(cm, name, handle) { + for (let i = 0; i < cm.state.keyMaps.length; i++) { + let result = lookupKey(name, cm.state.keyMaps[i], handle, cm) + if (result) return result + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm) +} + +// Note that, despite the name, this function is also used to check +// for bound mouse clicks. + +let stopSeq = new Delayed + +export function dispatchKey(cm, name, e, handle) { + let seq = cm.state.keySeq + if (seq) { + if (isModifierKey(name)) return "handled" + if (/\'$/.test(name)) + cm.state.keySeq = null + else + stopSeq.set(50, () => { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null + cm.display.input.reset() + } + }) + if (dispatchKeyInner(cm, seq + " " + name, e, handle)) return true + } + return dispatchKeyInner(cm, name, e, handle) +} + +function dispatchKeyInner(cm, name, e, handle) { + let result = lookupKeyForEditor(cm, name, handle) + + if (result == "multi") + cm.state.keySeq = name + if (result == "handled") + signalLater(cm, "keyHandled", cm, name, e) + + if (result == "handled" || result == "multi") { + e_preventDefault(e) + restartBlink(cm) + } + + return !!result +} + +// Handle a key from the keydown event. +function handleKeyBinding(cm, e) { + let name = keyName(e, true) + if (!name) return false + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, b => doHandleBinding(cm, b, true)) + || dispatchKey(cm, name, e, b => { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b) + }) + } else { + return dispatchKey(cm, name, e, b => doHandleBinding(cm, b)) + } +} + +// Handle a key from the keypress event +function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, b => doHandleBinding(cm, b, true)) +} + +let lastStoppedKey = null +export function onKeyDown(e) { + let cm = this + cm.curOp.focus = activeElt() + if (signalDOMEvent(cm, e)) return + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false + let code = e.keyCode + cm.display.shift = code == 16 || e.shiftKey + let handled = handleKeyBinding(cm, e) + if (presto) { + lastStoppedKey = handled ? code : null + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + cm.replaceSelection("", null, "cut") + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + showCrossHair(cm) +} + +function showCrossHair(cm) { + let lineDiv = cm.display.lineDiv + addClass(lineDiv, "CodeMirror-crosshair") + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair") + off(document, "keyup", up) + off(document, "mouseover", up) + } + } + on(document, "keyup", up) + on(document, "mouseover", up) +} + +export function onKeyUp(e) { + if (e.keyCode == 16) this.doc.sel.shift = false + signalDOMEvent(this, e) +} + +export function onKeyPress(e) { + let cm = this + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return + let keyCode = e.keyCode, charCode = e.charCode + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return + let ch = String.fromCharCode(charCode == null ? keyCode : charCode) + // Some browsers fire keypress events for backspace + if (ch == "\x08") return + if (handleCharBinding(cm, e, ch)) return + cm.display.input.onKeyPress(e) +} diff --git a/docs/js/node_modules/codemirror/src/edit/legacy.js b/docs/js/node_modules/codemirror/src/edit/legacy.js new file mode 100644 index 000000000..889badbe5 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/legacy.js @@ -0,0 +1,62 @@ +import { scrollbarModel } from "../display/scrollbars.js" +import { wheelEventPixels } from "../display/scroll_events.js" +import { keyMap, keyName, isModifierKey, lookupKey, normalizeKeyMap } from "../input/keymap.js" +import { keyNames } from "../input/keynames.js" +import { Line } from "../line/line_data.js" +import { cmp, Pos } from "../line/pos.js" +import { changeEnd } from "../model/change_measurement.js" +import Doc from "../model/Doc.js" +import { LineWidget } from "../model/line_widget.js" +import { SharedTextMarker, TextMarker } from "../model/mark_text.js" +import { copyState, extendMode, getMode, innerMode, mimeModes, modeExtensions, modes, resolveMode, startState } from "../modes.js" +import { addClass, contains, rmClass } from "../util/dom.js" +import { e_preventDefault, e_stop, e_stopPropagation, off, on, signal } from "../util/event.js" +import { splitLinesAuto } from "../util/feature_detection.js" +import { countColumn, findColumn, isWordCharBasic, Pass } from "../util/misc.js" +import StringStream from "../util/StringStream.js" + +import { commands } from "./commands.js" + +export function addLegacyProps(CodeMirror) { + CodeMirror.off = off + CodeMirror.on = on + CodeMirror.wheelEventPixels = wheelEventPixels + CodeMirror.Doc = Doc + CodeMirror.splitLines = splitLinesAuto + CodeMirror.countColumn = countColumn + CodeMirror.findColumn = findColumn + CodeMirror.isWordChar = isWordCharBasic + CodeMirror.Pass = Pass + CodeMirror.signal = signal + CodeMirror.Line = Line + CodeMirror.changeEnd = changeEnd + CodeMirror.scrollbarModel = scrollbarModel + CodeMirror.Pos = Pos + CodeMirror.cmpPos = cmp + CodeMirror.modes = modes + CodeMirror.mimeModes = mimeModes + CodeMirror.resolveMode = resolveMode + CodeMirror.getMode = getMode + CodeMirror.modeExtensions = modeExtensions + CodeMirror.extendMode = extendMode + CodeMirror.copyState = copyState + CodeMirror.startState = startState + CodeMirror.innerMode = innerMode + CodeMirror.commands = commands + CodeMirror.keyMap = keyMap + CodeMirror.keyName = keyName + CodeMirror.isModifierKey = isModifierKey + CodeMirror.lookupKey = lookupKey + CodeMirror.normalizeKeyMap = normalizeKeyMap + CodeMirror.StringStream = StringStream + CodeMirror.SharedTextMarker = SharedTextMarker + CodeMirror.TextMarker = TextMarker + CodeMirror.LineWidget = LineWidget + CodeMirror.e_preventDefault = e_preventDefault + CodeMirror.e_stopPropagation = e_stopPropagation + CodeMirror.e_stop = e_stop + CodeMirror.addClass = addClass + CodeMirror.contains = contains + CodeMirror.rmClass = rmClass + CodeMirror.keyNames = keyNames +} diff --git a/docs/js/node_modules/codemirror/src/edit/main.js b/docs/js/node_modules/codemirror/src/edit/main.js new file mode 100644 index 000000000..9b491152c --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/main.js @@ -0,0 +1,69 @@ +// EDITOR CONSTRUCTOR + +import { CodeMirror } from "./CodeMirror.js" +export { CodeMirror } from "./CodeMirror.js" + +import { eventMixin } from "../util/event.js" +import { indexOf } from "../util/misc.js" + +import { defineOptions } from "./options.js" + +defineOptions(CodeMirror) + +import addEditorMethods from "./methods.js" + +addEditorMethods(CodeMirror) + +import Doc from "../model/Doc.js" + +// Set up methods on CodeMirror's prototype to redirect to the editor's document. +let dontDelegate = "iter insert remove copy getEditor constructor".split(" ") +for (let prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments)} + })(Doc.prototype[prop]) + +eventMixin(Doc) + +// INPUT HANDLING + +import ContentEditableInput from "../input/ContentEditableInput.js" +import TextareaInput from "../input/TextareaInput.js" +CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput} + +// MODE DEFINITION AND QUERYING + +import { defineMIME, defineMode } from "../modes.js" + +// Extra arguments are stored as the mode's dependencies, which is +// used by (legacy) mechanisms like loadmode.js to automatically +// load a mode. (Preferred mechanism is the require/define calls.) +CodeMirror.defineMode = function(name/*, mode, …*/) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name + defineMode.apply(this, arguments) +} + +CodeMirror.defineMIME = defineMIME + +// Minimal default mode. +CodeMirror.defineMode("null", () => ({token: stream => stream.skipToEnd()})) +CodeMirror.defineMIME("text/plain", "null") + +// EXTENSIONS + +CodeMirror.defineExtension = (name, func) => { + CodeMirror.prototype[name] = func +} +CodeMirror.defineDocExtension = (name, func) => { + Doc.prototype[name] = func +} + +import { fromTextArea } from "./fromTextArea.js" + +CodeMirror.fromTextArea = fromTextArea + +import { addLegacyProps } from "./legacy.js" + +addLegacyProps(CodeMirror) + +CodeMirror.version = "5.49.2" diff --git a/docs/js/node_modules/codemirror/src/edit/methods.js b/docs/js/node_modules/codemirror/src/edit/methods.js new file mode 100644 index 000000000..e40012e75 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/methods.js @@ -0,0 +1,546 @@ +import { deleteNearSelection } from "./deleteNearSelection.js" +import { commands } from "./commands.js" +import { attachDoc } from "../model/document_data.js" +import { activeElt, addClass, rmClass } from "../util/dom.js" +import { eventMixin, signal } from "../util/event.js" +import { getLineStyles, getContextBefore, takeToken } from "../line/highlight.js" +import { indentLine } from "../input/indent.js" +import { triggerElectric } from "../input/input.js" +import { onKeyDown, onKeyPress, onKeyUp } from "./key_events.js" +import { onMouseDown } from "./mouse_events.js" +import { getKeyMap } from "../input/keymap.js" +import { endOfLine, moveLogically, moveVisually } from "../input/movement.js" +import { endOperation, methodOp, operation, runInOp, startOperation } from "../display/operations.js" +import { clipLine, clipPos, equalCursorPos, Pos } from "../line/pos.js" +import { charCoords, charWidth, clearCaches, clearLineMeasurementCache, coordsChar, cursorCoords, displayHeight, displayWidth, estimateLineHeights, fromCoordSystem, intoCoordSystem, scrollGap, textHeight } from "../measurement/position_measurement.js" +import { Range } from "../model/selection.js" +import { replaceOneSelection, skipAtomic } from "../model/selection_updates.js" +import { addToScrollTop, ensureCursorVisible, scrollIntoView, scrollToCoords, scrollToCoordsRange, scrollToRange } from "../display/scrolling.js" +import { heightAtLine } from "../line/spans.js" +import { updateGutterSpace } from "../display/update_display.js" +import { indexOf, insertSorted, isWordChar, sel_dontScroll, sel_move } from "../util/misc.js" +import { signalLater } from "../util/operation_group.js" +import { getLine, isLine, lineAtHeight } from "../line/utils_line.js" +import { regChange, regLineChange } from "../display/view_tracking.js" + +// The publicly visible API. Note that methodOp(f) means +// 'wrap f in an operation, performed on its `this` parameter'. + +// This is not the complete set of editor methods. Most of the +// methods defined on the Doc type are also injected into +// CodeMirror.prototype, for backwards compatibility and +// convenience. + +export default function(CodeMirror) { + let optionHandlers = CodeMirror.optionHandlers + + let helpers = CodeMirror.helpers = {} + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus()}, + + setOption: function(option, value) { + let options = this.options, old = options[option] + if (options[option] == value && option != "mode") return + options[option] = value + if (optionHandlers.hasOwnProperty(option)) + operation(this, optionHandlers[option])(this, value, old) + signal(this, "optionChange", this, option) + }, + + getOption: function(option) {return this.options[option]}, + getDoc: function() {return this.doc}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)) + }, + removeKeyMap: function(map) { + let maps = this.state.keyMaps + for (let i = 0; i < maps.length; ++i) + if (maps[i] == map || maps[i].name == map) { + maps.splice(i, 1) + return true + } + }, + + addOverlay: methodOp(function(spec, options) { + let mode = spec.token ? spec : CodeMirror.getMode(this.options, spec) + if (mode.startState) throw new Error("Overlays may not be stateful.") + insertSorted(this.state.overlays, + {mode: mode, modeSpec: spec, opaque: options && options.opaque, + priority: (options && options.priority) || 0}, + overlay => overlay.priority) + this.state.modeGen++ + regChange(this) + }), + removeOverlay: methodOp(function(spec) { + let overlays = this.state.overlays + for (let i = 0; i < overlays.length; ++i) { + let cur = overlays[i].modeSpec + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1) + this.state.modeGen++ + regChange(this) + return + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) dir = this.options.smartIndent ? "smart" : "prev" + else dir = dir ? "add" : "subtract" + } + if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive) + }), + indentSelection: methodOp(function(how) { + let ranges = this.doc.sel.ranges, end = -1 + for (let i = 0; i < ranges.length; i++) { + let range = ranges[i] + if (!range.empty()) { + let from = range.from(), to = range.to() + let start = Math.max(end, from.line) + end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1 + for (let j = start; j < end; ++j) + indentLine(this, j, how) + let newRanges = this.doc.sel.ranges + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll) + } else if (range.head.line > end) { + indentLine(this, range.head.line, how, true) + end = range.head.line + if (i == this.doc.sel.primIndex) ensureCursorVisible(this) + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise) + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true) + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos) + let styles = getLineStyles(this, getLine(this.doc, pos.line)) + let before = 0, after = (styles.length - 1) / 2, ch = pos.ch + let type + if (ch == 0) type = styles[2] + else for (;;) { + let mid = (before + after) >> 1 + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid + else if (styles[mid * 2 + 1] < ch) before = mid + 1 + else { type = styles[mid * 2 + 2]; break } + } + let cut = type ? type.indexOf("overlay ") : -1 + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) + }, + + getModeAt: function(pos) { + let mode = this.doc.mode + if (!mode.innerMode) return mode + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0] + }, + + getHelpers: function(pos, type) { + let found = [] + if (!helpers.hasOwnProperty(type)) return found + let help = helpers[type], mode = this.getModeAt(pos) + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]) + } else if (mode[type]) { + for (let i = 0; i < mode[type].length; i++) { + let val = help[mode[type][i]] + if (val) found.push(val) + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]) + } else if (help[mode.name]) { + found.push(help[mode.name]) + } + for (let i = 0; i < help._global.length; i++) { + let cur = help._global[i] + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val) + } + return found + }, + + getStateAfter: function(line, precise) { + let doc = this.doc + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) + return getContextBefore(this, line + 1, precise).state + }, + + cursorCoords: function(start, mode) { + let pos, range = this.doc.sel.primary() + if (start == null) pos = range.head + else if (typeof start == "object") pos = clipPos(this.doc, start) + else pos = start ? range.from() : range.to() + return cursorCoords(this, pos, mode || "page") + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page") + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page") + return coordsChar(this, coords.left, coords.top) + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top + return lineAtHeight(this.doc, height + this.display.viewOffset) + }, + heightAtLine: function(line, mode, includeWidgets) { + let end = false, lineObj + if (typeof line == "number") { + let last = this.doc.first + this.doc.size - 1 + if (line < this.doc.first) line = this.doc.first + else if (line > last) { line = last; end = true } + lineObj = getLine(this.doc, line) + } else { + lineObj = line + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + + (end ? this.doc.height - heightAtLine(lineObj) : 0) + }, + + defaultTextHeight: function() { return textHeight(this.display) }, + defaultCharWidth: function() { return charWidth(this.display) }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, + + addWidget: function(pos, node, scroll, vert, horiz) { + let display = this.display + pos = cursorCoords(this, clipPos(this.doc, pos)) + let top = pos.bottom, left = pos.left + node.style.position = "absolute" + node.setAttribute("cm-ignore-events", "true") + this.display.input.setUneditable(node) + display.sizer.appendChild(node) + if (vert == "over") { + top = pos.top + } else if (vert == "above" || vert == "near") { + let vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth) + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + top = pos.top - node.offsetHeight + else if (pos.bottom + node.offsetHeight <= vspace) + top = pos.bottom + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth + } + node.style.top = top + "px" + node.style.left = node.style.right = "" + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth + node.style.right = "0px" + } else { + if (horiz == "left") left = 0 + else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2 + node.style.left = left + "px" + } + if (scroll) + scrollIntoView(this, {left, top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}) + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + triggerOnMouseDown: methodOp(onMouseDown), + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd].call(null, this) + }, + + triggerElectric: methodOp(function(text) { triggerElectric(this, text) }), + + findPosH: function(from, amount, unit, visually) { + let dir = 1 + if (amount < 0) { dir = -1; amount = -amount } + let cur = clipPos(this.doc, from) + for (let i = 0; i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually) + if (cur.hitSide) break + } + return cur + }, + + moveH: methodOp(function(dir, unit) { + this.extendSelectionsBy(range => { + if (this.display.shift || this.doc.extend || range.empty()) + return findPosH(this.doc, range.head, dir, unit, this.options.rtlMoveVisually) + else + return dir < 0 ? range.from() : range.to() + }, sel_move) + }), + + deleteH: methodOp(function(dir, unit) { + let sel = this.doc.sel, doc = this.doc + if (sel.somethingSelected()) + doc.replaceSelection("", null, "+delete") + else + deleteNearSelection(this, range => { + let other = findPosH(doc, range.head, dir, unit, false) + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other} + }) + }), + + findPosV: function(from, amount, unit, goalColumn) { + let dir = 1, x = goalColumn + if (amount < 0) { dir = -1; amount = -amount } + let cur = clipPos(this.doc, from) + for (let i = 0; i < amount; ++i) { + let coords = cursorCoords(this, cur, "div") + if (x == null) x = coords.left + else coords.left = x + cur = findPosV(this, coords, dir, unit) + if (cur.hitSide) break + } + return cur + }, + + moveV: methodOp(function(dir, unit) { + let doc = this.doc, goals = [] + let collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected() + doc.extendSelectionsBy(range => { + if (collapse) + return dir < 0 ? range.from() : range.to() + let headPos = cursorCoords(this, range.head, "div") + if (range.goalColumn != null) headPos.left = range.goalColumn + goals.push(headPos.left) + let pos = findPosV(this, headPos, dir, unit) + if (unit == "page" && range == doc.sel.primary()) + addToScrollTop(this, charCoords(this, pos, "div").top - headPos.top) + return pos + }, sel_move) + if (goals.length) for (let i = 0; i < doc.sel.ranges.length; i++) + doc.sel.ranges[i].goalColumn = goals[i] + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + let doc = this.doc, line = getLine(doc, pos.line).text + let start = pos.ch, end = pos.ch + if (line) { + let helper = this.getHelper(pos, "wordChars") + if ((pos.sticky == "before" || end == line.length) && start) --start; else ++end + let startChar = line.charAt(start) + let check = isWordChar(startChar, helper) + ? ch => isWordChar(ch, helper) + : /\s/.test(startChar) ? ch => /\s/.test(ch) + : ch => (!/\s/.test(ch) && !isWordChar(ch)) + while (start > 0 && check(line.charAt(start - 1))) --start + while (end < line.length && check(line.charAt(end))) ++end + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)) + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return + if (this.state.overwrite = !this.state.overwrite) + addClass(this.display.cursorDiv, "CodeMirror-overwrite") + else + rmClass(this.display.cursorDiv, "CodeMirror-overwrite") + + signal(this, "overwriteToggle", this, this.state.overwrite) + }, + hasFocus: function() { return this.display.input.getField() == activeElt() }, + isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, + + scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }), + getScrollInfo: function() { + let scroller = this.display.scroller + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)} + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null} + if (margin == null) margin = this.options.cursorScrollMargin + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null} + } else if (range.from == null) { + range = {from: range, to: null} + } + if (!range.to) range.to = range.from + range.margin = margin || 0 + + if (range.from.line != null) { + scrollToRange(this, range) + } else { + scrollToCoordsRange(this, range.from, range.to, range.margin) + } + }), + + setSize: methodOp(function(width, height) { + let interpret = val => typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val + if (width != null) this.display.wrapper.style.width = interpret(width) + if (height != null) this.display.wrapper.style.height = interpret(height) + if (this.options.lineWrapping) clearLineMeasurementCache(this) + let lineNo = this.display.viewFrom + this.doc.iter(lineNo, this.display.viewTo, line => { + if (line.widgets) for (let i = 0; i < line.widgets.length; i++) + if (line.widgets[i].noHScroll) { regLineChange(this, lineNo, "widget"); break } + ++lineNo + }) + this.curOp.forceUpdate = true + signal(this, "refresh", this) + }), + + operation: function(f){return runInOp(this, f)}, + startOperation: function(){return startOperation(this)}, + endOperation: function(){return endOperation(this)}, + + refresh: methodOp(function() { + let oldHeight = this.display.cachedTextHeight + regChange(this) + this.curOp.forceUpdate = true + clearCaches(this) + scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop) + updateGutterSpace(this.display) + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + estimateLineHeights(this) + signal(this, "refresh", this) + }), + + swapDoc: methodOp(function(doc) { + let old = this.doc + old.cm = null + // Cancel the current text selection if any (#5821) + if (this.state.selectingText) this.state.selectingText() + attachDoc(this, doc) + clearCaches(this) + this.display.input.reset() + scrollToCoords(this, doc.scrollLeft, doc.scrollTop) + this.curOp.forceScroll = true + signalLater(this, "swapDoc", this, old) + return old + }), + + phrase: function(phraseText) { + let phrases = this.options.phrases + return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText + }, + + getInputField: function(){return this.display.input.getField()}, + getWrapperElement: function(){return this.display.wrapper}, + getScrollerElement: function(){return this.display.scroller}, + getGutterElement: function(){return this.display.gutters} + } + eventMixin(CodeMirror) + + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []} + helpers[type][name] = value + } + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value) + helpers[type]._global.push({pred: predicate, val: value}) + } +} + +// Used for horizontal relative motion. Dir is -1 or 1 (left or +// right), unit can be "char", "column" (like char, but doesn't +// cross line boundaries), "word" (across next word), or "group" (to +// the start of next group of word or non-word-non-whitespace +// chars). The visually param controls whether, in right-to-left +// text, direction 1 means to move towards the next index in the +// string, or towards the character to the right of the current +// position. The resulting position will have a hitSide=true +// property if it reached the end of the document. +function findPosH(doc, pos, dir, unit, visually) { + let oldPos = pos + let origDir = dir + let lineObj = getLine(doc, pos.line) + function findNextLine() { + let l = pos.line + dir + if (l < doc.first || l >= doc.first + doc.size) return false + pos = new Pos(l, pos.ch, pos.sticky) + return lineObj = getLine(doc, l) + } + function moveOnce(boundToLine) { + let next + if (visually) { + next = moveVisually(doc.cm, lineObj, pos, dir) + } else { + next = moveLogically(lineObj, pos, dir) + } + if (next == null) { + if (!boundToLine && findNextLine()) + pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir) + else + return false + } else { + pos = next + } + return true + } + + if (unit == "char") { + moveOnce() + } else if (unit == "column") { + moveOnce(true) + } else if (unit == "word" || unit == "group") { + let sawType = null, group = unit == "group" + let helper = doc.cm && doc.cm.getHelper(pos, "wordChars") + for (let first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break + let cur = lineObj.text.charAt(pos.ch) || "\n" + let type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p" + if (group && !first && !type) type = "s" + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"} + break + } + + if (type) sawType = type + if (dir > 0 && !moveOnce(!first)) break + } + } + let result = skipAtomic(doc, pos, oldPos, origDir, true) + if (equalCursorPos(oldPos, result)) result.hitSide = true + return result +} + +// For relative vertical movement. Dir may be -1 or 1. Unit can be +// "page" or "line". The resulting position will have a hitSide=true +// property if it reached the end of the document. +function findPosV(cm, pos, dir, unit) { + let doc = cm.doc, x = pos.left, y + if (unit == "page") { + let pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight) + let moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3) + y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount + + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3 + } + let target + for (;;) { + target = coordsChar(cm, x, y) + if (!target.outside) break + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } + y += dir * 5 + } + return target +} diff --git a/docs/js/node_modules/codemirror/src/edit/mouse_events.js b/docs/js/node_modules/codemirror/src/edit/mouse_events.js new file mode 100644 index 000000000..d0bbfba1d --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/mouse_events.js @@ -0,0 +1,412 @@ +import { delayBlurEvent, ensureFocus } from "../display/focus.js" +import { operation } from "../display/operations.js" +import { visibleLines } from "../display/update_lines.js" +import { clipPos, cmp, maxPos, minPos, Pos } from "../line/pos.js" +import { getLine, lineAtHeight } from "../line/utils_line.js" +import { posFromMouse } from "../measurement/position_measurement.js" +import { eventInWidget } from "../measurement/widgets.js" +import { normalizeSelection, Range, Selection } from "../model/selection.js" +import { extendRange, extendSelection, replaceOneSelection, setSelection } from "../model/selection_updates.js" +import { captureRightClick, chromeOS, ie, ie_version, mac, webkit } from "../util/browser.js" +import { getOrder, getBidiPartAt } from "../util/bidi.js" +import { activeElt } from "../util/dom.js" +import { e_button, e_defaultPrevented, e_preventDefault, e_target, hasHandler, off, on, signal, signalDOMEvent } from "../util/event.js" +import { dragAndDrop } from "../util/feature_detection.js" +import { bind, countColumn, findColumn, sel_mouse } from "../util/misc.js" +import { addModifierNames } from "../input/keymap.js" +import { Pass } from "../util/misc.js" + +import { dispatchKey } from "./key_events.js" +import { commands } from "./commands.js" + +const DOUBLECLICK_DELAY = 400 + +class PastClick { + constructor(time, pos, button) { + this.time = time + this.pos = pos + this.button = button + } + + compare(time, pos, button) { + return this.time + DOUBLECLICK_DELAY > time && + cmp(pos, this.pos) == 0 && button == this.button + } +} + +let lastClick, lastDoubleClick +function clickRepeat(pos, button) { + let now = +new Date + if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { + lastClick = lastDoubleClick = null + return "triple" + } else if (lastClick && lastClick.compare(now, pos, button)) { + lastDoubleClick = new PastClick(now, pos, button) + lastClick = null + return "double" + } else { + lastClick = new PastClick(now, pos, button) + lastDoubleClick = null + return "single" + } +} + +// A mouse down can be a single click, double click, triple click, +// start of selection drag, start of text drag, new cursor +// (ctrl-click), rectangle drag (alt-drag), or xwin +// middle-click-paste. Or it might be a click on something we should +// not interfere with, such as a scrollbar or widget. +export function onMouseDown(e) { + let cm = this, display = cm.display + if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return + display.input.ensurePolled() + display.shift = e.shiftKey + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false + setTimeout(() => display.scroller.draggable = true, 100) + } + return + } + if (clickInGutter(cm, e)) return + let pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single" + window.focus() + + // #3261: make sure, that we're not starting a second selection + if (button == 1 && cm.state.selectingText) + cm.state.selectingText(e) + + if (pos && handleMappedButton(cm, button, pos, repeat, e)) return + + if (button == 1) { + if (pos) leftButtonDown(cm, pos, repeat, e) + else if (e_target(e) == display.scroller) e_preventDefault(e) + } else if (button == 2) { + if (pos) extendSelection(cm.doc, pos) + setTimeout(() => display.input.focus(), 20) + } else if (button == 3) { + if (captureRightClick) cm.display.input.onContextMenu(e) + else delayBlurEvent(cm) + } +} + +function handleMappedButton(cm, button, pos, repeat, event) { + let name = "Click" + if (repeat == "double") name = "Double" + name + else if (repeat == "triple") name = "Triple" + name + name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name + + return dispatchKey(cm, addModifierNames(name, event), event, bound => { + if (typeof bound == "string") bound = commands[bound] + if (!bound) return false + let done = false + try { + if (cm.isReadOnly()) cm.state.suppressEdits = true + done = bound(cm, pos) != Pass + } finally { + cm.state.suppressEdits = false + } + return done + }) +} + +function configureMouse(cm, repeat, event) { + let option = cm.getOption("configureMouse") + let value = option ? option(cm, repeat, event) : {} + if (value.unit == null) { + let rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey + value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line" + } + if (value.extend == null || cm.doc.extend) value.extend = cm.doc.extend || event.shiftKey + if (value.addNew == null) value.addNew = mac ? event.metaKey : event.ctrlKey + if (value.moveOnDrag == null) value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey) + return value +} + +function leftButtonDown(cm, pos, repeat, event) { + if (ie) setTimeout(bind(ensureFocus, cm), 0) + else cm.curOp.focus = activeElt() + + let behavior = configureMouse(cm, repeat, event) + + let sel = cm.doc.sel, contained + if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && + repeat == "single" && (contained = sel.contains(pos)) > -1 && + (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && + (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) + leftButtonStartDrag(cm, event, pos, behavior) + else + leftButtonSelect(cm, event, pos, behavior) +} + +// Start a text drag. When it ends, see if any dragging actually +// happen, and treat as a click if it didn't. +function leftButtonStartDrag(cm, event, pos, behavior) { + let display = cm.display, moved = false + let dragEnd = operation(cm, e => { + if (webkit) display.scroller.draggable = false + cm.state.draggingText = false + off(display.wrapper.ownerDocument, "mouseup", dragEnd) + off(display.wrapper.ownerDocument, "mousemove", mouseMove) + off(display.scroller, "dragstart", dragStart) + off(display.scroller, "drop", dragEnd) + if (!moved) { + e_preventDefault(e) + if (!behavior.addNew) + extendSelection(cm.doc, pos, null, null, behavior.extend) + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) + setTimeout(() => {display.wrapper.ownerDocument.body.focus(); display.input.focus()}, 20) + else + display.input.focus() + } + }) + let mouseMove = function(e2) { + moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10 + } + let dragStart = () => moved = true + // Let the drag handler handle this. + if (webkit) display.scroller.draggable = true + cm.state.draggingText = dragEnd + dragEnd.copy = !behavior.moveOnDrag + // IE's approach to draggable + if (display.scroller.dragDrop) display.scroller.dragDrop() + on(display.wrapper.ownerDocument, "mouseup", dragEnd) + on(display.wrapper.ownerDocument, "mousemove", mouseMove) + on(display.scroller, "dragstart", dragStart) + on(display.scroller, "drop", dragEnd) + + delayBlurEvent(cm) + setTimeout(() => display.input.focus(), 20) +} + +function rangeForUnit(cm, pos, unit) { + if (unit == "char") return new Range(pos, pos) + if (unit == "word") return cm.findWordAt(pos) + if (unit == "line") return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) + let result = unit(cm, pos) + return new Range(result.from, result.to) +} + +// Normal selection, as opposed to text dragging. +function leftButtonSelect(cm, event, start, behavior) { + let display = cm.display, doc = cm.doc + e_preventDefault(event) + + let ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges + if (behavior.addNew && !behavior.extend) { + ourIndex = doc.sel.contains(start) + if (ourIndex > -1) + ourRange = ranges[ourIndex] + else + ourRange = new Range(start, start) + } else { + ourRange = doc.sel.primary() + ourIndex = doc.sel.primIndex + } + + if (behavior.unit == "rectangle") { + if (!behavior.addNew) ourRange = new Range(start, start) + start = posFromMouse(cm, event, true, true) + ourIndex = -1 + } else { + let range = rangeForUnit(cm, start, behavior.unit) + if (behavior.extend) + ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend) + else + ourRange = range + } + + if (!behavior.addNew) { + ourIndex = 0 + setSelection(doc, new Selection([ourRange], 0), sel_mouse) + startSel = doc.sel + } else if (ourIndex == -1) { + ourIndex = ranges.length + setSelection(doc, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}) + } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { + setSelection(doc, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), + {scroll: false, origin: "*mouse"}) + startSel = doc.sel + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse) + } + + let lastPos = start + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) return + lastPos = pos + + if (behavior.unit == "rectangle") { + let ranges = [], tabSize = cm.options.tabSize + let startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize) + let posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize) + let left = Math.min(startCol, posCol), right = Math.max(startCol, posCol) + for (let line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + let text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize) + if (left == right) + ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))) + else if (text.length > leftPos) + ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))) + } + if (!ranges.length) ranges.push(new Range(start, start)) + setSelection(doc, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}) + cm.scrollIntoView(pos) + } else { + let oldRange = ourRange + let range = rangeForUnit(cm, pos, behavior.unit) + let anchor = oldRange.anchor, head + if (cmp(range.anchor, anchor) > 0) { + head = range.head + anchor = minPos(oldRange.from(), range.anchor) + } else { + head = range.anchor + anchor = maxPos(oldRange.to(), range.head) + } + let ranges = startSel.ranges.slice(0) + ranges[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)) + setSelection(doc, normalizeSelection(cm, ranges, ourIndex), sel_mouse) + } + } + + let editorSize = display.wrapper.getBoundingClientRect() + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + let counter = 0 + + function extend(e) { + let curCount = ++counter + let cur = posFromMouse(cm, e, true, behavior.unit == "rectangle") + if (!cur) return + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt() + extendTo(cur) + let visible = visibleLines(display, doc) + if (cur.line >= visible.to || cur.line < visible.from) + setTimeout(operation(cm, () => {if (counter == curCount) extend(e)}), 150) + } else { + let outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0 + if (outside) setTimeout(operation(cm, () => { + if (counter != curCount) return + display.scroller.scrollTop += outside + extend(e) + }), 50) + } + } + + function done(e) { + cm.state.selectingText = false + counter = Infinity + // If e is null or undefined we interpret this as someone trying + // to explicitly cancel the selection rather than the user + // letting go of the mouse button. + if (e) { + e_preventDefault(e) + display.input.focus() + } + off(display.wrapper.ownerDocument, "mousemove", move) + off(display.wrapper.ownerDocument, "mouseup", up) + doc.history.lastSelOrigin = null + } + + let move = operation(cm, e => { + if (e.buttons === 0 || !e_button(e)) done(e) + else extend(e) + }) + let up = operation(cm, done) + cm.state.selectingText = up + on(display.wrapper.ownerDocument, "mousemove", move) + on(display.wrapper.ownerDocument, "mouseup", up) +} + +// Used when mouse-selecting to adjust the anchor to the proper side +// of a bidi jump depending on the visual position of the head. +function bidiSimplify(cm, range) { + let {anchor, head} = range, anchorLine = getLine(cm.doc, anchor.line) + if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) return range + let order = getOrder(anchorLine) + if (!order) return range + let index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index] + if (part.from != anchor.ch && part.to != anchor.ch) return range + let boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1) + if (boundary == 0 || boundary == order.length) return range + + // Compute the relative visual position of the head compared to the + // anchor (<0 is to the left, >0 to the right) + let leftSide + if (head.line != anchor.line) { + leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0 + } else { + let headIndex = getBidiPartAt(order, head.ch, head.sticky) + let dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1) + if (headIndex == boundary - 1 || headIndex == boundary) + leftSide = dir < 0 + else + leftSide = dir > 0 + } + + let usePart = order[boundary + (leftSide ? -1 : 0)] + let from = leftSide == (usePart.level == 1) + let ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before" + return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head) +} + + +// Determines whether an event happened in the gutter, and fires the +// handlers for the corresponding event. +function gutterEvent(cm, e, type, prevent) { + let mX, mY + if (e.touches) { + mX = e.touches[0].clientX + mY = e.touches[0].clientY + } else { + try { mX = e.clientX; mY = e.clientY } + catch(e) { return false } + } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false + if (prevent) e_preventDefault(e) + + let display = cm.display + let lineBox = display.lineDiv.getBoundingClientRect() + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e) + mY -= lineBox.top - display.viewOffset + + for (let i = 0; i < cm.display.gutterSpecs.length; ++i) { + let g = display.gutters.childNodes[i] + if (g && g.getBoundingClientRect().right >= mX) { + let line = lineAtHeight(cm.doc, mY) + let gutter = cm.display.gutterSpecs[i] + signal(cm, type, cm, line, gutter.className, e) + return e_defaultPrevented(e) + } + } +} + +export function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true) +} + +// CONTEXT MENU HANDLING + +// To make the context menu work, we need to briefly unhide the +// textarea (making it as unobtrusive as possible) to let the +// right-click take effect on it. +export function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return + if (signalDOMEvent(cm, e, "contextmenu")) return + if (!captureRightClick) cm.display.input.onContextMenu(e) +} + +function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false + return gutterEvent(cm, e, "gutterContextMenu", false) +} diff --git a/docs/js/node_modules/codemirror/src/edit/options.js b/docs/js/node_modules/codemirror/src/edit/options.js new file mode 100644 index 000000000..3abd3c3c1 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/options.js @@ -0,0 +1,188 @@ +import { onBlur } from "../display/focus.js" +import { getGutters, updateGutters } from "../display/gutters.js" +import { loadMode, resetModeState } from "../display/mode_state.js" +import { initScrollbars, updateScrollbars } from "../display/scrollbars.js" +import { updateSelection } from "../display/selection.js" +import { regChange } from "../display/view_tracking.js" +import { getKeyMap } from "../input/keymap.js" +import { defaultSpecialCharPlaceholder } from "../line/line_data.js" +import { Pos } from "../line/pos.js" +import { findMaxLine } from "../line/spans.js" +import { clearCaches, compensateForHScroll, estimateLineHeights } from "../measurement/position_measurement.js" +import { replaceRange } from "../model/changes.js" +import { mobile, windows } from "../util/browser.js" +import { addClass, rmClass } from "../util/dom.js" +import { off, on } from "../util/event.js" + +import { themeChanged } from "./utils.js" + +export let Init = {toString: function(){return "CodeMirror.Init"}} + +export let defaults = {} +export let optionHandlers = {} + +export function defineOptions(CodeMirror) { + let optionHandlers = CodeMirror.optionHandlers + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt + if (handle) optionHandlers[name] = + notOnInit ? (cm, val, old) => {if (old != Init) handle(cm, val, old)} : handle + } + + CodeMirror.defineOption = option + + // Passed to option handlers when there is no old value. + CodeMirror.Init = Init + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", (cm, val) => cm.setValue(val), true) + option("mode", null, (cm, val) => { + cm.doc.modeOption = val + loadMode(cm) + }, true) + + option("indentUnit", 2, loadMode, true) + option("indentWithTabs", false) + option("smartIndent", true) + option("tabSize", 4, cm => { + resetModeState(cm) + clearCaches(cm) + regChange(cm) + }, true) + + option("lineSeparator", null, (cm, val) => { + cm.doc.lineSep = val + if (!val) return + let newBreaks = [], lineNo = cm.doc.first + cm.doc.iter(line => { + for (let pos = 0;;) { + let found = line.text.indexOf(val, pos) + if (found == -1) break + pos = found + val.length + newBreaks.push(Pos(lineNo, found)) + } + lineNo++ + }) + for (let i = newBreaks.length - 1; i >= 0; i--) + replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) + }) + option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, (cm, val, old) => { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g") + if (old != Init) cm.refresh() + }) + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, cm => cm.refresh(), true) + option("electricChars", true) + option("inputStyle", mobile ? "contenteditable" : "textarea", () => { + throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME + }, true) + option("spellcheck", false, (cm, val) => cm.getInputField().spellcheck = val, true) + option("autocorrect", false, (cm, val) => cm.getInputField().autocorrect = val, true) + option("autocapitalize", false, (cm, val) => cm.getInputField().autocapitalize = val, true) + option("rtlMoveVisually", !windows) + option("wholeLineUpdateBefore", true) + + option("theme", "default", cm => { + themeChanged(cm) + updateGutters(cm) + }, true) + option("keyMap", "default", (cm, val, old) => { + let next = getKeyMap(val) + let prev = old != Init && getKeyMap(old) + if (prev && prev.detach) prev.detach(cm, next) + if (next.attach) next.attach(cm, prev || null) + }) + option("extraKeys", null) + option("configureMouse", null) + + option("lineWrapping", false, wrappingChanged, true) + option("gutters", [], (cm, val) => { + cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers) + updateGutters(cm) + }, true) + option("fixedGutter", true, (cm, val) => { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0" + cm.refresh() + }, true) + option("coverGutterNextToScrollbar", false, cm => updateScrollbars(cm), true) + option("scrollbarStyle", "native", cm => { + initScrollbars(cm) + updateScrollbars(cm) + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop) + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft) + }, true) + option("lineNumbers", false, (cm, val) => { + cm.display.gutterSpecs = getGutters(cm.options.gutters, val) + updateGutters(cm) + }, true) + option("firstLineNumber", 1, updateGutters, true) + option("lineNumberFormatter", integer => integer, updateGutters, true) + option("showCursorWhenSelecting", false, updateSelection, true) + + option("resetSelectionOnContextMenu", true) + option("lineWiseCopyCut", true) + option("pasteLinesPerSelection", true) + option("selectionsMayTouch", false) + + option("readOnly", false, (cm, val) => { + if (val == "nocursor") { + onBlur(cm) + cm.display.input.blur() + } + cm.display.input.readOnlyChanged(val) + }) + option("disableInput", false, (cm, val) => {if (!val) cm.display.input.reset()}, true) + option("dragDrop", true, dragDropChanged) + option("allowDropFileTypes", null) + + option("cursorBlinkRate", 530) + option("cursorScrollMargin", 0) + option("cursorHeight", 1, updateSelection, true) + option("singleCursorHeightPerLine", true, updateSelection, true) + option("workTime", 100) + option("workDelay", 100) + option("flattenSpans", true, resetModeState, true) + option("addModeClass", false, resetModeState, true) + option("pollInterval", 100) + option("undoDepth", 200, (cm, val) => cm.doc.history.undoDepth = val) + option("historyEventDelay", 1250) + option("viewportMargin", 10, cm => cm.refresh(), true) + option("maxHighlightLength", 10000, resetModeState, true) + option("moveInputWithCursor", true, (cm, val) => { + if (!val) cm.display.input.resetPosition() + }) + + option("tabindex", null, (cm, val) => cm.display.input.getField().tabIndex = val || "") + option("autofocus", null) + option("direction", "ltr", (cm, val) => cm.doc.setDirection(val), true) + option("phrases", null) +} + +function dragDropChanged(cm, value, old) { + let wasOn = old && old != Init + if (!value != !wasOn) { + let funcs = cm.display.dragFunctions + let toggle = value ? on : off + toggle(cm.display.scroller, "dragstart", funcs.start) + toggle(cm.display.scroller, "dragenter", funcs.enter) + toggle(cm.display.scroller, "dragover", funcs.over) + toggle(cm.display.scroller, "dragleave", funcs.leave) + toggle(cm.display.scroller, "drop", funcs.drop) + } +} + +function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap") + cm.display.sizer.style.minWidth = "" + cm.display.sizerWidth = null + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap") + findMaxLine(cm) + } + estimateLineHeights(cm) + regChange(cm) + clearCaches(cm) + setTimeout(() => updateScrollbars(cm), 100) +} diff --git a/docs/js/node_modules/codemirror/src/edit/utils.js b/docs/js/node_modules/codemirror/src/edit/utils.js new file mode 100644 index 000000000..fda0be741 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/edit/utils.js @@ -0,0 +1,7 @@ +import { clearCaches } from "../measurement/position_measurement.js" + +export function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-") + clearCaches(cm) +} diff --git a/docs/js/node_modules/codemirror/src/input/ContentEditableInput.js b/docs/js/node_modules/codemirror/src/input/ContentEditableInput.js new file mode 100644 index 000000000..b77c7d49d --- /dev/null +++ b/docs/js/node_modules/codemirror/src/input/ContentEditableInput.js @@ -0,0 +1,527 @@ +import { operation, runInOp } from "../display/operations.js" +import { prepareSelection } from "../display/selection.js" +import { regChange } from "../display/view_tracking.js" +import { applyTextInput, copyableRanges, disableBrowserMagic, handlePaste, hiddenTextarea, lastCopied, setLastCopied } from "./input.js" +import { cmp, maxPos, minPos, Pos } from "../line/pos.js" +import { getBetween, getLine, lineNo } from "../line/utils_line.js" +import { findViewForLine, findViewIndex, mapFromLineView, nodeAndOffsetInLineMap } from "../measurement/position_measurement.js" +import { replaceRange } from "../model/changes.js" +import { simpleSelection } from "../model/selection.js" +import { setSelection } from "../model/selection_updates.js" +import { getBidiPartAt, getOrder } from "../util/bidi.js" +import { android, chrome, gecko, ie_version } from "../util/browser.js" +import { contains, range, removeChildrenAndAdd, selectInput } from "../util/dom.js" +import { on, signalDOMEvent } from "../util/event.js" +import { Delayed, lst, sel_dontScroll } from "../util/misc.js" + +// CONTENTEDITABLE INPUT STYLE + +export default class ContentEditableInput { + constructor(cm) { + this.cm = cm + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null + this.polling = new Delayed() + this.composing = null + this.gracePeriod = false + this.readDOMTimeout = null + } + + init(display) { + let input = this, cm = input.cm + let div = input.div = display.lineDiv + disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize) + + on(div, "paste", e => { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return + // IE doesn't fire input events, so we schedule a read for the pasted content in this way + if (ie_version <= 11) setTimeout(operation(cm, () => this.updateFromDOM()), 20) + }) + + on(div, "compositionstart", e => { + this.composing = {data: e.data, done: false} + }) + on(div, "compositionupdate", e => { + if (!this.composing) this.composing = {data: e.data, done: false} + }) + on(div, "compositionend", e => { + if (this.composing) { + if (e.data != this.composing.data) this.readFromDOMSoon() + this.composing.done = true + } + }) + + on(div, "touchstart", () => input.forceCompositionEnd()) + + on(div, "input", () => { + if (!this.composing) this.readFromDOMSoon() + }) + + function onCopyCut(e) { + if (signalDOMEvent(cm, e)) return + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}) + if (e.type == "cut") cm.replaceSelection("", null, "cut") + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + let ranges = copyableRanges(cm) + setLastCopied({lineWise: true, text: ranges.text}) + if (e.type == "cut") { + cm.operation(() => { + cm.setSelections(ranges.ranges, 0, sel_dontScroll) + cm.replaceSelection("", null, "cut") + }) + } + } + if (e.clipboardData) { + e.clipboardData.clearData() + let content = lastCopied.text.join("\n") + // iOS exposes the clipboard API, but seems to discard content inserted into it + e.clipboardData.setData("Text", content) + if (e.clipboardData.getData("Text") == content) { + e.preventDefault() + return + } + } + // Old-fashioned briefly-focus-a-textarea hack + let kludge = hiddenTextarea(), te = kludge.firstChild + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild) + te.value = lastCopied.text.join("\n") + let hadFocus = document.activeElement + selectInput(te) + setTimeout(() => { + cm.display.lineSpace.removeChild(kludge) + hadFocus.focus() + if (hadFocus == div) input.showPrimarySelection() + }, 50) + } + on(div, "copy", onCopyCut) + on(div, "cut", onCopyCut) + } + + prepareSelection() { + let result = prepareSelection(this.cm, false) + result.focus = this.cm.state.focused + return result + } + + showSelection(info, takeFocus) { + if (!info || !this.cm.display.view.length) return + if (info.focus || takeFocus) this.showPrimarySelection() + this.showMultipleSelections(info) + } + + getSelection() { + return this.cm.display.wrapper.ownerDocument.getSelection() + } + + showPrimarySelection() { + let sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary() + let from = prim.from(), to = prim.to() + + if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { + sel.removeAllRanges() + return + } + + let curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) + let curFocus = domToPos(cm, sel.focusNode, sel.focusOffset) + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), from) == 0 && + cmp(maxPos(curAnchor, curFocus), to) == 0) + return + + let view = cm.display.view + let start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || + {node: view[0].measure.map[2], offset: 0} + let end = to.line < cm.display.viewTo && posToDOM(cm, to) + if (!end) { + let measure = view[view.length - 1].measure + let map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]} + } + + if (!start || !end) { + sel.removeAllRanges() + return + } + + let old = sel.rangeCount && sel.getRangeAt(0), rng + try { rng = range(start.node, start.offset, end.offset, end.node) } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + if (!gecko && cm.state.focused) { + sel.collapse(start.node, start.offset) + if (!rng.collapsed) { + sel.removeAllRanges() + sel.addRange(rng) + } + } else { + sel.removeAllRanges() + sel.addRange(rng) + } + if (old && sel.anchorNode == null) sel.addRange(old) + else if (gecko) this.startGracePeriod() + } + this.rememberSelection() + } + + startGracePeriod() { + clearTimeout(this.gracePeriod) + this.gracePeriod = setTimeout(() => { + this.gracePeriod = false + if (this.selectionChanged()) + this.cm.operation(() => this.cm.curOp.selectionChanged = true) + }, 20) + } + + showMultipleSelections(info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors) + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection) + } + + rememberSelection() { + let sel = this.getSelection() + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset + } + + selectionInEditor() { + let sel = this.getSelection() + if (!sel.rangeCount) return false + let node = sel.getRangeAt(0).commonAncestorContainer + return contains(this.div, node) + } + + focus() { + if (this.cm.options.readOnly != "nocursor") { + if (!this.selectionInEditor()) + this.showSelection(this.prepareSelection(), true) + this.div.focus() + } + } + blur() { this.div.blur() } + getField() { return this.div } + + supportsTouch() { return true } + + receivedFocus() { + let input = this + if (this.selectionInEditor()) + this.pollSelection() + else + runInOp(this.cm, () => input.cm.curOp.selectionChanged = true) + + function poll() { + if (input.cm.state.focused) { + input.pollSelection() + input.polling.set(input.cm.options.pollInterval, poll) + } + } + this.polling.set(this.cm.options.pollInterval, poll) + } + + selectionChanged() { + let sel = this.getSelection() + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset + } + + pollSelection() { + if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) return + let sel = this.getSelection(), cm = this.cm + // On Android Chrome (version 56, at least), backspacing into an + // uneditable block element will put the cursor in that element, + // and then, because it's not editable, hide the virtual keyboard. + // Because Android doesn't allow us to actually detect backspace + // presses in a sane way, this code checks for when that happens + // and simulates a backspace press in this case. + if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) { + this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}) + this.blur() + this.focus() + return + } + if (this.composing) return + this.rememberSelection() + let anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) + let head = domToPos(cm, sel.focusNode, sel.focusOffset) + if (anchor && head) runInOp(cm, () => { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll) + if (anchor.bad || head.bad) cm.curOp.selectionChanged = true + }) + } + + pollContent() { + if (this.readDOMTimeout != null) { + clearTimeout(this.readDOMTimeout) + this.readDOMTimeout = null + } + + let cm = this.cm, display = cm.display, sel = cm.doc.sel.primary() + let from = sel.from(), to = sel.to() + if (from.ch == 0 && from.line > cm.firstLine()) + from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) + if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) + to = Pos(to.line + 1, 0) + if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false + + let fromIndex, fromLine, fromNode + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + fromLine = lineNo(display.view[0].line) + fromNode = display.view[0].node + } else { + fromLine = lineNo(display.view[fromIndex].line) + fromNode = display.view[fromIndex - 1].node.nextSibling + } + let toIndex = findViewIndex(cm, to.line) + let toLine, toNode + if (toIndex == display.view.length - 1) { + toLine = display.viewTo - 1 + toNode = display.lineDiv.lastChild + } else { + toLine = lineNo(display.view[toIndex + 1].line) - 1 + toNode = display.view[toIndex + 1].node.previousSibling + } + + if (!fromNode) return false + let newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)) + let oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)) + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ } + else break + } + + let cutFront = 0, cutEnd = 0 + let newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length) + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + ++cutFront + let newBot = lst(newText), oldBot = lst(oldText) + let maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)) + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + ++cutEnd + // Try to move start of change to start of selection if ambiguous + if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { + while (cutFront && cutFront > from.ch && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { + cutFront-- + cutEnd++ + } + } + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "") + newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "") + + let chFrom = Pos(fromLine, cutFront) + let chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0) + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input") + return true + } + } + + ensurePolled() { + this.forceCompositionEnd() + } + reset() { + this.forceCompositionEnd() + } + forceCompositionEnd() { + if (!this.composing) return + clearTimeout(this.readDOMTimeout) + this.composing = null + this.updateFromDOM() + this.div.blur() + this.div.focus() + } + readFromDOMSoon() { + if (this.readDOMTimeout != null) return + this.readDOMTimeout = setTimeout(() => { + this.readDOMTimeout = null + if (this.composing) { + if (this.composing.done) this.composing = null + else return + } + this.updateFromDOM() + }, 80) + } + + updateFromDOM() { + if (this.cm.isReadOnly() || !this.pollContent()) + runInOp(this.cm, () => regChange(this.cm)) + } + + setUneditable(node) { + node.contentEditable = "false" + } + + onKeyPress(e) { + if (e.charCode == 0 || this.composing) return + e.preventDefault() + if (!this.cm.isReadOnly()) + operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) + } + + readOnlyChanged(val) { + this.div.contentEditable = String(val != "nocursor") + } + + onContextMenu() {} + resetPosition() {} +} + +ContentEditableInput.prototype.needsContentAttribute = true + +function posToDOM(cm, pos) { + let view = findViewForLine(cm, pos.line) + if (!view || view.hidden) return null + let line = getLine(cm.doc, pos.line) + let info = mapFromLineView(view, line, pos.line) + + let order = getOrder(line, cm.doc.direction), side = "left" + if (order) { + let partPos = getBidiPartAt(order, pos.ch) + side = partPos % 2 ? "right" : "left" + } + let result = nodeAndOffsetInLineMap(info.map, pos.ch, side) + result.offset = result.collapse == "right" ? result.end : result.start + return result +} + +function isInGutter(node) { + for (let scan = node; scan; scan = scan.parentNode) + if (/CodeMirror-gutter-wrapper/.test(scan.className)) return true + return false +} + +function badPos(pos, bad) { if (bad) pos.bad = true; return pos } + +function domTextBetween(cm, from, to, fromLine, toLine) { + let text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false + function recognizeMarker(id) { return marker => marker.id == id } + function close() { + if (closing) { + text += lineSep + if (extraLinebreak) text += lineSep + closing = extraLinebreak = false + } + } + function addText(str) { + if (str) { + close() + text += str + } + } + function walk(node) { + if (node.nodeType == 1) { + let cmText = node.getAttribute("cm-text") + if (cmText) { + addText(cmText) + return + } + let markerID = node.getAttribute("cm-marker"), range + if (markerID) { + let found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)) + if (found.length && (range = found[0].find(0))) + addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) + return + } + if (node.getAttribute("contenteditable") == "false") return + let isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName) + if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) return + + if (isBlock) close() + for (let i = 0; i < node.childNodes.length; i++) + walk(node.childNodes[i]) + + if (/^(pre|p)$/i.test(node.nodeName)) extraLinebreak = true + if (isBlock) closing = true + } else if (node.nodeType == 3) { + addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " ")) + } + } + for (;;) { + walk(from) + if (from == to) break + from = from.nextSibling + extraLinebreak = false + } + return text +} + +function domToPos(cm, node, offset) { + let lineNode + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset] + if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) + node = null; offset = 0 + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) return null + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break + } + } + for (let i = 0; i < cm.display.view.length; i++) { + let lineView = cm.display.view[i] + if (lineView.node == lineNode) + return locateNodeInLineView(lineView, node, offset) + } +} + +function locateNodeInLineView(lineView, node, offset) { + let wrapper = lineView.text.firstChild, bad = false + if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true) + if (node == wrapper) { + bad = true + node = wrapper.childNodes[offset] + offset = 0 + if (!node) { + let line = lineView.rest ? lst(lineView.rest) : lineView.line + return badPos(Pos(lineNo(line), line.text.length), bad) + } + } + + let textNode = node.nodeType == 3 ? node : null, topNode = node + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild + if (offset) offset = textNode.nodeValue.length + } + while (topNode.parentNode != wrapper) topNode = topNode.parentNode + let measure = lineView.measure, maps = measure.maps + + function find(textNode, topNode, offset) { + for (let i = -1; i < (maps ? maps.length : 0); i++) { + let map = i < 0 ? measure.map : maps[i] + for (let j = 0; j < map.length; j += 3) { + let curNode = map[j + 2] + if (curNode == textNode || curNode == topNode) { + let line = lineNo(i < 0 ? lineView.line : lineView.rest[i]) + let ch = map[j] + offset + if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)] + return Pos(line, ch) + } + } + } + } + let found = find(textNode, topNode, offset) + if (found) return badPos(found, bad) + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (let after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0) + if (found) + return badPos(Pos(found.line, found.ch - dist), bad) + else + dist += after.textContent.length + } + for (let before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1) + if (found) + return badPos(Pos(found.line, found.ch + dist), bad) + else + dist += before.textContent.length + } +} diff --git a/docs/js/node_modules/codemirror/src/input/TextareaInput.js b/docs/js/node_modules/codemirror/src/input/TextareaInput.js new file mode 100644 index 000000000..ab02230f9 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/input/TextareaInput.js @@ -0,0 +1,365 @@ +import { operation, runInOp } from "../display/operations.js" +import { prepareSelection } from "../display/selection.js" +import { applyTextInput, copyableRanges, handlePaste, hiddenTextarea, setLastCopied } from "./input.js" +import { cursorCoords, posFromMouse } from "../measurement/position_measurement.js" +import { eventInWidget } from "../measurement/widgets.js" +import { simpleSelection } from "../model/selection.js" +import { selectAll, setSelection } from "../model/selection_updates.js" +import { captureRightClick, ie, ie_version, ios, mac, mobile, presto, webkit } from "../util/browser.js" +import { activeElt, removeChildrenAndAdd, selectInput } from "../util/dom.js" +import { e_preventDefault, e_stop, off, on, signalDOMEvent } from "../util/event.js" +import { hasSelection } from "../util/feature_detection.js" +import { Delayed, sel_dontScroll } from "../util/misc.js" + +// TEXTAREA INPUT STYLE + +export default class TextareaInput { + constructor(cm) { + this.cm = cm + // See input.poll and input.reset + this.prevInput = "" + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false + // Self-resetting timeout for the poller + this.polling = new Delayed() + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false + this.composing = null + } + + init(display) { + let input = this, cm = this.cm + this.createField(display) + const te = this.textarea + + display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild) + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) te.style.width = "0px" + + on(te, "input", () => { + if (ie && ie_version >= 9 && this.hasSelection) this.hasSelection = null + input.poll() + }) + + on(te, "paste", e => { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return + + cm.state.pasteIncoming = +new Date + input.fastPoll() + }) + + function prepareCopyCut(e) { + if (signalDOMEvent(cm, e)) return + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}) + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + let ranges = copyableRanges(cm) + setLastCopied({lineWise: true, text: ranges.text}) + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll) + } else { + input.prevInput = "" + te.value = ranges.text.join("\n") + selectInput(te) + } + } + if (e.type == "cut") cm.state.cutIncoming = +new Date + } + on(te, "cut", prepareCopyCut) + on(te, "copy", prepareCopyCut) + + on(display.scroller, "paste", e => { + if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return + if (!te.dispatchEvent) { + cm.state.pasteIncoming = +new Date + input.focus() + return + } + + // Pass the `paste` event to the textarea so it's handled by its event listener. + const event = new Event("paste") + event.clipboardData = e.clipboardData + te.dispatchEvent(event) + }) + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", e => { + if (!eventInWidget(display, e)) e_preventDefault(e) + }) + + on(te, "compositionstart", () => { + let start = cm.getCursor("from") + if (input.composing) input.composing.range.clear() + input.composing = { + start: start, + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) + } + }) + on(te, "compositionend", () => { + if (input.composing) { + input.poll() + input.composing.range.clear() + input.composing = null + } + }) + } + + createField(_display) { + // Wraps and hides input textarea + this.wrapper = hiddenTextarea() + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + this.textarea = this.wrapper.firstChild + } + + prepareSelection() { + // Redraw the selection and/or cursor + let cm = this.cm, display = cm.display, doc = cm.doc + let result = prepareSelection(cm) + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + let headPos = cursorCoords(cm, doc.sel.primary().head, "div") + let wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect() + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)) + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)) + } + + return result + } + + showSelection(drawn) { + let cm = this.cm, display = cm.display + removeChildrenAndAdd(display.cursorDiv, drawn.cursors) + removeChildrenAndAdd(display.selectionDiv, drawn.selection) + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px" + this.wrapper.style.left = drawn.teLeft + "px" + } + } + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + reset(typing) { + if (this.contextMenuPending || this.composing) return + let cm = this.cm + if (cm.somethingSelected()) { + this.prevInput = "" + let content = cm.getSelection() + this.textarea.value = content + if (cm.state.focused) selectInput(this.textarea) + if (ie && ie_version >= 9) this.hasSelection = content + } else if (!typing) { + this.prevInput = this.textarea.value = "" + if (ie && ie_version >= 9) this.hasSelection = null + } + } + + getField() { return this.textarea } + + supportsTouch() { return false } + + focus() { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus() } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } + } + + blur() { this.textarea.blur() } + + resetPosition() { + this.wrapper.style.top = this.wrapper.style.left = 0 + } + + receivedFocus() { this.slowPoll() } + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + slowPoll() { + if (this.pollingFast) return + this.polling.set(this.cm.options.pollInterval, () => { + this.poll() + if (this.cm.state.focused) this.slowPoll() + }) + } + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + fastPoll() { + let missed = false, input = this + input.pollingFast = true + function p() { + let changed = input.poll() + if (!changed && !missed) {missed = true; input.polling.set(60, p)} + else {input.pollingFast = false; input.slowPoll()} + } + input.polling.set(20, p) + } + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + poll() { + let cm = this.cm, input = this.textarea, prevInput = this.prevInput + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (this.contextMenuPending || !cm.state.focused || + (hasSelection(input) && !prevInput && !this.composing) || + cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) + return false + + let text = input.value + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) return false + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset() + return false + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + let first = text.charCodeAt(0) + if (first == 0x200b && !prevInput) prevInput = "\u200b" + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } + } + // Find the part of the input that is actually new + let same = 0, l = Math.min(prevInput.length, text.length) + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same + + runInOp(cm, () => { + applyTextInput(cm, text.slice(same), prevInput.length - same, + null, this.composing ? "*compose" : null) + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = this.prevInput = "" + else this.prevInput = text + + if (this.composing) { + this.composing.range.clear() + this.composing.range = cm.markText(this.composing.start, cm.getCursor("to"), + {className: "CodeMirror-composing"}) + } + }) + return true + } + + ensurePolled() { + if (this.pollingFast && this.poll()) this.pollingFast = false + } + + onKeyPress() { + if (ie && ie_version >= 9) this.hasSelection = null + this.fastPoll() + } + + onContextMenu(e) { + let input = this, cm = input.cm, display = cm.display, te = input.textarea + if (input.contextMenuPending) input.contextMenuPending() + let pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop + if (!pos || presto) return // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + let reset = cm.options.resetSelectionOnContextMenu + if (reset && cm.doc.sel.contains(pos) == -1) + operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) + + let oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText + let wrapperBox = input.wrapper.offsetParent.getBoundingClientRect() + input.wrapper.style.cssText = "position: static" + te.style.cssText = `position: absolute; width: 30px; height: 30px; + top: ${e.clientY - wrapperBox.top - 5}px; left: ${e.clientX - wrapperBox.left - 5}px; + z-index: 1000; background: ${ie ? "rgba(255, 255, 255, .05)" : "transparent"}; + outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);` + let oldScrollY + if (webkit) oldScrollY = window.scrollY // Work around Chrome issue (#2712) + display.input.focus() + if (webkit) window.scrollTo(null, oldScrollY) + display.input.reset() + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) te.value = input.prevInput = " " + input.contextMenuPending = rehide + display.selForContextMenu = cm.doc.sel + clearTimeout(display.detectingSelectAll) + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + let selected = cm.somethingSelected() + let extval = "\u200b" + (selected ? te.value : "") + te.value = "\u21da" // Used to catch context-menu undo + te.value = extval + input.prevInput = selected ? "" : "\u200b" + te.selectionStart = 1; te.selectionEnd = extval.length + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel + } + } + function rehide() { + if (input.contextMenuPending != rehide) return + input.contextMenuPending = false + input.wrapper.style.cssText = oldWrapperCSS + te.style.cssText = oldCSS + if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) prepareSelectAllHack() + let i = 0, poll = () => { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") { + operation(cm, selectAll)(cm) + } else if (i++ < 10) { + display.detectingSelectAll = setTimeout(poll, 500) + } else { + display.selForContextMenu = null + display.input.reset() + } + } + display.detectingSelectAll = setTimeout(poll, 200) + } + } + + if (ie && ie_version >= 9) prepareSelectAllHack() + if (captureRightClick) { + e_stop(e) + let mouseup = () => { + off(window, "mouseup", mouseup) + setTimeout(rehide, 20) + } + on(window, "mouseup", mouseup) + } else { + setTimeout(rehide, 50) + } + } + + readOnlyChanged(val) { + if (!val) this.reset() + this.textarea.disabled = val == "nocursor" + } + + setUneditable() {} +} + +TextareaInput.prototype.needsContentAttribute = false diff --git a/docs/js/node_modules/codemirror/src/input/indent.js b/docs/js/node_modules/codemirror/src/input/indent.js new file mode 100644 index 000000000..c88772cb6 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/input/indent.js @@ -0,0 +1,71 @@ +import { getContextBefore } from "../line/highlight.js" +import { Pos } from "../line/pos.js" +import { getLine } from "../line/utils_line.js" +import { replaceRange } from "../model/changes.js" +import { Range } from "../model/selection.js" +import { replaceOneSelection } from "../model/selection_updates.js" +import { countColumn, Pass, spaceStr } from "../util/misc.js" + +// Indent the given line. The how parameter can be "smart", +// "add"/null, "subtract", or "prev". When aggressive is false +// (typically set to true for forced single-line indents), empty +// lines are not indented, and places where the mode returns Pass +// are left alone. +export function indentLine(cm, n, how, aggressive) { + let doc = cm.doc, state + if (how == null) how = "add" + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) how = "prev" + else state = getContextBefore(cm, n).state + } + + let tabSize = cm.options.tabSize + let line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize) + if (line.stateAfter) line.stateAfter = null + let curSpaceString = line.text.match(/^\s*/)[0], indentation + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0 + how = "not" + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text) + if (indentation == Pass || indentation > 150) { + if (!aggressive) return + how = "prev" + } + } + if (how == "prev") { + if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize) + else indentation = 0 + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit + } else if (typeof how == "number") { + indentation = curSpace + how + } + indentation = Math.max(0, indentation) + + let indentString = "", pos = 0 + if (cm.options.indentWithTabs) + for (let i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t"} + if (pos < indentation) indentString += spaceStr(indentation - pos) + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input") + line.stateAfter = null + return true + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (let i = 0; i < doc.sel.ranges.length; i++) { + let range = doc.sel.ranges[i] + if (range.head.line == n && range.head.ch < curSpaceString.length) { + let pos = Pos(n, curSpaceString.length) + replaceOneSelection(doc, i, new Range(pos, pos)) + break + } + } + } +} diff --git a/docs/js/node_modules/codemirror/src/input/input.js b/docs/js/node_modules/codemirror/src/input/input.js new file mode 100644 index 000000000..26bba1d26 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/input/input.js @@ -0,0 +1,135 @@ +import { runInOp } from "../display/operations.js" +import { ensureCursorVisible } from "../display/scrolling.js" +import { Pos } from "../line/pos.js" +import { getLine } from "../line/utils_line.js" +import { makeChange } from "../model/changes.js" +import { ios, webkit } from "../util/browser.js" +import { elt } from "../util/dom.js" +import { lst, map } from "../util/misc.js" +import { signalLater } from "../util/operation_group.js" +import { splitLinesAuto } from "../util/feature_detection.js" + +import { indentLine } from "./indent.js" + +// This will be set to a {lineWise: bool, text: [string]} object, so +// that, when pasting, we know what kind of selections the copied +// text was made out of. +export let lastCopied = null + +export function setLastCopied(newLastCopied) { + lastCopied = newLastCopied +} + +export function applyTextInput(cm, inserted, deleted, sel, origin) { + let doc = cm.doc + cm.display.shift = false + if (!sel) sel = doc.sel + + let recent = +new Date - 200 + let paste = origin == "paste" || cm.state.pasteIncoming > recent + let textLines = splitLinesAuto(inserted), multiPaste = null + // When pasting N lines into N selections, insert one line per selection + if (paste && sel.ranges.length > 1) { + if (lastCopied && lastCopied.text.join("\n") == inserted) { + if (sel.ranges.length % lastCopied.text.length == 0) { + multiPaste = [] + for (let i = 0; i < lastCopied.text.length; i++) + multiPaste.push(doc.splitLines(lastCopied.text[i])) + } + } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { + multiPaste = map(textLines, l => [l]) + } + } + + let updateInput = cm.curOp.updateInput + // Normal behavior is to insert the new text into every selection + for (let i = sel.ranges.length - 1; i >= 0; i--) { + let range = sel.ranges[i] + let from = range.from(), to = range.to() + if (range.empty()) { + if (deleted && deleted > 0) // Handle deletion + from = Pos(from.line, from.ch - deleted) + else if (cm.state.overwrite && !paste) // Handle overwrite + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)) + else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted) + from = to = Pos(from.line, 0) + } + let changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, + origin: origin || (paste ? "paste" : cm.state.cutIncoming > recent ? "cut" : "+input")} + makeChange(cm.doc, changeEvent) + signalLater(cm, "inputRead", cm, changeEvent) + } + if (inserted && !paste) + triggerElectric(cm, inserted) + + ensureCursorVisible(cm) + if (cm.curOp.updateInput < 2) cm.curOp.updateInput = updateInput + cm.curOp.typing = true + cm.state.pasteIncoming = cm.state.cutIncoming = -1 +} + +export function handlePaste(e, cm) { + let pasted = e.clipboardData && e.clipboardData.getData("Text") + if (pasted) { + e.preventDefault() + if (!cm.isReadOnly() && !cm.options.disableInput) + runInOp(cm, () => applyTextInput(cm, pasted, 0, null, "paste")) + return true + } +} + +export function triggerElectric(cm, inserted) { + // When an 'electric' character is inserted, immediately trigger a reindent + if (!cm.options.electricChars || !cm.options.smartIndent) return + let sel = cm.doc.sel + + for (let i = sel.ranges.length - 1; i >= 0; i--) { + let range = sel.ranges[i] + if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) continue + let mode = cm.getModeAt(range.head) + let indented = false + if (mode.electricChars) { + for (let j = 0; j < mode.electricChars.length; j++) + if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indented = indentLine(cm, range.head.line, "smart") + break + } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) + indented = indentLine(cm, range.head.line, "smart") + } + if (indented) signalLater(cm, "electricInput", cm, range.head.line) + } +} + +export function copyableRanges(cm) { + let text = [], ranges = [] + for (let i = 0; i < cm.doc.sel.ranges.length; i++) { + let line = cm.doc.sel.ranges[i].head.line + let lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)} + ranges.push(lineRange) + text.push(cm.getRange(lineRange.anchor, lineRange.head)) + } + return {text: text, ranges: ranges} +} + +export function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) { + field.setAttribute("autocorrect", autocorrect ? "" : "off") + field.setAttribute("autocapitalize", autocapitalize ? "" : "off") + field.setAttribute("spellcheck", !!spellcheck) +} + +export function hiddenTextarea() { + let te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none") + let div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;") + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) te.style.width = "1000px" + else te.setAttribute("wrap", "off") + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) te.style.border = "1px solid black" + disableBrowserMagic(te) + return div +} diff --git a/docs/js/node_modules/codemirror/src/input/keymap.js b/docs/js/node_modules/codemirror/src/input/keymap.js new file mode 100644 index 000000000..046b3505a --- /dev/null +++ b/docs/js/node_modules/codemirror/src/input/keymap.js @@ -0,0 +1,148 @@ +import { flipCtrlCmd, mac, presto } from "../util/browser.js" +import { map } from "../util/misc.js" + +import { keyNames } from "./keynames.js" + +export let keyMap = {} + +keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" +} +// Note that the save and find-related commands aren't defined by +// default. User code or addons can define them. Unknown commands +// are simply ignored. +keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + "fallthrough": "basic" +} +// Very basic readline/emacs-style bindings, which are standard on Mac. +keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", + "Ctrl-O": "openLine" +} +keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + "fallthrough": ["basic", "emacsy"] +} +keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault + +// KEYMAP DISPATCH + +function normalizeKeyName(name) { + let parts = name.split(/-(?!$)/) + name = parts[parts.length - 1] + let alt, ctrl, shift, cmd + for (let i = 0; i < parts.length - 1; i++) { + let mod = parts[i] + if (/^(cmd|meta|m)$/i.test(mod)) cmd = true + else if (/^a(lt)?$/i.test(mod)) alt = true + else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true + else if (/^s(hift)?$/i.test(mod)) shift = true + else throw new Error("Unrecognized modifier name: " + mod) + } + if (alt) name = "Alt-" + name + if (ctrl) name = "Ctrl-" + name + if (cmd) name = "Cmd-" + name + if (shift) name = "Shift-" + name + return name +} + +// This is a kludge to keep keymaps mostly working as raw objects +// (backwards compatibility) while at the same time support features +// like normalization and multi-stroke key bindings. It compiles a +// new normalized keymap, and then updates the old object to reflect +// this. +export function normalizeKeyMap(keymap) { + let copy = {} + for (let keyname in keymap) if (keymap.hasOwnProperty(keyname)) { + let value = keymap[keyname] + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue + if (value == "...") { delete keymap[keyname]; continue } + + let keys = map(keyname.split(" "), normalizeKeyName) + for (let i = 0; i < keys.length; i++) { + let val, name + if (i == keys.length - 1) { + name = keys.join(" ") + val = value + } else { + name = keys.slice(0, i + 1).join(" ") + val = "..." + } + let prev = copy[name] + if (!prev) copy[name] = val + else if (prev != val) throw new Error("Inconsistent bindings for " + name) + } + delete keymap[keyname] + } + for (let prop in copy) keymap[prop] = copy[prop] + return keymap +} + +export function lookupKey(key, map, handle, context) { + map = getKeyMap(map) + let found = map.call ? map.call(key, context) : map[key] + if (found === false) return "nothing" + if (found === "...") return "multi" + if (found != null && handle(found)) return "handled" + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + return lookupKey(key, map.fallthrough, handle, context) + for (let i = 0; i < map.fallthrough.length; i++) { + let result = lookupKey(key, map.fallthrough[i], handle, context) + if (result) return result + } + } +} + +// Modifier key presses don't count as 'real' key presses for the +// purpose of keymap fallthrough. +export function isModifierKey(value) { + let name = typeof value == "string" ? value : keyNames[value.keyCode] + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" +} + +export function addModifierNames(name, event, noShift) { + let base = name + if (event.altKey && base != "Alt") name = "Alt-" + name + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name + if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name + return name +} + +// Look up the name of a key as indicated by an event object. +export function keyName(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) return false + let name = keyNames[event.keyCode] + if (name == null || event.altGraphKey) return false + // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause, + // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+) + if (event.keyCode == 3 && event.code) name = event.code + return addModifierNames(name, event, noShift) +} + +export function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val +} diff --git a/docs/js/node_modules/codemirror/src/input/keynames.js b/docs/js/node_modules/codemirror/src/input/keynames.js new file mode 100644 index 000000000..d3339038f --- /dev/null +++ b/docs/js/node_modules/codemirror/src/input/keynames.js @@ -0,0 +1,17 @@ +export let keyNames = { + 3: "Pause", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", + 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 145: "ScrollLock", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" +} + +// Number keys +for (let i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i) +// Alphabetic keys +for (let i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i) +// Function keys +for (let i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i diff --git a/docs/js/node_modules/codemirror/src/input/movement.js b/docs/js/node_modules/codemirror/src/input/movement.js new file mode 100644 index 000000000..8d50fd2a0 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/input/movement.js @@ -0,0 +1,110 @@ +import { Pos } from "../line/pos.js" +import { prepareMeasureForLine, measureCharPrepared, wrappedLineExtentChar } from "../measurement/position_measurement.js" +import { getBidiPartAt, getOrder } from "../util/bidi.js" +import { findFirst, lst, skipExtendingChars } from "../util/misc.js" + +function moveCharLogically(line, ch, dir) { + let target = skipExtendingChars(line.text, ch + dir, dir) + return target < 0 || target > line.text.length ? null : target +} + +export function moveLogically(line, start, dir) { + let ch = moveCharLogically(line, start.ch, dir) + return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") +} + +export function endOfLine(visually, cm, lineObj, lineNo, dir) { + if (visually) { + let order = getOrder(lineObj, cm.doc.direction) + if (order) { + let part = dir < 0 ? lst(order) : order[0] + let moveInStorageOrder = (dir < 0) == (part.level == 1) + let sticky = moveInStorageOrder ? "after" : "before" + let ch + // With a wrapped rtl chunk (possibly spanning multiple bidi parts), + // it could be that the last bidi part is not on the last visual line, + // since visual lines contain content order-consecutive chunks. + // Thus, in rtl, we are looking for the first (content-order) character + // in the rtl chunk that is on the last line (that is, the same line + // as the last (content-order) character). + if (part.level > 0 || cm.doc.direction == "rtl") { + let prep = prepareMeasureForLine(cm, lineObj) + ch = dir < 0 ? lineObj.text.length - 1 : 0 + let targetTop = measureCharPrepared(cm, prep, ch).top + ch = findFirst(ch => measureCharPrepared(cm, prep, ch).top == targetTop, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch) + if (sticky == "before") ch = moveCharLogically(lineObj, ch, 1) + } else ch = dir < 0 ? part.to : part.from + return new Pos(lineNo, ch, sticky) + } + } + return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") +} + +export function moveVisually(cm, line, start, dir) { + let bidi = getOrder(line, cm.doc.direction) + if (!bidi) return moveLogically(line, start, dir) + if (start.ch >= line.text.length) { + start.ch = line.text.length + start.sticky = "before" + } else if (start.ch <= 0) { + start.ch = 0 + start.sticky = "after" + } + let partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos] + if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { + // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, + // nothing interesting happens. + return moveLogically(line, start, dir) + } + + let mv = (pos, dir) => moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir) + let prep + let getWrappedLineExtent = ch => { + if (!cm.options.lineWrapping) return {begin: 0, end: line.text.length} + prep = prep || prepareMeasureForLine(cm, line) + return wrappedLineExtentChar(cm, line, prep, ch) + } + let wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch) + + if (cm.doc.direction == "rtl" || part.level == 1) { + let moveInStorageOrder = (part.level == 1) == (dir < 0) + let ch = mv(start, moveInStorageOrder ? 1 : -1) + if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { + // Case 2: We move within an rtl part or in an rtl editor on the same visual line + let sticky = moveInStorageOrder ? "before" : "after" + return new Pos(start.line, ch, sticky) + } + } + + // Case 3: Could not move within this bidi part in this visual line, so leave + // the current bidi part + + let searchInVisualLine = (partPos, dir, wrappedLineExtent) => { + let getRes = (ch, moveInStorageOrder) => moveInStorageOrder + ? new Pos(start.line, mv(ch, 1), "before") + : new Pos(start.line, ch, "after") + + for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { + let part = bidi[partPos] + let moveInStorageOrder = (dir > 0) == (part.level != 1) + let ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1) + if (part.from <= ch && ch < part.to) return getRes(ch, moveInStorageOrder) + ch = moveInStorageOrder ? part.from : mv(part.to, -1) + if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) return getRes(ch, moveInStorageOrder) + } + } + + // Case 3a: Look for other bidi parts on the same visual line + let res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent) + if (res) return res + + // Case 3b: Look for other bidi parts on the next visual line + let nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1) + if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { + res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)) + if (res) return res + } + + // Case 4: Nowhere to move + return null +} diff --git a/docs/js/node_modules/codemirror/src/line/highlight.js b/docs/js/node_modules/codemirror/src/line/highlight.js new file mode 100644 index 000000000..9835d4626 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/line/highlight.js @@ -0,0 +1,284 @@ +import { countColumn } from "../util/misc.js" +import { copyState, innerMode, startState } from "../modes.js" +import StringStream from "../util/StringStream.js" + +import { getLine, lineNo } from "./utils_line.js" +import { clipPos } from "./pos.js" + +class SavedContext { + constructor(state, lookAhead) { + this.state = state + this.lookAhead = lookAhead + } +} + +class Context { + constructor(doc, state, line, lookAhead) { + this.state = state + this.doc = doc + this.line = line + this.maxLookAhead = lookAhead || 0 + this.baseTokens = null + this.baseTokenPos = 1 + } + + lookAhead(n) { + let line = this.doc.getLine(this.line + n) + if (line != null && n > this.maxLookAhead) this.maxLookAhead = n + return line + } + + baseToken(n) { + if (!this.baseTokens) return null + while (this.baseTokens[this.baseTokenPos] <= n) + this.baseTokenPos += 2 + let type = this.baseTokens[this.baseTokenPos + 1] + return {type: type && type.replace(/( |^)overlay .*/, ""), + size: this.baseTokens[this.baseTokenPos] - n} + } + + nextLine() { + this.line++ + if (this.maxLookAhead > 0) this.maxLookAhead-- + } + + static fromSaved(doc, saved, line) { + if (saved instanceof SavedContext) + return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) + else + return new Context(doc, copyState(doc.mode, saved), line) + } + + save(copy) { + let state = copy !== false ? copyState(this.doc.mode, this.state) : this.state + return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state + } +} + + +// Compute a style array (an array starting with a mode generation +// -- for invalidation -- followed by pairs of end positions and +// style strings), which is used to highlight the tokens on the +// line. +export function highlightLine(cm, line, context, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + let st = [cm.state.modeGen], lineClasses = {} + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, context, (end, style) => st.push(end, style), + lineClasses, forceToEnd) + let state = context.state + + // Run overlays, adjust style array. + for (let o = 0; o < cm.state.overlays.length; ++o) { + context.baseTokens = st + let overlay = cm.state.overlays[o], i = 1, at = 0 + context.state = true + runMode(cm, line.text, overlay.mode, context, (end, style) => { + let start = i + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + let i_end = st[i] + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end) + i += 2 + at = Math.min(end, i_end) + } + if (!style) return + if (overlay.opaque) { + st.splice(start, i - start, end, "overlay " + style) + i = start + 2 + } else { + for (; start < i; start += 2) { + let cur = st[start+1] + st[start+1] = (cur ? cur + " " : "") + "overlay " + style + } + } + }, lineClasses) + context.state = state + context.baseTokens = null + context.baseTokenPos = 1 + } + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} +} + +export function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + let context = getContextBefore(cm, lineNo(line)) + let resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state) + let result = highlightLine(cm, line, context) + if (resetState) context.state = resetState + line.stateAfter = context.save(!resetState) + line.styles = result.styles + if (result.classes) line.styleClasses = result.classes + else if (line.styleClasses) line.styleClasses = null + if (updateFrontier === cm.doc.highlightFrontier) + cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier) + } + return line.styles +} + +export function getContextBefore(cm, n, precise) { + let doc = cm.doc, display = cm.display + if (!doc.mode.startState) return new Context(doc, true, n) + let start = findStartLine(cm, n, precise) + let saved = start > doc.first && getLine(doc, start - 1).stateAfter + let context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start) + + doc.iter(start, n, line => { + processLine(cm, line.text, context) + let pos = context.line + line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null + context.nextLine() + }) + if (precise) doc.modeFrontier = context.line + return context +} + +// Lightweight form of highlight -- proceed over this line and +// update state, but don't save a style array. Used for lines that +// aren't currently visible. +export function processLine(cm, text, context, startAt) { + let mode = cm.doc.mode + let stream = new StringStream(text, cm.options.tabSize, context) + stream.start = stream.pos = startAt || 0 + if (text == "") callBlankLine(mode, context.state) + while (!stream.eol()) { + readToken(mode, stream, context.state) + stream.start = stream.pos + } +} + +function callBlankLine(mode, state) { + if (mode.blankLine) return mode.blankLine(state) + if (!mode.innerMode) return + let inner = innerMode(mode, state) + if (inner.mode.blankLine) return inner.mode.blankLine(inner.state) +} + +function readToken(mode, stream, state, inner) { + for (let i = 0; i < 10; i++) { + if (inner) inner[0] = innerMode(mode, state).mode + let style = mode.token(stream, state) + if (stream.pos > stream.start) return style + } + throw new Error("Mode " + mode.name + " failed to advance stream.") +} + +class Token { + constructor(stream, type, state) { + this.start = stream.start; this.end = stream.pos + this.string = stream.current() + this.type = type || null + this.state = state + } +} + +// Utility for getTokenAt and getLineTokens +export function takeToken(cm, pos, precise, asArray) { + let doc = cm.doc, mode = doc.mode, style + pos = clipPos(doc, pos) + let line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise) + let stream = new StringStream(line.text, cm.options.tabSize, context), tokens + if (asArray) tokens = [] + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos + style = readToken(mode, stream, context.state) + if (asArray) tokens.push(new Token(stream, style, copyState(doc.mode, context.state))) + } + return asArray ? tokens : new Token(stream, style, context.state) +} + +function extractLineClasses(type, output) { + if (type) for (;;) { + let lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/) + if (!lineClass) break + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length) + let prop = lineClass[1] ? "bgClass" : "textClass" + if (output[prop] == null) + output[prop] = lineClass[2] + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + output[prop] += " " + lineClass[2] + } + return type +} + +// Run the given mode's parser over a line, calling f for each token. +function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { + let flattenSpans = mode.flattenSpans + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans + let curStart = 0, curStyle = null + let stream = new StringStream(text, cm.options.tabSize, context), style + let inner = cm.options.addModeClass && [null] + if (text == "") extractLineClasses(callBlankLine(mode, context.state), lineClasses) + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false + if (forceToEnd) processLine(cm, text, context, stream.pos) + stream.pos = text.length + style = null + } else { + style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses) + } + if (inner) { + let mName = inner[0].name + if (mName) style = "m-" + (style ? mName + " " + style : mName) + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 5000) + f(curStart, curStyle) + } + curStyle = style + } + stream.start = stream.pos + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 + // characters, and returns inaccurate measurements in nodes + // starting around 5000 chars. + let pos = Math.min(stream.pos, curStart + 5000) + f(pos, curStyle) + curStart = pos + } +} + +// Finds the line to start with when starting a parse. Tries to +// find a line with a stateAfter, so that it can start with a +// valid state. If that fails, it returns the line with the +// smallest indentation, which tends to need the least context to +// parse correctly. +function findStartLine(cm, n, precise) { + let minindent, minline, doc = cm.doc + let lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100) + for (let search = n; search > lim; --search) { + if (search <= doc.first) return doc.first + let line = getLine(doc, search - 1), after = line.stateAfter + if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) + return search + let indented = countColumn(line.text, null, cm.options.tabSize) + if (minline == null || minindent > indented) { + minline = search - 1 + minindent = indented + } + } + return minline +} + +export function retreatFrontier(doc, n) { + doc.modeFrontier = Math.min(doc.modeFrontier, n) + if (doc.highlightFrontier < n - 10) return + let start = doc.first + for (let line = n - 1; line > start; line--) { + let saved = getLine(doc, line).stateAfter + // change is on 3 + // state on line 1 looked ahead 2 -- so saw 3 + // test 1 + 2 < 3 should cover this + if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { + start = line + 1 + break + } + } + doc.highlightFrontier = Math.min(doc.highlightFrontier, start) +} diff --git a/docs/js/node_modules/codemirror/src/line/line_data.js b/docs/js/node_modules/codemirror/src/line/line_data.js new file mode 100644 index 000000000..20dd43283 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/line/line_data.js @@ -0,0 +1,349 @@ +import { getOrder } from "../util/bidi.js" +import { ie, ie_version, webkit } from "../util/browser.js" +import { elt, eltP, joinClasses } from "../util/dom.js" +import { eventMixin, signal } from "../util/event.js" +import { hasBadBidiRects, zeroWidthElement } from "../util/feature_detection.js" +import { lst, spaceStr } from "../util/misc.js" + +import { getLineStyles } from "./highlight.js" +import { attachMarkedSpans, compareCollapsedMarkers, detachMarkedSpans, lineIsHidden, visualLineContinued } from "./spans.js" +import { getLine, lineNo, updateLineHeight } from "./utils_line.js" + +// LINE DATA STRUCTURE + +// Line objects. These hold state related to a line, including +// highlighting info (the styles array). +export class Line { + constructor(text, markedSpans, estimateHeight) { + this.text = text + attachMarkedSpans(this, markedSpans) + this.height = estimateHeight ? estimateHeight(this) : 1 + } + + lineNo() { return lineNo(this) } +} +eventMixin(Line) + +// Change the content (text, markers) of a line. Automatically +// invalidates cached information and tries to re-estimate the +// line's height. +export function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text + if (line.stateAfter) line.stateAfter = null + if (line.styles) line.styles = null + if (line.order != null) line.order = null + detachMarkedSpans(line) + attachMarkedSpans(line, markedSpans) + let estHeight = estimateHeight ? estimateHeight(line) : 1 + if (estHeight != line.height) updateLineHeight(line, estHeight) +} + +// Detach a line from the document tree and its markers. +export function cleanUpLine(line) { + line.parent = null + detachMarkedSpans(line) +} + +// Convert a style as returned by a mode (either null, or a string +// containing one or more styles) to a CSS style. This is cached, +// and also looks for line-wide styles. +let styleToClassCache = {}, styleToClassCacheWithMode = {} +function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) return null + let cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")) +} + +// Render the DOM representation of the text of a line. Also builds +// up a 'line map', which points at the DOM nodes that represent +// specific stretches of text, and is used by the measuring code. +// The returned object contains the DOM node, this map, and +// information about line-wide styles that were set by the mode. +export function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + let content = eltP("span", null, null, webkit ? "padding-right: .1px" : null) + let builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, + col: 0, pos: 0, cm: cm, + trailingSpace: false, + splitSpaces: cm.getOption("lineWrapping")} + lineView.measure = {} + + // Iterate over the logical lines that make up this visual line. + for (let i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + let line = i ? lineView.rest[i - 1] : lineView.line, order + builder.pos = 0 + builder.addToken = buildToken + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) + builder.addToken = buildTokenBadBidi(builder.addToken, order) + builder.map = [] + let allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line) + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)) + if (line.styleClasses) { + if (line.styleClasses.bgClass) + builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "") + if (line.styleClasses.textClass) + builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "") + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))) + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map + lineView.measure.cache = {} + } else { + ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) + ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}) + } + } + + // See issue #2901 + if (webkit) { + let last = builder.content.lastChild + if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) + builder.content.className = "cm-tab-wrap-hack" + } + + signal(cm, "renderLine", cm, lineView.line, builder.pre) + if (builder.pre.className) + builder.textClass = joinClasses(builder.pre.className, builder.textClass || "") + + return builder +} + +export function defaultSpecialCharPlaceholder(ch) { + let token = elt("span", "\u2022", "cm-invalidchar") + token.title = "\\u" + ch.charCodeAt(0).toString(16) + token.setAttribute("aria-label", token.title) + return token +} + +// Build up the DOM representation for a single token, and add it to +// the line map. Takes care to render special characters separately. +function buildToken(builder, text, style, startStyle, endStyle, css, attributes) { + if (!text) return + let displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text + let special = builder.cm.state.specialChars, mustWrap = false + let content + if (!special.test(text)) { + builder.col += text.length + content = document.createTextNode(displayText) + builder.map.push(builder.pos, builder.pos + text.length, content) + if (ie && ie_version < 9) mustWrap = true + builder.pos += text.length + } else { + content = document.createDocumentFragment() + let pos = 0 + while (true) { + special.lastIndex = pos + let m = special.exec(text) + let skipped = m ? m.index - pos : text.length - pos + if (skipped) { + let txt = document.createTextNode(displayText.slice(pos, pos + skipped)) + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])) + else content.appendChild(txt) + builder.map.push(builder.pos, builder.pos + skipped, txt) + builder.col += skipped + builder.pos += skipped + } + if (!m) break + pos += skipped + 1 + let txt + if (m[0] == "\t") { + let tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize + txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")) + txt.setAttribute("role", "presentation") + txt.setAttribute("cm-text", "\t") + builder.col += tabWidth + } else if (m[0] == "\r" || m[0] == "\n") { + txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")) + txt.setAttribute("cm-text", m[0]) + builder.col += 1 + } else { + txt = builder.cm.options.specialCharPlaceholder(m[0]) + txt.setAttribute("cm-text", m[0]) + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])) + else content.appendChild(txt) + builder.col += 1 + } + builder.map.push(builder.pos, builder.pos + 1, txt) + builder.pos++ + } + } + builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 + if (style || startStyle || endStyle || mustWrap || css) { + let fullStyle = style || "" + if (startStyle) fullStyle += startStyle + if (endStyle) fullStyle += endStyle + let token = elt("span", [content], fullStyle, css) + if (attributes) { + for (let attr in attributes) if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class") + token.setAttribute(attr, attributes[attr]) + } + return builder.content.appendChild(token) + } + builder.content.appendChild(content) +} + +// Change some spaces to NBSP to prevent the browser from collapsing +// trailing spaces at the end of a line when rendering text (issue #1362). +function splitSpaces(text, trailingBefore) { + if (text.length > 1 && !/ /.test(text)) return text + let spaceBefore = trailingBefore, result = "" + for (let i = 0; i < text.length; i++) { + let ch = text.charAt(i) + if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) + ch = "\u00a0" + result += ch + spaceBefore = ch == " " + } + return result +} + +// Work around nonsense dimensions being reported for stretches of +// right-to-left text. +function buildTokenBadBidi(inner, order) { + return (builder, text, style, startStyle, endStyle, css, attributes) => { + style = style ? style + " cm-force-border" : "cm-force-border" + let start = builder.pos, end = start + text.length + for (;;) { + // Find the part that overlaps with the start of this text + let part + for (let i = 0; i < order.length; i++) { + part = order[i] + if (part.to > start && part.from <= start) break + } + if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, css, attributes) + inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes) + startStyle = null + text = text.slice(part.to - start) + start = part.to + } + } +} + +function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + let widget = !ignoreWidget && marker.widgetNode + if (widget) builder.map.push(builder.pos, builder.pos + size, widget) + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + widget = builder.content.appendChild(document.createElement("span")) + widget.setAttribute("cm-marker", marker.id) + } + if (widget) { + builder.cm.display.input.setUneditable(widget) + builder.content.appendChild(widget) + } + builder.pos += size + builder.trailingSpace = false +} + +// Outputs a number of spans to make up a line, taking highlighting +// and marked text into account. +function insertLineContent(line, builder, styles) { + let spans = line.markedSpans, allText = line.text, at = 0 + if (!spans) { + for (let i = 1; i < styles.length; i+=2) + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)) + return + } + + let len = allText.length, pos = 0, i = 1, text = "", style, css + let nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = css = "" + attributes = null + collapsed = null; nextChange = Infinity + let foundBookmarks = [], endStyles + for (let j = 0; j < spans.length; ++j) { + let sp = spans[j], m = sp.marker + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { + foundBookmarks.push(m) + } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { + if (sp.to != null && sp.to != pos && nextChange > sp.to) { + nextChange = sp.to + spanEndStyle = "" + } + if (m.className) spanStyle += " " + m.className + if (m.css) css = (css ? css + ";" : "") + m.css + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle + if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to) + // support for the old title property + // https://github.com/codemirror/CodeMirror/pull/5673 + if (m.title) (attributes || (attributes = {})).title = m.title + if (m.attributes) { + for (let attr in m.attributes) + (attributes || (attributes = {}))[attr] = m.attributes[attr] + } + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + collapsed = sp + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from + } + } + if (endStyles) for (let j = 0; j < endStyles.length; j += 2) + if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j] + + if (!collapsed || collapsed.from == pos) for (let j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]) + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null) + if (collapsed.to == null) return + if (collapsed.to == pos) collapsed = false + } + } + if (pos >= len) break + + let upto = Math.min(len, nextChange) + while (true) { + if (text) { + let end = pos + text.length + if (!collapsed) { + let tokenText = end > upto ? text.slice(0, upto - pos) : text + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", css, attributes) + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} + pos = end + spanStartStyle = "" + } + text = allText.slice(at, at = styles[i++]) + style = interpretTokenStyle(styles[i++], builder.cm.options) + } + } +} + + +// These objects are used to represent the visible (currently drawn) +// part of the document. A LineView may correspond to multiple +// logical lines, if those are connected by collapsed ranges. +export function LineView(doc, line, lineN) { + // The starting line + this.line = line + // Continuing lines, if any + this.rest = visualLineContinued(line) + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1 + this.node = this.text = null + this.hidden = lineIsHidden(doc, line) +} + +// Create a range of LineView objects for the given lines. +export function buildViewArray(cm, from, to) { + let array = [], nextPos + for (let pos = from; pos < to; pos = nextPos) { + let view = new LineView(cm.doc, getLine(cm.doc, pos), pos) + nextPos = pos + view.size + array.push(view) + } + return array +} diff --git a/docs/js/node_modules/codemirror/src/line/pos.js b/docs/js/node_modules/codemirror/src/line/pos.js new file mode 100644 index 000000000..2a498f8f3 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/line/pos.js @@ -0,0 +1,40 @@ +import { getLine } from "./utils_line.js" + +// A Pos instance represents a position within the text. +export function Pos(line, ch, sticky = null) { + if (!(this instanceof Pos)) return new Pos(line, ch, sticky) + this.line = line + this.ch = ch + this.sticky = sticky +} + +// Compare two positions, return 0 if they are the same, a negative +// number when a is less, and a positive number otherwise. +export function cmp(a, b) { return a.line - b.line || a.ch - b.ch } + +export function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } + +export function copyPos(x) {return Pos(x.line, x.ch)} +export function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } +export function minPos(a, b) { return cmp(a, b) < 0 ? a : b } + +// Most of the external API clips given positions to make sure they +// actually exist within the document. +export function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} +export function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0) + let last = doc.first + doc.size - 1 + if (pos.line > last) return Pos(last, getLine(doc, last).text.length) + return clipToLen(pos, getLine(doc, pos.line).text.length) +} +function clipToLen(pos, linelen) { + let ch = pos.ch + if (ch == null || ch > linelen) return Pos(pos.line, linelen) + else if (ch < 0) return Pos(pos.line, 0) + else return pos +} +export function clipPosArray(doc, array) { + let out = [] + for (let i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]) + return out +} diff --git a/docs/js/node_modules/codemirror/src/line/saw_special_spans.js b/docs/js/node_modules/codemirror/src/line/saw_special_spans.js new file mode 100644 index 000000000..d315e7ba9 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/line/saw_special_spans.js @@ -0,0 +1,10 @@ +// Optimize some code when these features are not used. +export let sawReadOnlySpans = false, sawCollapsedSpans = false + +export function seeReadOnlySpans() { + sawReadOnlySpans = true +} + +export function seeCollapsedSpans() { + sawCollapsedSpans = true +} diff --git a/docs/js/node_modules/codemirror/src/line/spans.js b/docs/js/node_modules/codemirror/src/line/spans.js new file mode 100644 index 000000000..d81dec4b8 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/line/spans.js @@ -0,0 +1,382 @@ +import { indexOf, lst } from "../util/misc.js" + +import { cmp } from "./pos.js" +import { sawCollapsedSpans } from "./saw_special_spans.js" +import { getLine, isLine, lineNo } from "./utils_line.js" + +// TEXTMARKER SPANS + +export function MarkedSpan(marker, from, to) { + this.marker = marker + this.from = from; this.to = to +} + +// Search an array of spans for a span matching the given marker. +export function getMarkedSpanFor(spans, marker) { + if (spans) for (let i = 0; i < spans.length; ++i) { + let span = spans[i] + if (span.marker == marker) return span + } +} +// Remove a span from an array, returning undefined if no spans are +// left (we don't store arrays for lines without spans). +export function removeMarkedSpan(spans, span) { + let r + for (let i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]) + return r +} +// Add a span to a line. +export function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span] + span.marker.attachLine(line) +} + +// Used for the algorithm that adjusts markers for a change in the +// document. These functions cut an array of spans at a given +// character position, returning an array of remaining chunks (or +// undefined if nothing remains). +function markedSpansBefore(old, startCh, isInsert) { + let nw + if (old) for (let i = 0; i < old.length; ++i) { + let span = old[i], marker = span.marker + let startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh) + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + let endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)) + } + } + return nw +} +function markedSpansAfter(old, endCh, isInsert) { + let nw + if (old) for (let i = 0; i < old.length; ++i) { + let span = old[i], marker = span.marker + let endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh) + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + let startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)) + } + } + return nw +} + +// Given a change object, compute the new set of marker spans that +// cover the line in which the change took place. Removes spans +// entirely within the change, reconnects spans belonging to the +// same marker that appear on both sides of the change, and cuts off +// spans partially within the change. Returns an array of span +// arrays with one element for each line in (after) the change. +export function stretchSpansOverChange(doc, change) { + if (change.full) return null + let oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans + let oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans + if (!oldFirst && !oldLast) return null + + let startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0 + // Get the spans that 'stick out' on both sides + let first = markedSpansBefore(oldFirst, startCh, isInsert) + let last = markedSpansAfter(oldLast, endCh, isInsert) + + // Next, merge those two ends + let sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0) + if (first) { + // Fix up .to properties of first + for (let i = 0; i < first.length; ++i) { + let span = first[i] + if (span.to == null) { + let found = getMarkedSpanFor(last, span.marker) + if (!found) span.to = startCh + else if (sameLine) span.to = found.to == null ? null : found.to + offset + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (let i = 0; i < last.length; ++i) { + let span = last[i] + if (span.to != null) span.to += offset + if (span.from == null) { + let found = getMarkedSpanFor(first, span.marker) + if (!found) { + span.from = offset + if (sameLine) (first || (first = [])).push(span) + } + } else { + span.from += offset + if (sameLine) (first || (first = [])).push(span) + } + } + } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first) + if (last && last != first) last = clearEmptySpans(last) + + let newMarkers = [first] + if (!sameLine) { + // Fill gap with whole-line-spans + let gap = change.text.length - 2, gapMarkers + if (gap > 0 && first) + for (let i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)) + for (let i = 0; i < gap; ++i) + newMarkers.push(gapMarkers) + newMarkers.push(last) + } + return newMarkers +} + +// Remove spans that are empty and don't have a clearWhenEmpty +// option of false. +function clearEmptySpans(spans) { + for (let i = 0; i < spans.length; ++i) { + let span = spans[i] + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1) + } + if (!spans.length) return null + return spans +} + +// Used to 'clip' out readOnly ranges when making a change. +export function removeReadOnlyRanges(doc, from, to) { + let markers = null + doc.iter(from.line, to.line + 1, line => { + if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) { + let mark = line.markedSpans[i].marker + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark) + } + }) + if (!markers) return null + let parts = [{from: from, to: to}] + for (let i = 0; i < markers.length; ++i) { + let mk = markers[i], m = mk.find(0) + for (let j = 0; j < parts.length; ++j) { + let p = parts[j] + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue + let newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to) + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + newParts.push({from: p.from, to: m.from}) + if (dto > 0 || !mk.inclusiveRight && !dto) + newParts.push({from: m.to, to: p.to}) + parts.splice.apply(parts, newParts) + j += newParts.length - 3 + } + } + return parts +} + +// Connect or disconnect spans from a line. +export function detachMarkedSpans(line) { + let spans = line.markedSpans + if (!spans) return + for (let i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line) + line.markedSpans = null +} +export function attachMarkedSpans(line, spans) { + if (!spans) return + for (let i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line) + line.markedSpans = spans +} + +// Helpers used when computing which overlapping collapsed span +// counts as the larger one. +function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } +function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } + +// Returns a number indicating which of two overlapping collapsed +// spans is larger (and thus includes the other). Falls back to +// comparing ids when the spans cover exactly the same range. +export function compareCollapsedMarkers(a, b) { + let lenDiff = a.lines.length - b.lines.length + if (lenDiff != 0) return lenDiff + let aPos = a.find(), bPos = b.find() + let fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b) + if (fromCmp) return -fromCmp + let toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b) + if (toCmp) return toCmp + return b.id - a.id +} + +// Find out whether a line ends or starts in a collapsed span. If +// so, return the marker for that span. +function collapsedSpanAtSide(line, start) { + let sps = sawCollapsedSpans && line.markedSpans, found + if (sps) for (let sp, i = 0; i < sps.length; ++i) { + sp = sps[i] + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + found = sp.marker + } + return found +} +export function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } +export function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } + +export function collapsedSpanAround(line, ch) { + let sps = sawCollapsedSpans && line.markedSpans, found + if (sps) for (let i = 0; i < sps.length; ++i) { + let sp = sps[i] + if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) found = sp.marker + } + return found +} + +// Test whether there exists a collapsed span that partially +// overlaps (covers the start or end, but not both) of a new span. +// Such overlap is not allowed. +export function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + let line = getLine(doc, lineNo) + let sps = sawCollapsedSpans && line.markedSpans + if (sps) for (let i = 0; i < sps.length; ++i) { + let sp = sps[i] + if (!sp.marker.collapsed) continue + let found = sp.marker.find(0) + let fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker) + let toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker) + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue + if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || + fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) + return true + } +} + +// A visual line is a line as drawn on the screen. Folding, for +// example, can cause multiple logical lines to appear on the same +// visual line. This finds the start of the visual line that the +// given line is part of (usually that is the line itself). +export function visualLine(line) { + let merged + while (merged = collapsedSpanAtStart(line)) + line = merged.find(-1, true).line + return line +} + +export function visualLineEnd(line) { + let merged + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line + return line +} + +// Returns an array of logical lines that continue the visual line +// started by the argument, or undefined if there are no such lines. +export function visualLineContinued(line) { + let merged, lines + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line + ;(lines || (lines = [])).push(line) + } + return lines +} + +// Get the line number of the start of the visual line that the +// given line number is part of. +export function visualLineNo(doc, lineN) { + let line = getLine(doc, lineN), vis = visualLine(line) + if (line == vis) return lineN + return lineNo(vis) +} + +// Get the line number of the start of the next visual line after +// the given line. +export function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) return lineN + let line = getLine(doc, lineN), merged + if (!lineIsHidden(doc, line)) return lineN + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line + return lineNo(line) + 1 +} + +// Compute whether a line is hidden. Lines count as hidden when they +// are part of a visual line that starts with another line, or when +// they are entirely covered by collapsed, non-widget span. +export function lineIsHidden(doc, line) { + let sps = sawCollapsedSpans && line.markedSpans + if (sps) for (let sp, i = 0; i < sps.length; ++i) { + sp = sps[i] + if (!sp.marker.collapsed) continue + if (sp.from == null) return true + if (sp.marker.widgetNode) continue + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true + } +} +function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + let end = span.marker.find(1, true) + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true + for (let sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i] + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true + } +} + +// Find the height above the given line. +export function heightAtLine(lineObj) { + lineObj = visualLine(lineObj) + + let h = 0, chunk = lineObj.parent + for (let i = 0; i < chunk.lines.length; ++i) { + let line = chunk.lines[i] + if (line == lineObj) break + else h += line.height + } + for (let p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (let i = 0; i < p.children.length; ++i) { + let cur = p.children[i] + if (cur == chunk) break + else h += cur.height + } + } + return h +} + +// Compute the character length of a line, taking into account +// collapsed ranges (see markText) that might hide parts, and join +// other lines onto it. +export function lineLength(line) { + if (line.height == 0) return 0 + let len = line.text.length, merged, cur = line + while (merged = collapsedSpanAtStart(cur)) { + let found = merged.find(0, true) + cur = found.from.line + len += found.from.ch - found.to.ch + } + cur = line + while (merged = collapsedSpanAtEnd(cur)) { + let found = merged.find(0, true) + len -= cur.text.length - found.from.ch + cur = found.to.line + len += cur.text.length - found.to.ch + } + return len +} + +// Find the longest line in the document. +export function findMaxLine(cm) { + let d = cm.display, doc = cm.doc + d.maxLine = getLine(doc, doc.first) + d.maxLineLength = lineLength(d.maxLine) + d.maxLineChanged = true + doc.iter(line => { + let len = lineLength(line) + if (len > d.maxLineLength) { + d.maxLineLength = len + d.maxLine = line + } + }) +} diff --git a/docs/js/node_modules/codemirror/src/line/utils_line.js b/docs/js/node_modules/codemirror/src/line/utils_line.js new file mode 100644 index 000000000..c88629435 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/line/utils_line.js @@ -0,0 +1,85 @@ +import { indexOf } from "../util/misc.js" + +// Find the line object corresponding to the given line number. +export function getLine(doc, n) { + n -= doc.first + if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document.") + let chunk = doc + while (!chunk.lines) { + for (let i = 0;; ++i) { + let child = chunk.children[i], sz = child.chunkSize() + if (n < sz) { chunk = child; break } + n -= sz + } + } + return chunk.lines[n] +} + +// Get the part of a document between two positions, as an array of +// strings. +export function getBetween(doc, start, end) { + let out = [], n = start.line + doc.iter(start.line, end.line + 1, line => { + let text = line.text + if (n == end.line) text = text.slice(0, end.ch) + if (n == start.line) text = text.slice(start.ch) + out.push(text) + ++n + }) + return out +} +// Get the lines between from and to, as array of strings. +export function getLines(doc, from, to) { + let out = [] + doc.iter(from, to, line => { out.push(line.text) }) // iter aborts when callback returns truthy value + return out +} + +// Update the height of a line, propagating the height change +// upwards to parent nodes. +export function updateLineHeight(line, height) { + let diff = height - line.height + if (diff) for (let n = line; n; n = n.parent) n.height += diff +} + +// Given a line object, find its line number by walking up through +// its parent links. +export function lineNo(line) { + if (line.parent == null) return null + let cur = line.parent, no = indexOf(cur.lines, line) + for (let chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (let i = 0;; ++i) { + if (chunk.children[i] == cur) break + no += chunk.children[i].chunkSize() + } + } + return no + cur.first +} + +// Find the line at the given vertical position, using the height +// information in the document tree. +export function lineAtHeight(chunk, h) { + let n = chunk.first + outer: do { + for (let i = 0; i < chunk.children.length; ++i) { + let child = chunk.children[i], ch = child.height + if (h < ch) { chunk = child; continue outer } + h -= ch + n += child.chunkSize() + } + return n + } while (!chunk.lines) + let i = 0 + for (; i < chunk.lines.length; ++i) { + let line = chunk.lines[i], lh = line.height + if (h < lh) break + h -= lh + } + return n + i +} + +export function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} + +export function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)) +} diff --git a/docs/js/node_modules/codemirror/src/measurement/position_measurement.js b/docs/js/node_modules/codemirror/src/measurement/position_measurement.js new file mode 100644 index 000000000..18ec11df8 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/measurement/position_measurement.js @@ -0,0 +1,700 @@ +import { buildLineContent, LineView } from "../line/line_data.js" +import { clipPos, Pos } from "../line/pos.js" +import { collapsedSpanAround, heightAtLine, lineIsHidden, visualLine } from "../line/spans.js" +import { getLine, lineAtHeight, lineNo, updateLineHeight } from "../line/utils_line.js" +import { bidiOther, getBidiPartAt, getOrder } from "../util/bidi.js" +import { chrome, android, ie, ie_version } from "../util/browser.js" +import { elt, removeChildren, range, removeChildrenAndAdd } from "../util/dom.js" +import { e_target } from "../util/event.js" +import { hasBadZoomedRects } from "../util/feature_detection.js" +import { countColumn, findFirst, isExtendingChar, scrollerGap, skipExtendingChars } from "../util/misc.js" +import { updateLineForChanges } from "../display/update_line.js" + +import { widgetHeight } from "./widgets.js" + +// POSITION MEASUREMENT + +export function paddingTop(display) {return display.lineSpace.offsetTop} +export function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} +export function paddingH(display) { + if (display.cachedPaddingH) return display.cachedPaddingH + let e = removeChildrenAndAdd(display.measure, elt("pre", "x", "CodeMirror-line-like")) + let style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle + let data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)} + if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data + return data +} + +export function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } +export function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth +} +export function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight +} + +// Ensure the lineView.wrapping.heights array is populated. This is +// an array of bottom offsets for the lines that make up a drawn +// line. When lineWrapping is on, there might be more than one +// height. +function ensureLineHeights(cm, lineView, rect) { + let wrapping = cm.options.lineWrapping + let curWidth = wrapping && displayWidth(cm) + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + let heights = lineView.measure.heights = [] + if (wrapping) { + lineView.measure.width = curWidth + let rects = lineView.text.firstChild.getClientRects() + for (let i = 0; i < rects.length - 1; i++) { + let cur = rects[i], next = rects[i + 1] + if (Math.abs(cur.bottom - next.bottom) > 2) + heights.push((cur.bottom + next.top) / 2 - rect.top) + } + } + heights.push(rect.bottom - rect.top) + } +} + +// Find a line map (mapping character offsets to text nodes) and a +// measurement cache for the given line number. (A line view might +// contain multiple lines when collapsed ranges are present.) +export function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + return {map: lineView.measure.map, cache: lineView.measure.cache} + for (let i = 0; i < lineView.rest.length; i++) + if (lineView.rest[i] == line) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} + for (let i = 0; i < lineView.rest.length; i++) + if (lineNo(lineView.rest[i]) > lineN) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true} +} + +// Render a line into the hidden node display.externalMeasured. Used +// when measurement is needed for a line that's not in the viewport. +function updateExternalMeasurement(cm, line) { + line = visualLine(line) + let lineN = lineNo(line) + let view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN) + view.lineN = lineN + let built = view.built = buildLineContent(cm, view) + view.text = built.pre + removeChildrenAndAdd(cm.display.lineMeasure, built.pre) + return view +} + +// Get a {top, bottom, left, right} box (in line-local coordinates) +// for a given character. +export function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) +} + +// Find a line view that corresponds to the given line number. +export function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + return cm.display.view[findViewIndex(cm, lineN)] + let ext = cm.display.externalMeasured + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + return ext +} + +// Measurement can be split in two steps, the set-up work that +// applies to the whole line, and the measurement of the actual +// character. Functions like coordsChar, that need to do a lot of +// measurements in a row, can thus ensure that the set-up work is +// only done once. +export function prepareMeasureForLine(cm, line) { + let lineN = lineNo(line) + let view = findViewForLine(cm, lineN) + if (view && !view.text) { + view = null + } else if (view && view.changes) { + updateLineForChanges(cm, view, lineN, getDimensions(cm)) + cm.curOp.forceUpdate = true + } + if (!view) + view = updateExternalMeasurement(cm, line) + + let info = mapFromLineView(view, line, lineN) + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + } +} + +// Given a prepared measurement object, measures the position of an +// actual character (or fetches it from the cache). +export function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) ch = -1 + let key = ch + (bias || ""), found + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key] + } else { + if (!prepared.rect) + prepared.rect = prepared.view.text.getBoundingClientRect() + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect) + prepared.hasHeights = true + } + found = measureCharInner(cm, prepared, ch, bias) + if (!found.bogus) prepared.cache[key] = found + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom} +} + +let nullRect = {left: 0, right: 0, top: 0, bottom: 0} + +export function nodeAndOffsetInLineMap(map, ch, bias) { + let node, start, end, collapse, mStart, mEnd + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (let i = 0; i < map.length; i += 3) { + mStart = map[i] + mEnd = map[i + 1] + if (ch < mStart) { + start = 0; end = 1 + collapse = "left" + } else if (ch < mEnd) { + start = ch - mStart + end = start + 1 + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart + start = end - 1 + if (ch >= mEnd) collapse = "right" + } + if (start != null) { + node = map[i + 2] + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + collapse = bias + if (bias == "left" && start == 0) + while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2] + collapse = "left" + } + if (bias == "right" && start == mEnd - mStart) + while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2] + collapse = "right" + } + break + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} +} + +function getUsefulRect(rects, bias) { + let rect = nullRect + if (bias == "left") for (let i = 0; i < rects.length; i++) { + if ((rect = rects[i]).left != rect.right) break + } else for (let i = rects.length - 1; i >= 0; i--) { + if ((rect = rects[i]).left != rect.right) break + } + return rect +} + +function measureCharInner(cm, prepared, ch, bias) { + let place = nodeAndOffsetInLineMap(prepared.map, ch, bias) + let node = place.node, start = place.start, end = place.end, collapse = place.collapse + + let rect + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (let i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) + rect = node.parentNode.getBoundingClientRect() + else + rect = getUsefulRect(range(node, start, end).getClientRects(), bias) + if (rect.left || rect.right || start == 0) break + end = start + start = start - 1 + collapse = "right" + } + if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect) + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) collapse = bias = "right" + let rects + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + rect = rects[bias == "right" ? rects.length - 1 : 0] + else + rect = node.getBoundingClientRect() + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + let rSpan = node.parentNode.getClientRects()[0] + if (rSpan) + rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom} + else + rect = nullRect + } + + let rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top + let mid = (rtop + rbot) / 2 + let heights = prepared.view.measure.heights + let i = 0 + for (; i < heights.length - 1; i++) + if (mid < heights[i]) break + let top = i ? heights[i - 1] : 0, bot = heights[i] + let result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot} + if (!rect.left && !rect.right) result.bogus = true + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot } + + return result +} + +// Work around problem with bounding client rects on ranges being +// returned incorrectly when zoomed on IE10 and below. +function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + return rect + let scaleX = screen.logicalXDPI / screen.deviceXDPI + let scaleY = screen.logicalYDPI / screen.deviceYDPI + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY} +} + +export function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {} + lineView.measure.heights = null + if (lineView.rest) for (let i = 0; i < lineView.rest.length; i++) + lineView.measure.caches[i] = {} + } +} + +export function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null + removeChildren(cm.display.lineMeasure) + for (let i = 0; i < cm.display.view.length; i++) + clearLineMeasurementCacheFor(cm.display.view[i]) +} + +export function clearCaches(cm) { + clearLineMeasurementCache(cm) + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true + cm.display.lineNumChars = null +} + +function pageScrollX() { + // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 + // which causes page_Offset and bounding client rects to use + // different reference viewports and invalidate our calculations. + if (chrome && android) return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) + return window.pageXOffset || (document.documentElement || document.body).scrollLeft +} +function pageScrollY() { + if (chrome && android) return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) + return window.pageYOffset || (document.documentElement || document.body).scrollTop +} + +function widgetTopHeight(lineObj) { + let height = 0 + if (lineObj.widgets) for (let i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) + height += widgetHeight(lineObj.widgets[i]) + return height +} + +// Converts a {top, bottom, left, right} box from line-local +// coordinates into another coordinate system. Context may be one of +// "line", "div" (display.lineDiv), "local"./null (editor), "window", +// or "page". +export function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { + if (!includeWidgets) { + let height = widgetTopHeight(lineObj) + rect.top += height; rect.bottom += height + } + if (context == "line") return rect + if (!context) context = "local" + let yOff = heightAtLine(lineObj) + if (context == "local") yOff += paddingTop(cm.display) + else yOff -= cm.display.viewOffset + if (context == "page" || context == "window") { + let lOff = cm.display.lineSpace.getBoundingClientRect() + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()) + let xOff = lOff.left + (context == "window" ? 0 : pageScrollX()) + rect.left += xOff; rect.right += xOff + } + rect.top += yOff; rect.bottom += yOff + return rect +} + +// Coverts a box from "div" coords to another coordinate system. +// Context may be "window", "page", "div", or "local"./null. +export function fromCoordSystem(cm, coords, context) { + if (context == "div") return coords + let left = coords.left, top = coords.top + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX() + top -= pageScrollY() + } else if (context == "local" || !context) { + let localBox = cm.display.sizer.getBoundingClientRect() + left += localBox.left + top += localBox.top + } + + let lineSpaceBox = cm.display.lineSpace.getBoundingClientRect() + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} +} + +export function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) lineObj = getLine(cm.doc, pos.line) + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) +} + +// Returns a box for a given cursor position, which may have an +// 'other' property containing the position of the secondary cursor +// on a bidi boundary. +// A cursor Pos(line, char, "before") is on the same visual line as `char - 1` +// and after `char - 1` in writing order of `char - 1` +// A cursor Pos(line, char, "after") is on the same visual line as `char` +// and before `char` in writing order of `char` +// Examples (upper-case letters are RTL, lower-case are LTR): +// Pos(0, 1, ...) +// before after +// ab a|b a|b +// aB a|B aB| +// Ab |Ab A|b +// AB B|A B|A +// Every position after the last character on a line is considered to stick +// to the last character on the line. +export function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line) + if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj) + function get(ch, right) { + let m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight) + if (right) m.left = m.right; else m.right = m.left + return intoCoordSystem(cm, lineObj, m, context) + } + let order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky + if (ch >= lineObj.text.length) { + ch = lineObj.text.length + sticky = "before" + } else if (ch <= 0) { + ch = 0 + sticky = "after" + } + if (!order) return get(sticky == "before" ? ch - 1 : ch, sticky == "before") + + function getBidi(ch, partPos, invert) { + let part = order[partPos], right = part.level == 1 + return get(invert ? ch - 1 : ch, right != invert) + } + let partPos = getBidiPartAt(order, ch, sticky) + let other = bidiOther + let val = getBidi(ch, partPos, sticky == "before") + if (other != null) val.other = getBidi(ch, other, sticky != "before") + return val +} + +// Used to cheaply estimate the coordinates for a position. Used for +// intermediate scroll updates. +export function estimateCoords(cm, pos) { + let left = 0 + pos = clipPos(cm.doc, pos) + if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch + let lineObj = getLine(cm.doc, pos.line) + let top = heightAtLine(lineObj) + paddingTop(cm.display) + return {left: left, right: left, top: top, bottom: top + lineObj.height} +} + +// Positions returned by coordsChar contain some extra information. +// xRel is the relative x position of the input coordinates compared +// to the found position (so xRel > 0 means the coordinates are to +// the right of the character position, for example). When outside +// is true, that means the coordinates lie outside the line's +// vertical range. +function PosWithInfo(line, ch, sticky, outside, xRel) { + let pos = Pos(line, ch, sticky) + pos.xRel = xRel + if (outside) pos.outside = outside + return pos +} + +// Compute the character position closest to the given coordinates. +// Input must be lineSpace-local ("div" coordinate system). +export function coordsChar(cm, x, y) { + let doc = cm.doc + y += cm.display.viewOffset + if (y < 0) return PosWithInfo(doc.first, 0, null, -1, -1) + let lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1 + if (lineN > last) + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, 1, 1) + if (x < 0) x = 0 + + let lineObj = getLine(doc, lineN) + for (;;) { + let found = coordsCharInner(cm, lineObj, lineN, x, y) + let collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0)) + if (!collapsed) return found + let rangeEnd = collapsed.find(1) + if (rangeEnd.line == lineN) return rangeEnd + lineObj = getLine(doc, lineN = rangeEnd.line) + } +} + +function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { + y -= widgetTopHeight(lineObj) + let end = lineObj.text.length + let begin = findFirst(ch => measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y, end, 0) + end = findFirst(ch => measureCharPrepared(cm, preparedMeasure, ch).top > y, begin, end) + return {begin, end} +} + +export function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { + if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj) + let targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top + return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) +} + +// Returns true if the given side of a box is after the given +// coordinates, in top-to-bottom, left-to-right order. +function boxIsAfter(box, x, y, left) { + return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x +} + +function coordsCharInner(cm, lineObj, lineNo, x, y) { + // Move y into line-local coordinate space + y -= heightAtLine(lineObj) + let preparedMeasure = prepareMeasureForLine(cm, lineObj) + // When directly calling `measureCharPrepared`, we have to adjust + // for the widgets at this line. + let widgetHeight = widgetTopHeight(lineObj) + let begin = 0, end = lineObj.text.length, ltr = true + + let order = getOrder(lineObj, cm.doc.direction) + // If the line isn't plain left-to-right text, first figure out + // which bidi section the coordinates fall into. + if (order) { + let part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) + (cm, lineObj, lineNo, preparedMeasure, order, x, y) + ltr = part.level != 1 + // The awkward -1 offsets are needed because findFirst (called + // on these below) will treat its first bound as inclusive, + // second as exclusive, but we want to actually address the + // characters in the part's range + begin = ltr ? part.from : part.to - 1 + end = ltr ? part.to : part.from - 1 + } + + // A binary search to find the first character whose bounding box + // starts after the coordinates. If we run across any whose box wrap + // the coordinates, store that. + let chAround = null, boxAround = null + let ch = findFirst(ch => { + let box = measureCharPrepared(cm, preparedMeasure, ch) + box.top += widgetHeight; box.bottom += widgetHeight + if (!boxIsAfter(box, x, y, false)) return false + if (box.top <= y && box.left <= x) { + chAround = ch + boxAround = box + } + return true + }, begin, end) + + let baseX, sticky, outside = false + // If a box around the coordinates was found, use that + if (boxAround) { + // Distinguish coordinates nearer to the left or right side of the box + let atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr + ch = chAround + (atStart ? 0 : 1) + sticky = atStart ? "after" : "before" + baseX = atLeft ? boxAround.left : boxAround.right + } else { + // (Adjust for extended bound, if necessary.) + if (!ltr && (ch == end || ch == begin)) ch++ + // To determine which side to associate with, get the box to the + // left of the character and compare it's vertical position to the + // coordinates + sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : + (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ? + "after" : "before" + // Now get accurate coordinates for this place, in order to get a + // base X position + let coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure) + baseX = coords.left + outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0 + } + + ch = skipExtendingChars(lineObj.text, ch, 1) + return PosWithInfo(lineNo, ch, sticky, outside, x - baseX) +} + +function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { + // Bidi parts are sorted left-to-right, and in a non-line-wrapping + // situation, we can take this ordering to correspond to the visual + // ordering. This finds the first part whose end is after the given + // coordinates. + let index = findFirst(i => { + let part = order[i], ltr = part.level != 1 + return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"), + "line", lineObj, preparedMeasure), x, y, true) + }, 0, order.length - 1) + let part = order[index] + // If this isn't the first part, the part's start is also after + // the coordinates, and the coordinates aren't on the same line as + // that start, move one part back. + if (index > 0) { + let ltr = part.level != 1 + let start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"), + "line", lineObj, preparedMeasure) + if (boxIsAfter(start, x, y, true) && start.top > y) + part = order[index - 1] + } + return part +} + +function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { + // In a wrapped line, rtl text on wrapping boundaries can do things + // that don't correspond to the ordering in our `order` array at + // all, so a binary search doesn't work, and we want to return a + // part that only spans one line so that the binary search in + // coordsCharInner is safe. As such, we first find the extent of the + // wrapped line, and then do a flat search in which we discard any + // spans that aren't on the line. + let {begin, end} = wrappedLineExtent(cm, lineObj, preparedMeasure, y) + if (/\s/.test(lineObj.text.charAt(end - 1))) end-- + let part = null, closestDist = null + for (let i = 0; i < order.length; i++) { + let p = order[i] + if (p.from >= end || p.to <= begin) continue + let ltr = p.level != 1 + let endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right + // Weigh against spans ending before this, so that they are only + // picked if nothing ends after + let dist = endX < x ? x - endX + 1e9 : endX - x + if (!part || closestDist > dist) { + part = p + closestDist = dist + } + } + if (!part) part = order[order.length - 1] + // Clip the part to the wrapped line. + if (part.from < begin) part = {from: begin, to: part.to, level: part.level} + if (part.to > end) part = {from: part.from, to: end, level: part.level} + return part +} + +let measureText +// Compute the default text height. +export function textHeight(display) { + if (display.cachedTextHeight != null) return display.cachedTextHeight + if (measureText == null) { + measureText = elt("pre", null, "CodeMirror-line-like") + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (let i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")) + measureText.appendChild(elt("br")) + } + measureText.appendChild(document.createTextNode("x")) + } + removeChildrenAndAdd(display.measure, measureText) + let height = measureText.offsetHeight / 50 + if (height > 3) display.cachedTextHeight = height + removeChildren(display.measure) + return height || 1 +} + +// Compute the default character width. +export function charWidth(display) { + if (display.cachedCharWidth != null) return display.cachedCharWidth + let anchor = elt("span", "xxxxxxxxxx") + let pre = elt("pre", [anchor], "CodeMirror-line-like") + removeChildrenAndAdd(display.measure, pre) + let rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10 + if (width > 2) display.cachedCharWidth = width + return width || 10 +} + +// Do a bulk-read of the DOM positions and sizes needed to draw the +// view, so that we don't interleave reading and writing to the DOM. +export function getDimensions(cm) { + let d = cm.display, left = {}, width = {} + let gutterLeft = d.gutters.clientLeft + for (let n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + let id = cm.display.gutterSpecs[i].className + left[id] = n.offsetLeft + n.clientLeft + gutterLeft + width[id] = n.clientWidth + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth} +} + +// Computes display.scroller.scrollLeft + display.gutters.offsetWidth, +// but using getBoundingClientRect to get a sub-pixel-accurate +// result. +export function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left +} + +// Returns a function that estimates the height of a line, to use as +// first approximation until the line becomes visible (and is thus +// properly measurable). +export function estimateHeight(cm) { + let th = textHeight(cm.display), wrapping = cm.options.lineWrapping + let perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3) + return line => { + if (lineIsHidden(cm.doc, line)) return 0 + + let widgetsHeight = 0 + if (line.widgets) for (let i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) widgetsHeight += line.widgets[i].height + } + + if (wrapping) + return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th + else + return widgetsHeight + th + } +} + +export function estimateLineHeights(cm) { + let doc = cm.doc, est = estimateHeight(cm) + doc.iter(line => { + let estHeight = est(line) + if (estHeight != line.height) updateLineHeight(line, estHeight) + }) +} + +// Given a mouse event, find the corresponding position. If liberal +// is false, it checks whether a gutter or scrollbar was clicked, +// and returns null if it was. forRect is used by rectangular +// selections, and tries to estimate a character position even for +// coordinates beyond the right of the text. +export function posFromMouse(cm, e, liberal, forRect) { + let display = cm.display + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null + + let x, y, space = display.lineSpace.getBoundingClientRect() + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top } + catch (e) { return null } + let coords = coordsChar(cm, x, y), line + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + let colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)) + } + return coords +} + +// Find the view element corresponding to a given line. Return null +// when the line isn't visible. +export function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) return null + n -= cm.display.viewFrom + if (n < 0) return null + let view = cm.display.view + for (let i = 0; i < view.length; i++) { + n -= view[i].size + if (n < 0) return i + } +} diff --git a/docs/js/node_modules/codemirror/src/measurement/widgets.js b/docs/js/node_modules/codemirror/src/measurement/widgets.js new file mode 100644 index 000000000..39d7553d1 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/measurement/widgets.js @@ -0,0 +1,26 @@ +import { contains, elt, removeChildrenAndAdd } from "../util/dom.js" +import { e_target } from "../util/event.js" + +export function widgetHeight(widget) { + if (widget.height != null) return widget.height + let cm = widget.doc.cm + if (!cm) return 0 + if (!contains(document.body, widget.node)) { + let parentStyle = "position: relative;" + if (widget.coverGutter) + parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;" + if (widget.noHScroll) + parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;" + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)) + } + return widget.height = widget.node.parentNode.offsetHeight +} + +// Return true when the given mouse event happened in a widget +export function eventInWidget(display, e) { + for (let n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + return true + } +} diff --git a/docs/js/node_modules/codemirror/src/model/Doc.js b/docs/js/node_modules/codemirror/src/model/Doc.js new file mode 100644 index 000000000..956f752b9 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/model/Doc.js @@ -0,0 +1,432 @@ +import CodeMirror from "../edit/CodeMirror.js" +import { docMethodOp } from "../display/operations.js" +import { Line } from "../line/line_data.js" +import { clipPos, clipPosArray, Pos } from "../line/pos.js" +import { visualLine } from "../line/spans.js" +import { getBetween, getLine, getLines, isLine, lineNo } from "../line/utils_line.js" +import { classTest } from "../util/dom.js" +import { splitLinesAuto } from "../util/feature_detection.js" +import { createObj, map, isEmpty, sel_dontScroll } from "../util/misc.js" +import { ensureCursorVisible, scrollToCoords } from "../display/scrolling.js" + +import { changeLine, makeChange, makeChangeFromHistory, replaceRange } from "./changes.js" +import { computeReplacedSel } from "./change_measurement.js" +import { BranchChunk, LeafChunk } from "./chunk.js" +import { directionChanged, linkedDocs, updateDoc } from "./document_data.js" +import { copyHistoryArray, History } from "./history.js" +import { addLineWidget } from "./line_widget.js" +import { copySharedMarkers, detachSharedMarkers, findSharedMarkers, markText } from "./mark_text.js" +import { normalizeSelection, Range, simpleSelection } from "./selection.js" +import { extendSelection, extendSelections, setSelection, setSelectionReplaceHistory, setSimpleSelection } from "./selection_updates.js" + +let nextDocId = 0 +let Doc = function(text, mode, firstLine, lineSep, direction) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep, direction) + if (firstLine == null) firstLine = 0 + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]) + this.first = firstLine + this.scrollTop = this.scrollLeft = 0 + this.cantEdit = false + this.cleanGeneration = 1 + this.modeFrontier = this.highlightFrontier = firstLine + let start = Pos(firstLine, 0) + this.sel = simpleSelection(start) + this.history = new History(null) + this.id = ++nextDocId + this.modeOption = mode + this.lineSep = lineSep + this.direction = (direction == "rtl") ? "rtl" : "ltr" + this.extend = false + + if (typeof text == "string") text = this.splitLines(text) + updateDoc(this, {from: start, to: start, text: text}) + setSelection(this, simpleSelection(start), sel_dontScroll) +} + +Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) this.iterN(from - this.first, to - from, op) + else this.iterN(this.first, this.first + this.size, from) + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + let height = 0 + for (let i = 0; i < lines.length; ++i) height += lines[i].height + this.insertInner(at - this.first, lines, height) + }, + remove: function(at, n) { this.removeInner(at - this.first, n) }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + let lines = getLines(this, this.first, this.first + this.size) + if (lineSep === false) return lines + return lines.join(lineSep || this.lineSeparator()) + }, + setValue: docMethodOp(function(code) { + let top = Pos(this.first, 0), last = this.first + this.size - 1 + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: this.splitLines(code), origin: "setValue", full: true}, true) + if (this.cm) scrollToCoords(this.cm, 0, 0) + setSelection(this, simpleSelection(top), sel_dontScroll) + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from) + to = to ? clipPos(this, to) : from + replaceRange(this, code, from, to, origin) + }, + getRange: function(from, to, lineSep) { + let lines = getBetween(this, clipPos(this, from), clipPos(this, to)) + if (lineSep === false) return lines + return lines.join(lineSep || this.lineSeparator()) + }, + + getLine: function(line) {let l = this.getLineHandle(line); return l && l.text}, + + getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line)}, + getLineNumber: function(line) {return lineNo(line)}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line) + return visualLine(line) + }, + + lineCount: function() {return this.size}, + firstLine: function() {return this.first}, + lastLine: function() {return this.first + this.size - 1}, + + clipPos: function(pos) {return clipPos(this, pos)}, + + getCursor: function(start) { + let range = this.sel.primary(), pos + if (start == null || start == "head") pos = range.head + else if (start == "anchor") pos = range.anchor + else if (start == "end" || start == "to" || start === false) pos = range.to() + else pos = range.from() + return pos + }, + listSelections: function() { return this.sel.ranges }, + somethingSelected: function() {return this.sel.somethingSelected()}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options) + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options) + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options) + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads), options) + }), + extendSelectionsBy: docMethodOp(function(f, options) { + let heads = map(this.sel.ranges, f) + extendSelections(this, clipPosArray(this, heads), options) + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) return + let out = [] + for (let i = 0; i < ranges.length; i++) + out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head)) + if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex) + setSelection(this, normalizeSelection(this.cm, out, primary), options) + }), + addSelection: docMethodOp(function(anchor, head, options) { + let ranges = this.sel.ranges.slice(0) + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))) + setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options) + }), + + getSelection: function(lineSep) { + let ranges = this.sel.ranges, lines + for (let i = 0; i < ranges.length; i++) { + let sel = getBetween(this, ranges[i].from(), ranges[i].to()) + lines = lines ? lines.concat(sel) : sel + } + if (lineSep === false) return lines + else return lines.join(lineSep || this.lineSeparator()) + }, + getSelections: function(lineSep) { + let parts = [], ranges = this.sel.ranges + for (let i = 0; i < ranges.length; i++) { + let sel = getBetween(this, ranges[i].from(), ranges[i].to()) + if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator()) + parts[i] = sel + } + return parts + }, + replaceSelection: function(code, collapse, origin) { + let dup = [] + for (let i = 0; i < this.sel.ranges.length; i++) + dup[i] = code + this.replaceSelections(dup, collapse, origin || "+input") + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + let changes = [], sel = this.sel + for (let i = 0; i < sel.ranges.length; i++) { + let range = sel.ranges[i] + changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin} + } + let newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse) + for (let i = changes.length - 1; i >= 0; i--) + makeChange(this, changes[i]) + if (newSel) setSelectionReplaceHistory(this, newSel) + else if (this.cm) ensureCursorVisible(this.cm) + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}), + + setExtending: function(val) {this.extend = val}, + getExtending: function() {return this.extend}, + + historySize: function() { + let hist = this.history, done = 0, undone = 0 + for (let i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done + for (let i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone + return {undo: done, redo: undone} + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration)}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true) + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null + return this.history.generation + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration) + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)} + }, + setHistory: function(histData) { + let hist = this.history = new History(this.history.maxGeneration) + hist.done = copyHistoryArray(histData.done.slice(0), null, true) + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true) + }, + + setGutterMarker: docMethodOp(function(line, gutterID, value) { + return changeLine(this, line, "gutter", line => { + let markers = line.gutterMarkers || (line.gutterMarkers = {}) + markers[gutterID] = value + if (!value && isEmpty(markers)) line.gutterMarkers = null + return true + }) + }), + + clearGutter: docMethodOp(function(gutterID) { + this.iter(line => { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + changeLine(this, line, "gutter", () => { + line.gutterMarkers[gutterID] = null + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null + return true + }) + } + }) + }), + + lineInfo: function(line) { + let n + if (typeof line == "number") { + if (!isLine(this, line)) return null + n = line + line = getLine(this, line) + if (!line) return null + } else { + n = lineNo(line) + if (n == null) return null + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets} + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", line => { + let prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass" + if (!line[prop]) line[prop] = cls + else if (classTest(cls).test(line[prop])) return false + else line[prop] += " " + cls + return true + }) + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", line => { + let prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass" + let cur = line[prop] + if (!cur) return false + else if (cls == null) line[prop] = null + else { + let found = cur.match(classTest(cls)) + if (!found) return false + let end = found.index + found[0].length + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null + } + return true + }) + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options) + }), + removeLineWidget: function(widget) { widget.clear() }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") + }, + setBookmark: function(pos, options) { + let realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents} + pos = clipPos(this, pos) + return markText(this, pos, pos, realOpts, "bookmark") + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos) + let markers = [], spans = getLine(this, pos.line).markedSpans + if (spans) for (let i = 0; i < spans.length; ++i) { + let span = spans[i] + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker.parent || span.marker) + } + return markers + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to) + let found = [], lineNo = from.line + this.iter(from.line, to.line + 1, line => { + let spans = line.markedSpans + if (spans) for (let i = 0; i < spans.length; i++) { + let span = spans[i] + if (!(span.to != null && lineNo == from.line && from.ch >= span.to || + span.from == null && lineNo != from.line || + span.from != null && lineNo == to.line && span.from >= to.ch) && + (!filter || filter(span.marker))) + found.push(span.marker.parent || span.marker) + } + ++lineNo + }) + return found + }, + getAllMarks: function() { + let markers = [] + this.iter(line => { + let sps = line.markedSpans + if (sps) for (let i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker) + }) + return markers + }, + + posFromIndex: function(off) { + let ch, lineNo = this.first, sepSize = this.lineSeparator().length + this.iter(line => { + let sz = line.text.length + sepSize + if (sz > off) { ch = off; return true } + off -= sz + ++lineNo + }) + return clipPos(this, Pos(lineNo, ch)) + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords) + let index = coords.ch + if (coords.line < this.first || coords.ch < 0) return 0 + let sepSize = this.lineSeparator().length + this.iter(this.first, coords.line, line => { // iter aborts when callback returns a truthy value + index += line.text.length + sepSize + }) + return index + }, + + copy: function(copyHistory) { + let doc = new Doc(getLines(this, this.first, this.first + this.size), + this.modeOption, this.first, this.lineSep, this.direction) + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft + doc.sel = this.sel + doc.extend = false + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth + doc.setHistory(this.getHistory()) + } + return doc + }, + + linkedDoc: function(options) { + if (!options) options = {} + let from = this.first, to = this.first + this.size + if (options.from != null && options.from > from) from = options.from + if (options.to != null && options.to < to) to = options.to + let copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction) + if (options.sharedHist) copy.history = this.history + ;(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}) + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}] + copySharedMarkers(copy, findSharedMarkers(this)) + return copy + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc + if (this.linked) for (let i = 0; i < this.linked.length; ++i) { + let link = this.linked[i] + if (link.doc != other) continue + this.linked.splice(i, 1) + other.unlinkDoc(this) + detachSharedMarkers(findSharedMarkers(this)) + break + } + // If the histories were shared, split them again + if (other.history == this.history) { + let splitIds = [other.id] + linkedDocs(other, doc => splitIds.push(doc.id), true) + other.history = new History(null) + other.history.done = copyHistoryArray(this.history.done, splitIds) + other.history.undone = copyHistoryArray(this.history.undone, splitIds) + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f)}, + + getMode: function() {return this.mode}, + getEditor: function() {return this.cm}, + + splitLines: function(str) { + if (this.lineSep) return str.split(this.lineSep) + return splitLinesAuto(str) + }, + lineSeparator: function() { return this.lineSep || "\n" }, + + setDirection: docMethodOp(function (dir) { + if (dir != "rtl") dir = "ltr" + if (dir == this.direction) return + this.direction = dir + this.iter(line => line.order = null) + if (this.cm) directionChanged(this.cm) + }) +}) + +// Public alias. +Doc.prototype.eachLine = Doc.prototype.iter + +export default Doc diff --git a/docs/js/node_modules/codemirror/src/model/change_measurement.js b/docs/js/node_modules/codemirror/src/model/change_measurement.js new file mode 100644 index 000000000..010e7f81e --- /dev/null +++ b/docs/js/node_modules/codemirror/src/model/change_measurement.js @@ -0,0 +1,61 @@ +import { cmp, Pos } from "../line/pos.js" +import { lst } from "../util/misc.js" + +import { normalizeSelection, Range, Selection } from "./selection.js" + +// Compute the position of the end of a change (its 'to' property +// refers to the pre-change end). +export function changeEnd(change) { + if (!change.text) return change.to + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) +} + +// Adjust a position to refer to the post-change position of the +// same text, or the end of the change if the change covers it. +function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) return pos + if (cmp(pos, change.to) <= 0) return changeEnd(change) + + let line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch + if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch + return Pos(line, ch) +} + +export function computeSelAfterChange(doc, change) { + let out = [] + for (let i = 0; i < doc.sel.ranges.length; i++) { + let range = doc.sel.ranges[i] + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))) + } + return normalizeSelection(doc.cm, out, doc.sel.primIndex) +} + +function offsetPos(pos, old, nw) { + if (pos.line == old.line) + return Pos(nw.line, pos.ch - old.ch + nw.ch) + else + return Pos(nw.line + (pos.line - old.line), pos.ch) +} + +// Used by replaceSelections to allow moving the selection to the +// start or around the replaced test. Hint may be "start" or "around". +export function computeReplacedSel(doc, changes, hint) { + let out = [] + let oldPrev = Pos(doc.first, 0), newPrev = oldPrev + for (let i = 0; i < changes.length; i++) { + let change = changes[i] + let from = offsetPos(change.from, oldPrev, newPrev) + let to = offsetPos(changeEnd(change), oldPrev, newPrev) + oldPrev = change.to + newPrev = to + if (hint == "around") { + let range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0 + out[i] = new Range(inv ? to : from, inv ? from : to) + } else { + out[i] = new Range(from, from) + } + } + return new Selection(out, doc.sel.primIndex) +} diff --git a/docs/js/node_modules/codemirror/src/model/changes.js b/docs/js/node_modules/codemirror/src/model/changes.js new file mode 100644 index 000000000..48d2f6bb9 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/model/changes.js @@ -0,0 +1,339 @@ +import { retreatFrontier } from "../line/highlight.js" +import { startWorker } from "../display/highlight_worker.js" +import { operation } from "../display/operations.js" +import { regChange, regLineChange } from "../display/view_tracking.js" +import { clipLine, clipPos, cmp, Pos } from "../line/pos.js" +import { sawReadOnlySpans } from "../line/saw_special_spans.js" +import { lineLength, removeReadOnlyRanges, stretchSpansOverChange, visualLine } from "../line/spans.js" +import { getBetween, getLine, lineNo } from "../line/utils_line.js" +import { estimateHeight } from "../measurement/position_measurement.js" +import { hasHandler, signal, signalCursorActivity } from "../util/event.js" +import { indexOf, lst, map, sel_dontScroll } from "../util/misc.js" +import { signalLater } from "../util/operation_group.js" + +import { changeEnd, computeSelAfterChange } from "./change_measurement.js" +import { isWholeLineUpdate, linkedDocs, updateDoc } from "./document_data.js" +import { addChangeToHistory, historyChangeFromChange, mergeOldSpans, pushSelectionToHistory } from "./history.js" +import { Range, Selection } from "./selection.js" +import { setSelection, setSelectionNoUndo, skipAtomic } from "./selection_updates.js" + +// UPDATING + +// Allow "beforeChange" event handlers to influence a change +function filterChange(doc, change, update) { + let obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: () => obj.canceled = true + } + if (update) obj.update = (from, to, text, origin) => { + if (from) obj.from = clipPos(doc, from) + if (to) obj.to = clipPos(doc, to) + if (text) obj.text = text + if (origin !== undefined) obj.origin = origin + } + signal(doc, "beforeChange", doc, obj) + if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj) + + if (obj.canceled) { + if (doc.cm) doc.cm.curOp.updateInput = 2 + return null + } + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} +} + +// Apply a change to a document, and add it to the document's +// history, and propagating it to all linked documents. +export function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) + if (doc.cm.state.suppressEdits) return + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true) + if (!change) return + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + let split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to) + if (split) { + for (let i = split.length - 1; i >= 0; --i) + makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}) + } else { + makeChangeInner(doc, change) + } +} + +function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return + let selAfter = computeSelAfterChange(doc, change) + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN) + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)) + let rebased = [] + + linkedDocs(doc, (doc, sharedHist) => { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change) + rebased.push(doc.history) + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)) + }) +} + +// Revert a change stored in a document's history. +export function makeChangeFromHistory(doc, type, allowSelectionOnly) { + let suppress = doc.cm && doc.cm.state.suppressEdits + if (suppress && !allowSelectionOnly) return + + let hist = doc.history, event, selAfter = doc.sel + let source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + let i = 0 + for (; i < source.length; i++) { + event = source[i] + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + break + } + if (i == source.length) return + hist.lastOrigin = hist.lastSelOrigin = null + + for (;;) { + event = source.pop() + if (event.ranges) { + pushSelectionToHistory(event, dest) + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}) + return + } + selAfter = event + } else if (suppress) { + source.push(event) + return + } else break + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + let antiChanges = [] + pushSelectionToHistory(selAfter, dest) + dest.push({changes: antiChanges, generation: hist.generation}) + hist.generation = event.generation || ++hist.maxGeneration + + let filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange") + + for (let i = event.changes.length - 1; i >= 0; --i) { + let change = event.changes[i] + change.origin = type + if (filter && !filterChange(doc, change, false)) { + source.length = 0 + return + } + + antiChanges.push(historyChangeFromChange(doc, change)) + + let after = i ? computeSelAfterChange(doc, change) : lst(source) + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)) + if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}) + let rebased = [] + + // Propagate to the linked documents + linkedDocs(doc, (doc, sharedHist) => { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change) + rebased.push(doc.history) + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)) + }) + } +} + +// Sub-views need their line numbers shifted when text is added +// above or below them in the parent document. +function shiftDoc(doc, distance) { + if (distance == 0) return + doc.first += distance + doc.sel = new Selection(map(doc.sel.ranges, range => new Range( + Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch) + )), doc.sel.primIndex) + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance) + for (let d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + regLineChange(doc.cm, l, "gutter") + } +} + +// More lower-level change function, handling only a single document +// (not linked ones). +function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)) + return + } + if (change.from.line > doc.lastLine()) return + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + let shift = change.text.length - 1 - (doc.first - change.from.line) + shiftDoc(doc, shift) + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin} + } + let last = doc.lastLine() + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin} + } + + change.removed = getBetween(doc, change.from, change.to) + + if (!selAfter) selAfter = computeSelAfterChange(doc, change) + if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans) + else updateDoc(doc, change, spans) + setSelectionNoUndo(doc, selAfter, sel_dontScroll) + + if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0))) + doc.cantEdit = false +} + +// Handle the interaction of a change to a document with the editor +// that this document is part of. +function makeChangeSingleDocInEditor(cm, change, spans) { + let doc = cm.doc, display = cm.display, from = change.from, to = change.to + + let recomputeMaxLength = false, checkWidthStart = from.line + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))) + doc.iter(checkWidthStart, to.line + 1, line => { + if (line == display.maxLine) { + recomputeMaxLength = true + return true + } + }) + } + + if (doc.sel.contains(change.from, change.to) > -1) + signalCursorActivity(cm) + + updateDoc(doc, change, spans, estimateHeight(cm)) + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, line => { + let len = lineLength(line) + if (len > display.maxLineLength) { + display.maxLine = line + display.maxLineLength = len + display.maxLineChanged = true + recomputeMaxLength = false + } + }) + if (recomputeMaxLength) cm.curOp.updateMaxLine = true + } + + retreatFrontier(doc, from.line) + startWorker(cm, 400) + + let lendiff = change.text.length - (to.line - from.line) - 1 + // Remember that these lines changed, for updating the display + if (change.full) + regChange(cm) + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + regLineChange(cm, from.line, "text") + else + regChange(cm, from.line, to.line + 1, lendiff) + + let changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change") + if (changeHandler || changesHandler) { + let obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + } + if (changeHandler) signalLater(cm, "change", cm, obj) + if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj) + } + cm.display.selForContextMenu = null +} + +export function replaceRange(doc, code, from, to, origin) { + if (!to) to = from + if (cmp(to, from) < 0) [from, to] = [to, from] + if (typeof code == "string") code = doc.splitLines(code) + makeChange(doc, {from, to, text: code, origin}) +} + +// Rebasing/resetting history to deal with externally-sourced changes + +function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff + } else if (from < pos.line) { + pos.line = from + pos.ch = 0 + } +} + +// Tries to rebase an array of history events given a change in the +// document. If the change touches the same lines as the event, the +// event, and everything 'behind' it, is discarded. If the change is +// before the event, the event's positions are updated. Uses a +// copy-on-write scheme for the positions, to avoid having to +// reallocate them all on every rebase, but also avoid problems with +// shared position objects being unsafely updated. +function rebaseHistArray(array, from, to, diff) { + for (let i = 0; i < array.length; ++i) { + let sub = array[i], ok = true + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true } + for (let j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff) + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff) + } + continue + } + for (let j = 0; j < sub.changes.length; ++j) { + let cur = sub.changes[j] + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch) + cur.to = Pos(cur.to.line + diff, cur.to.ch) + } else if (from <= cur.to.line) { + ok = false + break + } + } + if (!ok) { + array.splice(0, i + 1) + i = 0 + } + } +} + +function rebaseHist(hist, change) { + let from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1 + rebaseHistArray(hist.done, from, to, diff) + rebaseHistArray(hist.undone, from, to, diff) +} + +// Utility for applying a change to a line by handle or number, +// returning the number and optionally registering the line as +// changed. +export function changeLine(doc, handle, changeType, op) { + let no = handle, line = handle + if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)) + else no = lineNo(handle) + if (no == null) return null + if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType) + return line +} diff --git a/docs/js/node_modules/codemirror/src/model/chunk.js b/docs/js/node_modules/codemirror/src/model/chunk.js new file mode 100644 index 000000000..d82716ded --- /dev/null +++ b/docs/js/node_modules/codemirror/src/model/chunk.js @@ -0,0 +1,167 @@ +import { cleanUpLine } from "../line/line_data.js" +import { indexOf } from "../util/misc.js" +import { signalLater } from "../util/operation_group.js" + +// The document is represented as a BTree consisting of leaves, with +// chunk of lines in them, and branches, with up to ten leaves or +// other branch nodes below them. The top node is always a branch +// node, and is the document object itself (meaning it has +// additional methods and properties). +// +// All nodes have parent links. The tree is used both to go from +// line numbers to line objects, and to go from objects to numbers. +// It also indexes by height, and is used to convert between height +// and line object, and to find the total height of the document. +// +// See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + +export function LeafChunk(lines) { + this.lines = lines + this.parent = null + let height = 0 + for (let i = 0; i < lines.length; ++i) { + lines[i].parent = this + height += lines[i].height + } + this.height = height +} + +LeafChunk.prototype = { + chunkSize() { return this.lines.length }, + + // Remove the n lines at offset 'at'. + removeInner(at, n) { + for (let i = at, e = at + n; i < e; ++i) { + let line = this.lines[i] + this.height -= line.height + cleanUpLine(line) + signalLater(line, "delete") + } + this.lines.splice(at, n) + }, + + // Helper used to collapse a small branch into a single leaf. + collapse(lines) { + lines.push.apply(lines, this.lines) + }, + + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner(at, lines, height) { + this.height += height + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) + for (let i = 0; i < lines.length; ++i) lines[i].parent = this + }, + + // Used to iterate over a part of the tree. + iterN(at, n, op) { + for (let e = at + n; at < e; ++at) + if (op(this.lines[at])) return true + } +} + +export function BranchChunk(children) { + this.children = children + let size = 0, height = 0 + for (let i = 0; i < children.length; ++i) { + let ch = children[i] + size += ch.chunkSize(); height += ch.height + ch.parent = this + } + this.size = size + this.height = height + this.parent = null +} + +BranchChunk.prototype = { + chunkSize() { return this.size }, + + removeInner(at, n) { + this.size -= n + for (let i = 0; i < this.children.length; ++i) { + let child = this.children[i], sz = child.chunkSize() + if (at < sz) { + let rm = Math.min(n, sz - at), oldHeight = child.height + child.removeInner(at, rm) + this.height -= oldHeight - child.height + if (sz == rm) { this.children.splice(i--, 1); child.parent = null } + if ((n -= rm) == 0) break + at = 0 + } else at -= sz + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + let lines = [] + this.collapse(lines) + this.children = [new LeafChunk(lines)] + this.children[0].parent = this + } + }, + + collapse(lines) { + for (let i = 0; i < this.children.length; ++i) this.children[i].collapse(lines) + }, + + insertInner(at, lines, height) { + this.size += lines.length + this.height += height + for (let i = 0; i < this.children.length; ++i) { + let child = this.children[i], sz = child.chunkSize() + if (at <= sz) { + child.insertInner(at, lines, height) + if (child.lines && child.lines.length > 50) { + // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. + // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. + let remaining = child.lines.length % 25 + 25 + for (let pos = remaining; pos < child.lines.length;) { + let leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) + child.height -= leaf.height + this.children.splice(++i, 0, leaf) + leaf.parent = this + } + child.lines = child.lines.slice(0, remaining) + this.maybeSpill() + } + break + } + at -= sz + } + }, + + // When a node has grown, check whether it should be split. + maybeSpill() { + if (this.children.length <= 10) return + let me = this + do { + let spilled = me.children.splice(me.children.length - 5, 5) + let sibling = new BranchChunk(spilled) + if (!me.parent) { // Become the parent node + let copy = new BranchChunk(me.children) + copy.parent = me + me.children = [copy, sibling] + me = copy + } else { + me.size -= sibling.size + me.height -= sibling.height + let myIndex = indexOf(me.parent.children, me) + me.parent.children.splice(myIndex + 1, 0, sibling) + } + sibling.parent = me.parent + } while (me.children.length > 10) + me.parent.maybeSpill() + }, + + iterN(at, n, op) { + for (let i = 0; i < this.children.length; ++i) { + let child = this.children[i], sz = child.chunkSize() + if (at < sz) { + let used = Math.min(n, sz - at) + if (child.iterN(at, used, op)) return true + if ((n -= used) == 0) break + at = 0 + } else at -= sz + } + } +} diff --git a/docs/js/node_modules/codemirror/src/model/document_data.js b/docs/js/node_modules/codemirror/src/model/document_data.js new file mode 100644 index 000000000..d946e7af1 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/model/document_data.js @@ -0,0 +1,111 @@ +import { loadMode } from "../display/mode_state.js" +import { runInOp } from "../display/operations.js" +import { regChange } from "../display/view_tracking.js" +import { Line, updateLine } from "../line/line_data.js" +import { findMaxLine } from "../line/spans.js" +import { getLine } from "../line/utils_line.js" +import { estimateLineHeights } from "../measurement/position_measurement.js" +import { addClass, rmClass } from "../util/dom.js" +import { lst } from "../util/misc.js" +import { signalLater } from "../util/operation_group.js" + +// DOCUMENT DATA STRUCTURE + +// By default, updates that start and end at the beginning of a line +// are treated specially, in order to make the association of line +// widgets and marker elements with the text behave more intuitive. +export function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore) +} + +// Perform a change on the document data structure. +export function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight) + signalLater(line, "change", line, change) + } + function linesFor(start, end) { + let result = [] + for (let i = start; i < end; ++i) + result.push(new Line(text[i], spansFor(i), estimateHeight)) + return result + } + + let from = change.from, to = change.to, text = change.text + let firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line) + let lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)) + doc.remove(text.length, doc.size - text.length) + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + let added = linesFor(0, text.length - 1) + update(lastLine, lastLine.text, lastSpans) + if (nlines) doc.remove(from.line, nlines) + if (added.length) doc.insert(from.line, added) + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans) + } else { + let added = linesFor(1, text.length - 1) + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)) + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) + doc.insert(from.line + 1, added) + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)) + doc.remove(from.line + 1, nlines) + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans) + let added = linesFor(1, text.length - 1) + if (nlines > 1) doc.remove(from.line + 1, nlines - 1) + doc.insert(from.line + 1, added) + } + + signalLater(doc, "change", doc, change) +} + +// Call f for all linked documents. +export function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) for (let i = 0; i < doc.linked.length; ++i) { + let rel = doc.linked[i] + if (rel.doc == skip) continue + let shared = sharedHist && rel.sharedHist + if (sharedHistOnly && !shared) continue + f(rel.doc, shared) + propagate(rel.doc, doc, shared) + } + } + propagate(doc, null, true) +} + +// Attach a document to an editor. +export function attachDoc(cm, doc) { + if (doc.cm) throw new Error("This document is already in use.") + cm.doc = doc + doc.cm = cm + estimateLineHeights(cm) + loadMode(cm) + setDirectionClass(cm) + if (!cm.options.lineWrapping) findMaxLine(cm) + cm.options.mode = doc.modeOption + regChange(cm) +} + +function setDirectionClass(cm) { + ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl") +} + +export function directionChanged(cm) { + runInOp(cm, () => { + setDirectionClass(cm) + regChange(cm) + }) +} diff --git a/docs/js/node_modules/codemirror/src/model/history.js b/docs/js/node_modules/codemirror/src/model/history.js new file mode 100644 index 000000000..2d9359f00 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/model/history.js @@ -0,0 +1,228 @@ +import { cmp, copyPos } from "../line/pos.js" +import { stretchSpansOverChange } from "../line/spans.js" +import { getBetween } from "../line/utils_line.js" +import { signal } from "../util/event.js" +import { indexOf, lst } from "../util/misc.js" + +import { changeEnd } from "./change_measurement.js" +import { linkedDocs } from "./document_data.js" +import { Selection } from "./selection.js" + +export function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = [] + this.undoDepth = Infinity + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0 + this.lastOp = this.lastSelOp = null + this.lastOrigin = this.lastSelOrigin = null + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1 +} + +// Create a history change event from an updateDoc-style change +// object. +export function historyChangeFromChange(doc, change) { + let histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)} + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1) + linkedDocs(doc, doc => attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1), true) + return histChange +} + +// Pop all selection events off the end of a history array. Stop at +// a change event. +function clearSelectionEvents(array) { + while (array.length) { + let last = lst(array) + if (last.ranges) array.pop() + else break + } +} + +// Find the top change event in the history. Pop off selection +// events that are in the way. +function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done) + return lst(hist.done) + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done) + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop() + return lst(hist.done) + } +} + +// Register a change in the history. Merges changes that are within +// a single operation, or are close together with an origin that +// allows merging (starting with "+") into a single event. +export function addChangeToHistory(doc, change, selAfter, opId) { + let hist = doc.history + hist.undone.length = 0 + let time = +new Date, cur + let last + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + last = lst(cur.changes) + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change) + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)) + } + } else { + // Can not be merged, start a new event. + let before = lst(hist.done) + if (!before || !before.ranges) + pushSelectionToHistory(doc.sel, hist.done) + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation} + hist.done.push(cur) + while (hist.done.length > hist.undoDepth) { + hist.done.shift() + if (!hist.done[0].ranges) hist.done.shift() + } + } + hist.done.push(selAfter) + hist.generation = ++hist.maxGeneration + hist.lastModTime = hist.lastSelTime = time + hist.lastOp = hist.lastSelOp = opId + hist.lastOrigin = hist.lastSelOrigin = change.origin + + if (!last) signal(doc, "historyAdded") +} + +function selectionEventCanBeMerged(doc, origin, prev, sel) { + let ch = origin.charAt(0) + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) +} + +// Called whenever the selection changes, sets the new selection as +// the pending selection in the history, and pushes the old pending +// selection into the 'done' array when it was significantly +// different (in number of selected ranges, emptiness, or time). +export function addSelectionToHistory(doc, sel, opId, options) { + let hist = doc.history, origin = options && options.origin + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + hist.done[hist.done.length - 1] = sel + else + pushSelectionToHistory(sel, hist.done) + + hist.lastSelTime = +new Date + hist.lastSelOrigin = origin + hist.lastSelOp = opId + if (options && options.clearRedo !== false) + clearSelectionEvents(hist.undone) +} + +export function pushSelectionToHistory(sel, dest) { + let top = lst(dest) + if (!(top && top.ranges && top.equals(sel))) + dest.push(sel) +} + +// Used to store marked span information in the history. +function attachLocalSpans(doc, change, from, to) { + let existing = change["spans_" + doc.id], n = 0 + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), line => { + if (line.markedSpans) + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans + ++n + }) +} + +// When un/re-doing restores text containing marked spans, those +// that have been explicitly cleared should not be restored. +function removeClearedSpans(spans) { + if (!spans) return null + let out + for (let i = 0; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i) } + else if (out) out.push(spans[i]) + } + return !out ? spans : out.length ? out : null +} + +// Retrieve and filter the old marked spans stored in a change event. +function getOldSpans(doc, change) { + let found = change["spans_" + doc.id] + if (!found) return null + let nw = [] + for (let i = 0; i < change.text.length; ++i) + nw.push(removeClearedSpans(found[i])) + return nw +} + +// Used for un/re-doing changes from the history. Combines the +// result of computing the existing spans with the set of spans that +// existed in the history (so that deleting around a span and then +// undoing brings back the span). +export function mergeOldSpans(doc, change) { + let old = getOldSpans(doc, change) + let stretched = stretchSpansOverChange(doc, change) + if (!old) return stretched + if (!stretched) return old + + for (let i = 0; i < old.length; ++i) { + let oldCur = old[i], stretchCur = stretched[i] + if (oldCur && stretchCur) { + spans: for (let j = 0; j < stretchCur.length; ++j) { + let span = stretchCur[j] + for (let k = 0; k < oldCur.length; ++k) + if (oldCur[k].marker == span.marker) continue spans + oldCur.push(span) + } + } else if (stretchCur) { + old[i] = stretchCur + } + } + return old +} + +// Used both to provide a JSON-safe object in .getHistory, and, when +// detaching a document, to split the history in two +export function copyHistoryArray(events, newGroup, instantiateSel) { + let copy = [] + for (let i = 0; i < events.length; ++i) { + let event = events[i] + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event) + continue + } + let changes = event.changes, newChanges = [] + copy.push({changes: newChanges}) + for (let j = 0; j < changes.length; ++j) { + let change = changes[j], m + newChanges.push({from: change.from, to: change.to, text: change.text}) + if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop] + delete change[prop] + } + } + } + } + return copy +} diff --git a/docs/js/node_modules/codemirror/src/model/line_widget.js b/docs/js/node_modules/codemirror/src/model/line_widget.js new file mode 100644 index 000000000..5444d89df --- /dev/null +++ b/docs/js/node_modules/codemirror/src/model/line_widget.js @@ -0,0 +1,78 @@ +import { runInOp } from "../display/operations.js" +import { addToScrollTop } from "../display/scrolling.js" +import { regLineChange } from "../display/view_tracking.js" +import { heightAtLine, lineIsHidden } from "../line/spans.js" +import { lineNo, updateLineHeight } from "../line/utils_line.js" +import { widgetHeight } from "../measurement/widgets.js" +import { changeLine } from "./changes.js" +import { eventMixin } from "../util/event.js" +import { signalLater } from "../util/operation_group.js" + +// Line widgets are block elements displayed above or below a line. + +export class LineWidget { + constructor(doc, node, options) { + if (options) for (let opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt] + this.doc = doc + this.node = node + } + + clear() { + let cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line) + if (no == null || !ws) return + for (let i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1) + if (!ws.length) line.widgets = null + let height = widgetHeight(this) + updateLineHeight(line, Math.max(0, line.height - height)) + if (cm) { + runInOp(cm, () => { + adjustScrollWhenAboveVisible(cm, line, -height) + regLineChange(cm, no, "widget") + }) + signalLater(cm, "lineWidgetCleared", cm, this, no) + } + } + + changed() { + let oldH = this.height, cm = this.doc.cm, line = this.line + this.height = null + let diff = widgetHeight(this) - oldH + if (!diff) return + if (!lineIsHidden(this.doc, line)) updateLineHeight(line, line.height + diff) + if (cm) { + runInOp(cm, () => { + cm.curOp.forceUpdate = true + adjustScrollWhenAboveVisible(cm, line, diff) + signalLater(cm, "lineWidgetChanged", cm, this, lineNo(line)) + }) + } + } +} +eventMixin(LineWidget) + +function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + addToScrollTop(cm, diff) +} + +export function addLineWidget(doc, handle, node, options) { + let widget = new LineWidget(doc, node, options) + let cm = doc.cm + if (cm && widget.noHScroll) cm.display.alignWidgets = true + changeLine(doc, handle, "widget", line => { + let widgets = line.widgets || (line.widgets = []) + if (widget.insertAt == null) widgets.push(widget) + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget) + widget.line = line + if (cm && !lineIsHidden(doc, line)) { + let aboveVisible = heightAtLine(line) < doc.scrollTop + updateLineHeight(line, line.height + widgetHeight(widget)) + if (aboveVisible) addToScrollTop(cm, widget.height) + cm.curOp.forceUpdate = true + } + return true + }) + if (cm) signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)) + return widget +} diff --git a/docs/js/node_modules/codemirror/src/model/mark_text.js b/docs/js/node_modules/codemirror/src/model/mark_text.js new file mode 100644 index 000000000..088f9c98e --- /dev/null +++ b/docs/js/node_modules/codemirror/src/model/mark_text.js @@ -0,0 +1,293 @@ +import { eltP } from "../util/dom.js" +import { eventMixin, hasHandler, on } from "../util/event.js" +import { endOperation, operation, runInOp, startOperation } from "../display/operations.js" +import { clipPos, cmp, Pos } from "../line/pos.js" +import { lineNo, updateLineHeight } from "../line/utils_line.js" +import { clearLineMeasurementCacheFor, findViewForLine, textHeight } from "../measurement/position_measurement.js" +import { seeReadOnlySpans, seeCollapsedSpans } from "../line/saw_special_spans.js" +import { addMarkedSpan, conflictingCollapsedRange, getMarkedSpanFor, lineIsHidden, lineLength, MarkedSpan, removeMarkedSpan, visualLine } from "../line/spans.js" +import { copyObj, indexOf, lst } from "../util/misc.js" +import { signalLater } from "../util/operation_group.js" +import { widgetHeight } from "../measurement/widgets.js" +import { regChange, regLineChange } from "../display/view_tracking.js" + +import { linkedDocs } from "./document_data.js" +import { addChangeToHistory } from "./history.js" +import { reCheckSelection } from "./selection_updates.js" + +// TEXTMARKERS + +// Created with markText and setBookmark methods. A TextMarker is a +// handle that can be used to clear or find a marked position in the +// document. Line objects hold arrays (markedSpans) containing +// {from, to, marker} object pointing to such marker objects, and +// indicating that such a marker is present on that line. Multiple +// lines may point to the same marker when it spans across lines. +// The spans will have null for their from/to properties when the +// marker continues beyond the start/end of the line. Markers have +// links back to the lines they currently touch. + +// Collapsed markers have unique ids, in order to be able to order +// them, which is needed for uniquely determining an outer marker +// when they overlap (they may nest, but not partially overlap). +let nextMarkerId = 0 + +export class TextMarker { + constructor(doc, type) { + this.lines = [] + this.type = type + this.doc = doc + this.id = ++nextMarkerId + } + + // Clear the marker. + clear() { + if (this.explicitlyCleared) return + let cm = this.doc.cm, withOp = cm && !cm.curOp + if (withOp) startOperation(cm) + if (hasHandler(this, "clear")) { + let found = this.find() + if (found) signalLater(this, "clear", found.from, found.to) + } + let min = null, max = null + for (let i = 0; i < this.lines.length; ++i) { + let line = this.lines[i] + let span = getMarkedSpanFor(line.markedSpans, this) + if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text") + else if (cm) { + if (span.to != null) max = lineNo(line) + if (span.from != null) min = lineNo(line) + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span) + if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) + updateLineHeight(line, textHeight(cm.display)) + } + if (cm && this.collapsed && !cm.options.lineWrapping) for (let i = 0; i < this.lines.length; ++i) { + let visual = visualLine(this.lines[i]), len = lineLength(visual) + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual + cm.display.maxLineLength = len + cm.display.maxLineChanged = true + } + } + + if (min != null && cm && this.collapsed) regChange(cm, min, max + 1) + this.lines.length = 0 + this.explicitlyCleared = true + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false + if (cm) reCheckSelection(cm.doc) + } + if (cm) signalLater(cm, "markerCleared", cm, this, min, max) + if (withOp) endOperation(cm) + if (this.parent) this.parent.clear() + } + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + find(side, lineObj) { + if (side == null && this.type == "bookmark") side = 1 + let from, to + for (let i = 0; i < this.lines.length; ++i) { + let line = this.lines[i] + let span = getMarkedSpanFor(line.markedSpans, this) + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from) + if (side == -1) return from + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to) + if (side == 1) return to + } + } + return from && {from: from, to: to} + } + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + changed() { + let pos = this.find(-1, true), widget = this, cm = this.doc.cm + if (!pos || !cm) return + runInOp(cm, () => { + let line = pos.line, lineN = lineNo(pos.line) + let view = findViewForLine(cm, lineN) + if (view) { + clearLineMeasurementCacheFor(view) + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true + } + cm.curOp.updateMaxLine = true + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + let oldHeight = widget.height + widget.height = null + let dHeight = widgetHeight(widget) - oldHeight + if (dHeight) + updateLineHeight(line, line.height + dHeight) + } + signalLater(cm, "markerChanged", cm, this) + }) + } + + attachLine(line) { + if (!this.lines.length && this.doc.cm) { + let op = this.doc.cm.curOp + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) + } + this.lines.push(line) + } + + detachLine(line) { + this.lines.splice(indexOf(this.lines, line), 1) + if (!this.lines.length && this.doc.cm) { + let op = this.doc.cm.curOp + ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this) + } + } +} +eventMixin(TextMarker) + +// Create a marker, wire it up to the right lines, and +export function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) return markTextShared(doc, from, to, options, type) + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type) + + let marker = new TextMarker(doc, type), diff = cmp(from, to) + if (options) copyObj(options, marker, false) + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + return marker + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true + marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget") + if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true") + if (options.insertLeft) marker.widgetNode.insertLeft = true + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + throw new Error("Inserting collapsed marker partially overlapping an existing one") + seeCollapsedSpans() + } + + if (marker.addToHistory) + addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN) + + let curLine = from.line, cm = doc.cm, updateMaxLine + doc.iter(curLine, to.line + 1, line => { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + updateMaxLine = true + if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0) + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)) + ++curLine + }) + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) doc.iter(from.line, to.line + 1, line => { + if (lineIsHidden(doc, line)) updateLineHeight(line, 0) + }) + + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", () => marker.clear()) + + if (marker.readOnly) { + seeReadOnlySpans() + if (doc.history.done.length || doc.history.undone.length) + doc.clearHistory() + } + if (marker.collapsed) { + marker.id = ++nextMarkerId + marker.atomic = true + } + if (cm) { + // Sync editor state + if (updateMaxLine) cm.curOp.updateMaxLine = true + if (marker.collapsed) + regChange(cm, from.line, to.line + 1) + else if (marker.className || marker.startStyle || marker.endStyle || marker.css || + marker.attributes || marker.title) + for (let i = from.line; i <= to.line; i++) regLineChange(cm, i, "text") + if (marker.atomic) reCheckSelection(cm.doc) + signalLater(cm, "markerAdded", cm, marker) + } + return marker +} + +// SHARED TEXTMARKERS + +// A shared marker spans multiple linked documents. It is +// implemented as a meta-marker-object controlling multiple normal +// markers. +export class SharedTextMarker { + constructor(markers, primary) { + this.markers = markers + this.primary = primary + for (let i = 0; i < markers.length; ++i) + markers[i].parent = this + } + + clear() { + if (this.explicitlyCleared) return + this.explicitlyCleared = true + for (let i = 0; i < this.markers.length; ++i) + this.markers[i].clear() + signalLater(this, "clear") + } + + find(side, lineObj) { + return this.primary.find(side, lineObj) + } +} +eventMixin(SharedTextMarker) + +function markTextShared(doc, from, to, options, type) { + options = copyObj(options) + options.shared = false + let markers = [markText(doc, from, to, options, type)], primary = markers[0] + let widget = options.widgetNode + linkedDocs(doc, doc => { + if (widget) options.widgetNode = widget.cloneNode(true) + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)) + for (let i = 0; i < doc.linked.length; ++i) + if (doc.linked[i].isParent) return + primary = lst(markers) + }) + return new SharedTextMarker(markers, primary) +} + +export function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), m => m.parent) +} + +export function copySharedMarkers(doc, markers) { + for (let i = 0; i < markers.length; i++) { + let marker = markers[i], pos = marker.find() + let mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to) + if (cmp(mFrom, mTo)) { + let subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type) + marker.markers.push(subMark) + subMark.parent = marker + } + } +} + +export function detachSharedMarkers(markers) { + for (let i = 0; i < markers.length; i++) { + let marker = markers[i], linked = [marker.primary.doc] + linkedDocs(marker.primary.doc, d => linked.push(d)) + for (let j = 0; j < marker.markers.length; j++) { + let subMarker = marker.markers[j] + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null + marker.markers.splice(j--, 1) + } + } + } +} diff --git a/docs/js/node_modules/codemirror/src/model/selection.js b/docs/js/node_modules/codemirror/src/model/selection.js new file mode 100644 index 000000000..793cb4ca0 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/model/selection.js @@ -0,0 +1,84 @@ +import { cmp, copyPos, equalCursorPos, maxPos, minPos } from "../line/pos.js" +import { indexOf } from "../util/misc.js" + +// Selection objects are immutable. A new one is created every time +// the selection changes. A selection is one or more non-overlapping +// (and non-touching) ranges, sorted, and an integer that indicates +// which one is the primary selection (the one that's scrolled into +// view, that getCursor returns, etc). +export class Selection { + constructor(ranges, primIndex) { + this.ranges = ranges + this.primIndex = primIndex + } + + primary() { return this.ranges[this.primIndex] } + + equals(other) { + if (other == this) return true + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false + for (let i = 0; i < this.ranges.length; i++) { + let here = this.ranges[i], there = other.ranges[i] + if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) return false + } + return true + } + + deepCopy() { + let out = [] + for (let i = 0; i < this.ranges.length; i++) + out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)) + return new Selection(out, this.primIndex) + } + + somethingSelected() { + for (let i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].empty()) return true + return false + } + + contains(pos, end) { + if (!end) end = pos + for (let i = 0; i < this.ranges.length; i++) { + let range = this.ranges[i] + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + return i + } + return -1 + } +} + +export class Range { + constructor(anchor, head) { + this.anchor = anchor; this.head = head + } + + from() { return minPos(this.anchor, this.head) } + to() { return maxPos(this.anchor, this.head) } + empty() { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch } +} + +// Take an unsorted, potentially overlapping set of ranges, and +// build a selection out of it. 'Consumes' ranges array (modifying +// it). +export function normalizeSelection(cm, ranges, primIndex) { + let mayTouch = cm && cm.options.selectionsMayTouch + let prim = ranges[primIndex] + ranges.sort((a, b) => cmp(a.from(), b.from())) + primIndex = indexOf(ranges, prim) + for (let i = 1; i < ranges.length; i++) { + let cur = ranges[i], prev = ranges[i - 1] + let diff = cmp(prev.to(), cur.from()) + if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) { + let from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()) + let inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head + if (i <= primIndex) --primIndex + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)) + } + } + return new Selection(ranges, primIndex) +} + +export function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0) +} diff --git a/docs/js/node_modules/codemirror/src/model/selection_updates.js b/docs/js/node_modules/codemirror/src/model/selection_updates.js new file mode 100644 index 000000000..4db2bd7f5 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/model/selection_updates.js @@ -0,0 +1,216 @@ +import { signalLater } from "../util/operation_group.js" +import { ensureCursorVisible } from "../display/scrolling.js" +import { clipPos, cmp, Pos } from "../line/pos.js" +import { getLine } from "../line/utils_line.js" +import { hasHandler, signal, signalCursorActivity } from "../util/event.js" +import { lst, sel_dontScroll } from "../util/misc.js" + +import { addSelectionToHistory } from "./history.js" +import { normalizeSelection, Range, Selection, simpleSelection } from "./selection.js" + +// The 'scroll' parameter given to many of these indicated whether +// the new cursor position should be scrolled into view after +// modifying the selection. + +// If shift is held or the extend flag is set, extends a range to +// include a given position (and optionally a second position). +// Otherwise, simply returns the range between the given positions. +// Used for cursor motion and such. +export function extendRange(range, head, other, extend) { + if (extend) { + let anchor = range.anchor + if (other) { + let posBefore = cmp(head, anchor) < 0 + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head + head = other + } else if (posBefore != (cmp(head, other) < 0)) { + head = other + } + } + return new Range(anchor, head) + } else { + return new Range(other || head, head) + } +} + +// Extend the primary selection range, discard the rest. +export function extendSelection(doc, head, other, options, extend) { + if (extend == null) extend = doc.cm && (doc.cm.display.shift || doc.extend) + setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options) +} + +// Extend all selections (pos is an array of selections with length +// equal the number of selections) +export function extendSelections(doc, heads, options) { + let out = [] + let extend = doc.cm && (doc.cm.display.shift || doc.extend) + for (let i = 0; i < doc.sel.ranges.length; i++) + out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend) + let newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex) + setSelection(doc, newSel, options) +} + +// Updates a single range in the selection. +export function replaceOneSelection(doc, i, range, options) { + let ranges = doc.sel.ranges.slice(0) + ranges[i] = range + setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options) +} + +// Reset the selection to a single range. +export function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options) +} + +// Give beforeSelectionChange handlers a change to influence a +// selection update. +function filterSelectionChange(doc, sel, options) { + let obj = { + ranges: sel.ranges, + update: function(ranges) { + this.ranges = [] + for (let i = 0; i < ranges.length; i++) + this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)) + }, + origin: options && options.origin + } + signal(doc, "beforeSelectionChange", doc, obj) + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj) + if (obj.ranges != sel.ranges) return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) + else return sel +} + +export function setSelectionReplaceHistory(doc, sel, options) { + let done = doc.history.done, last = lst(done) + if (last && last.ranges) { + done[done.length - 1] = sel + setSelectionNoUndo(doc, sel, options) + } else { + setSelection(doc, sel, options) + } +} + +// Set a new selection. +export function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options) + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) +} + +export function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + sel = filterSelectionChange(doc, sel, options) + + let bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1) + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)) + + if (!(options && options.scroll === false) && doc.cm) + ensureCursorVisible(doc.cm) +} + +function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) return + + doc.sel = sel + + if (doc.cm) { + doc.cm.curOp.updateInput = 1 + doc.cm.curOp.selectionChanged = true + signalCursorActivity(doc.cm) + } + signalLater(doc, "cursorActivity", doc) +} + +// Verify that the selection does not partially select any atomic +// marked ranges. +export function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)) +} + +// Return a selection that does not partially select any atomic +// ranges. +function skipAtomicInSelection(doc, sel, bias, mayClear) { + let out + for (let i = 0; i < sel.ranges.length; i++) { + let range = sel.ranges[i] + let old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i] + let newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear) + let newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear) + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) out = sel.ranges.slice(0, i) + out[i] = new Range(newAnchor, newHead) + } + } + return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel +} + +function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { + let line = getLine(doc, pos.line) + if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) { + let sp = line.markedSpans[i], m = sp.marker + + // Determine if we should prevent the cursor being placed to the left/right of an atomic marker + // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it + // is with selectLeft/Right + let preventCursorLeft = ("selectLeft" in m) ? !m.selectLeft : m.inclusiveLeft + let preventCursorRight = ("selectRight" in m) ? !m.selectRight : m.inclusiveRight + + if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && + (sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter") + if (m.explicitlyCleared) { + if (!line.markedSpans) break + else {--i; continue} + } + } + if (!m.atomic) continue + + if (oldPos) { + let near = m.find(dir < 0 ? 1 : -1), diff + if (dir < 0 ? preventCursorRight : preventCursorLeft) + near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) + if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) + return skipAtomicInner(doc, near, pos, dir, mayClear) + } + + let far = m.find(dir < 0 ? -1 : 1) + if (dir < 0 ? preventCursorLeft : preventCursorRight) + far = movePos(doc, far, dir, far.line == pos.line ? line : null) + return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null + } + } + return pos +} + +// Ensure a given position is not inside an atomic range. +export function skipAtomic(doc, pos, oldPos, bias, mayClear) { + let dir = bias || 1 + let found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || + skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)) + if (!found) { + doc.cantEdit = true + return Pos(doc.first, 0) + } + return found +} + +function movePos(doc, pos, dir, line) { + if (dir < 0 && pos.ch == 0) { + if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1)) + else return null + } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { + if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0) + else return null + } else { + return new Pos(pos.line, pos.ch + dir) + } +} + +export function selectAll(cm) { + cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll) +} diff --git a/docs/js/node_modules/codemirror/src/modes.js b/docs/js/node_modules/codemirror/src/modes.js new file mode 100644 index 000000000..838451702 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/modes.js @@ -0,0 +1,96 @@ +import { copyObj, createObj } from "./util/misc.js" + +// Known modes, by name and by MIME +export let modes = {}, mimeModes = {} + +// Extra arguments are stored as the mode's dependencies, which is +// used by (legacy) mechanisms like loadmode.js to automatically +// load a mode. (Preferred mechanism is the require/define calls.) +export function defineMode(name, mode) { + if (arguments.length > 2) + mode.dependencies = Array.prototype.slice.call(arguments, 2) + modes[name] = mode +} + +export function defineMIME(mime, spec) { + mimeModes[mime] = spec +} + +// Given a MIME type, a {name, ...options} config object, or a name +// string, return a mode config object. +export function resolveMode(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec] + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + let found = mimeModes[spec.name] + if (typeof found == "string") found = {name: found} + spec = createObj(found, spec) + spec.name = found.name + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return resolveMode("application/xml") + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { + return resolveMode("application/json") + } + if (typeof spec == "string") return {name: spec} + else return spec || {name: "null"} +} + +// Given a mode spec (anything that resolveMode accepts), find and +// initialize an actual mode object. +export function getMode(options, spec) { + spec = resolveMode(spec) + let mfactory = modes[spec.name] + if (!mfactory) return getMode(options, "text/plain") + let modeObj = mfactory(options, spec) + if (modeExtensions.hasOwnProperty(spec.name)) { + let exts = modeExtensions[spec.name] + for (let prop in exts) { + if (!exts.hasOwnProperty(prop)) continue + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop] + modeObj[prop] = exts[prop] + } + } + modeObj.name = spec.name + if (spec.helperType) modeObj.helperType = spec.helperType + if (spec.modeProps) for (let prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop] + + return modeObj +} + +// This can be used to attach properties to mode objects from +// outside the actual mode definition. +export let modeExtensions = {} +export function extendMode(mode, properties) { + let exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}) + copyObj(properties, exts) +} + +export function copyState(mode, state) { + if (state === true) return state + if (mode.copyState) return mode.copyState(state) + let nstate = {} + for (let n in state) { + let val = state[n] + if (val instanceof Array) val = val.concat([]) + nstate[n] = val + } + return nstate +} + +// Given a mode and a state (for that mode), find the inner mode and +// state at the position that the state refers to. +export function innerMode(mode, state) { + let info + while (mode.innerMode) { + info = mode.innerMode(state) + if (!info || info.mode == mode) break + state = info.state + mode = info.mode + } + return info || {mode: mode, state: state} +} + +export function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true +} diff --git a/docs/js/node_modules/codemirror/src/util/StringStream.js b/docs/js/node_modules/codemirror/src/util/StringStream.js new file mode 100644 index 000000000..022c4bc20 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/util/StringStream.js @@ -0,0 +1,90 @@ +import { countColumn } from "./misc.js" + +// STRING STREAM + +// Fed to the mode parsers, provides helper functions to make +// parsers more succinct. + +class StringStream { + constructor(string, tabSize, lineOracle) { + this.pos = this.start = 0 + this.string = string + this.tabSize = tabSize || 8 + this.lastColumnPos = this.lastColumnValue = 0 + this.lineStart = 0 + this.lineOracle = lineOracle + } + + eol() {return this.pos >= this.string.length} + sol() {return this.pos == this.lineStart} + peek() {return this.string.charAt(this.pos) || undefined} + next() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++) + } + eat(match) { + let ch = this.string.charAt(this.pos) + let ok + if (typeof match == "string") ok = ch == match + else ok = ch && (match.test ? match.test(ch) : match(ch)) + if (ok) {++this.pos; return ch} + } + eatWhile(match) { + let start = this.pos + while (this.eat(match)){} + return this.pos > start + } + eatSpace() { + let start = this.pos + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos + return this.pos > start + } + skipToEnd() {this.pos = this.string.length} + skipTo(ch) { + let found = this.string.indexOf(ch, this.pos) + if (found > -1) {this.pos = found; return true} + } + backUp(n) {this.pos -= n} + column() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue) + this.lastColumnPos = this.start + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + } + indentation() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + } + match(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + let cased = str => caseInsensitive ? str.toLowerCase() : str + let substr = this.string.substr(this.pos, pattern.length) + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length + return true + } + } else { + let match = this.string.slice(this.pos).match(pattern) + if (match && match.index > 0) return null + if (match && consume !== false) this.pos += match[0].length + return match + } + } + current(){return this.string.slice(this.start, this.pos)} + hideFirstChars(n, inner) { + this.lineStart += n + try { return inner() } + finally { this.lineStart -= n } + } + lookAhead(n) { + let oracle = this.lineOracle + return oracle && oracle.lookAhead(n) + } + baseToken() { + let oracle = this.lineOracle + return oracle && oracle.baseToken(this.pos) + } +} + +export default StringStream diff --git a/docs/js/node_modules/codemirror/src/util/bidi.js b/docs/js/node_modules/codemirror/src/util/bidi.js new file mode 100644 index 000000000..33ab854d8 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/util/bidi.js @@ -0,0 +1,214 @@ +import { lst } from "./misc.js" + +// BIDI HELPERS + +export function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr", 0) + let found = false + for (let i = 0; i < order.length; ++i) { + let part = order[i] + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i) + found = true + } + } + if (!found) f(from, to, "ltr") +} + +export let bidiOther = null +export function getBidiPartAt(order, ch, sticky) { + let found + bidiOther = null + for (let i = 0; i < order.length; ++i) { + let cur = order[i] + if (cur.from < ch && cur.to > ch) return i + if (cur.to == ch) { + if (cur.from != cur.to && sticky == "before") found = i + else bidiOther = i + } + if (cur.from == ch) { + if (cur.from != cur.to && sticky != "before") found = i + else bidiOther = i + } + } + return found != null ? found : bidiOther +} + +// Bidirectional ordering algorithm +// See http://unicode.org/reports/tr9/tr9-13.html for the algorithm +// that this (partially) implements. + +// One-char codes used for character types: +// L (L): Left-to-Right +// R (R): Right-to-Left +// r (AL): Right-to-Left Arabic +// 1 (EN): European Number +// + (ES): European Number Separator +// % (ET): European Number Terminator +// n (AN): Arabic Number +// , (CS): Common Number Separator +// m (NSM): Non-Spacing Mark +// b (BN): Boundary Neutral +// s (B): Paragraph Separator +// t (S): Segment Separator +// w (WS): Whitespace +// N (ON): Other Neutrals + +// Returns null if characters are ordered as they appear +// (left-to-right), or an array of sections ({from, to, level} +// objects) in the order in which they occur visually. +let bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + let lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN" + // Character types for codepoints 0x600 to 0x6f9 + let arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111" + function charType(code) { + if (code <= 0xf7) return lowTypes.charAt(code) + else if (0x590 <= code && code <= 0x5f4) return "R" + else if (0x600 <= code && code <= 0x6f9) return arabicTypes.charAt(code - 0x600) + else if (0x6ee <= code && code <= 0x8ac) return "r" + else if (0x2000 <= code && code <= 0x200b) return "w" + else if (code == 0x200c) return "b" + else return "L" + } + + let bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/ + let isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/ + + function BidiSpan(level, from, to) { + this.level = level + this.from = from; this.to = to + } + + return function(str, direction) { + let outerType = direction == "ltr" ? "L" : "R" + + if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) return false + let len = str.length, types = [] + for (let i = 0; i < len; ++i) + types.push(charType(str.charCodeAt(i))) + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (let i = 0, prev = outerType; i < len; ++i) { + let type = types[i] + if (type == "m") types[i] = prev + else prev = type + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (let i = 0, cur = outerType; i < len; ++i) { + let type = types[i] + if (type == "1" && cur == "r") types[i] = "n" + else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R" } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (let i = 1, prev = types[0]; i < len - 1; ++i) { + let type = types[i] + if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1" + else if (type == "," && prev == types[i+1] && + (prev == "1" || prev == "n")) types[i] = prev + prev = type + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (let i = 0; i < len; ++i) { + let type = types[i] + if (type == ",") types[i] = "N" + else if (type == "%") { + let end + for (end = i + 1; end < len && types[end] == "%"; ++end) {} + let replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N" + for (let j = i; j < end; ++j) types[j] = replace + i = end - 1 + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (let i = 0, cur = outerType; i < len; ++i) { + let type = types[i] + if (cur == "L" && type == "1") types[i] = "L" + else if (isStrong.test(type)) cur = type + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (let i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + let end + for (end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + let before = (i ? types[i-1] : outerType) == "L" + let after = (end < len ? types[end] : outerType) == "L" + let replace = before == after ? (before ? "L" : "R") : outerType + for (let j = i; j < end; ++j) types[j] = replace + i = end - 1 + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + let order = [], m + for (let i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + let start = i + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push(new BidiSpan(0, start, i)) + } else { + let pos = i, at = order.length + for (++i; i < len && types[i] != "L"; ++i) {} + for (let j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)) + let nstart = j + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, new BidiSpan(2, nstart, j)) + pos = j + } else ++j + } + if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)) + } + } + if (direction == "ltr") { + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length + order.unshift(new BidiSpan(0, 0, m[0].length)) + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length + order.push(new BidiSpan(0, len - m[0].length, len)) + } + } + + return direction == "rtl" ? order.reverse() : order + } +})() + +// Get the bidi ordering for the given line (and cache it). Returns +// false for lines that are fully left-to-right, and an array of +// BidiSpan objects otherwise. +export function getOrder(line, direction) { + let order = line.order + if (order == null) order = line.order = bidiOrdering(line.text, direction) + return order +} diff --git a/docs/js/node_modules/codemirror/src/util/browser.js b/docs/js/node_modules/codemirror/src/util/browser.js new file mode 100644 index 000000000..9fc4602c6 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/util/browser.js @@ -0,0 +1,33 @@ +// Kludges for bugs and behavior differences that can't be feature +// detected are enabled based on userAgent etc sniffing. +let userAgent = navigator.userAgent +let platform = navigator.platform + +export let gecko = /gecko\/\d/i.test(userAgent) +let ie_upto10 = /MSIE \d/.test(userAgent) +let ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent) +let edge = /Edge\/(\d+)/.exec(userAgent) +export let ie = ie_upto10 || ie_11up || edge +export let ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]) +export let webkit = !edge && /WebKit\//.test(userAgent) +let qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent) +export let chrome = !edge && /Chrome\//.test(userAgent) +export let presto = /Opera\//.test(userAgent) +export let safari = /Apple Computer/.test(navigator.vendor) +export let mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent) +export let phantom = /PhantomJS/.test(userAgent) + +export let ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent) +export let android = /Android/.test(userAgent) +// This is woefully incomplete. Suggestions for alternative methods welcome. +export let mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent) +export let mac = ios || /Mac/.test(platform) +export let chromeOS = /\bCrOS\b/.test(userAgent) +export let windows = /win/i.test(platform) + +let presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/) +if (presto_version) presto_version = Number(presto_version[1]) +if (presto_version && presto_version >= 15) { presto = false; webkit = true } +// Some browsers use the wrong event properties to signal cmd/ctrl on OS X +export let flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)) +export let captureRightClick = gecko || (ie && ie_version >= 9) diff --git a/docs/js/node_modules/codemirror/src/util/dom.js b/docs/js/node_modules/codemirror/src/util/dom.js new file mode 100644 index 000000000..04d2569d2 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/util/dom.js @@ -0,0 +1,97 @@ +import { ie, ios } from "./browser.js" + +export function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } + +export let rmClass = function(node, cls) { + let current = node.className + let match = classTest(cls).exec(current) + if (match) { + let after = current.slice(match.index + match[0].length) + node.className = current.slice(0, match.index) + (after ? match[1] + after : "") + } +} + +export function removeChildren(e) { + for (let count = e.childNodes.length; count > 0; --count) + e.removeChild(e.firstChild) + return e +} + +export function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e) +} + +export function elt(tag, content, className, style) { + let e = document.createElement(tag) + if (className) e.className = className + if (style) e.style.cssText = style + if (typeof content == "string") e.appendChild(document.createTextNode(content)) + else if (content) for (let i = 0; i < content.length; ++i) e.appendChild(content[i]) + return e +} +// wrapper for elt, which removes the elt from the accessibility tree +export function eltP(tag, content, className, style) { + let e = elt(tag, content, className, style) + e.setAttribute("role", "presentation") + return e +} + +export let range +if (document.createRange) range = function(node, start, end, endNode) { + let r = document.createRange() + r.setEnd(endNode || node, end) + r.setStart(node, start) + return r +} +else range = function(node, start, end) { + let r = document.body.createTextRange() + try { r.moveToElementText(node.parentNode) } + catch(e) { return r } + r.collapse(true) + r.moveEnd("character", end) + r.moveStart("character", start) + return r +} + +export function contains(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + child = child.parentNode + if (parent.contains) + return parent.contains(child) + do { + if (child.nodeType == 11) child = child.host + if (child == parent) return true + } while (child = child.parentNode) +} + +export function activeElt() { + // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. + // IE < 10 will throw when accessed while the page is loading or in an iframe. + // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. + let activeElement + try { + activeElement = document.activeElement + } catch(e) { + activeElement = document.body || null + } + while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) + activeElement = activeElement.shadowRoot.activeElement + return activeElement +} + +export function addClass(node, cls) { + let current = node.className + if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls +} +export function joinClasses(a, b) { + let as = a.split(" ") + for (let i = 0; i < as.length; i++) + if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i] + return b +} + +export let selectInput = function(node) { node.select() } +if (ios) // Mobile Safari apparently has a bug where select() is broken. + selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length } +else if (ie) // Suppress mysterious IE10 errors + selectInput = function(node) { try { node.select() } catch(_e) {} } diff --git a/docs/js/node_modules/codemirror/src/util/event.js b/docs/js/node_modules/codemirror/src/util/event.js new file mode 100644 index 000000000..4b6c77057 --- /dev/null +++ b/docs/js/node_modules/codemirror/src/util/event.js @@ -0,0 +1,103 @@ +import { mac } from "./browser.js" +import { indexOf } from "./misc.js" + +// EVENT HANDLING + +// Lightweight event framework. on/off also work on DOM nodes, +// registering native DOM handlers. + +const noHandlers = [] + +export let on = function(emitter, type, f) { + if (emitter.addEventListener) { + emitter.addEventListener(type, f, false) + } else if (emitter.attachEvent) { + emitter.attachEvent("on" + type, f) + } else { + let map = emitter._handlers || (emitter._handlers = {}) + map[type] = (map[type] || noHandlers).concat(f) + } +} + +export function getHandlers(emitter, type) { + return emitter._handlers && emitter._handlers[type] || noHandlers +} + +export function off(emitter, type, f) { + if (emitter.removeEventListener) { + emitter.removeEventListener(type, f, false) + } else if (emitter.detachEvent) { + emitter.detachEvent("on" + type, f) + } else { + let map = emitter._handlers, arr = map && map[type] + if (arr) { + let index = indexOf(arr, f) + if (index > -1) + map[type] = arr.slice(0, index).concat(arr.slice(index + 1)) + } + } +} + +export function signal(emitter, type /*, values...*/) { + let handlers = getHandlers(emitter, type) + if (!handlers.length) return + let args = Array.prototype.slice.call(arguments, 2) + for (let i = 0; i < handlers.length; ++i) handlers[i].apply(null, args) +} + +// The DOM events that CodeMirror handles can be overridden by +// registering a (non-DOM) handler on the editor for the event name, +// and preventDefault-ing the event in that handler. +export function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + e = {type: e, preventDefault: function() { this.defaultPrevented = true }} + signal(cm, override || e.type, cm, e) + return e_defaultPrevented(e) || e.codemirrorIgnore +} + +export function signalCursorActivity(cm) { + let arr = cm._handlers && cm._handlers.cursorActivity + if (!arr) return + let set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []) + for (let i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) + set.push(arr[i]) +} + +export function hasHandler(emitter, type) { + return getHandlers(emitter, type).length > 0 +} + +// Add on and off methods to a constructor's prototype, to make +// registering events on such objects more convenient. +export function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f)} + ctor.prototype.off = function(type, f) {off(this, type, f)} +} + +// Due to the fact that we still support jurassic IE versions, some +// compatibility wrappers are needed. + +export function e_preventDefault(e) { + if (e.preventDefault) e.preventDefault() + else e.returnValue = false +} +export function e_stopPropagation(e) { + if (e.stopPropagation) e.stopPropagation() + else e.cancelBubble = true +} +export function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false +} +export function e_stop(e) {e_preventDefault(e); e_stopPropagation(e)} + +export function e_target(e) {return e.target || e.srcElement} +export function e_button(e) { + let b = e.which + if (b == null) { + if (e.button & 1) b = 1 + else if (e.button & 2) b = 3 + else if (e.button & 4) b = 2 + } + if (mac && e.ctrlKey && b == 1) b = 3 + return b +} diff --git a/docs/js/node_modules/codemirror/src/util/feature_detection.js b/docs/js/node_modules/codemirror/src/util/feature_detection.js new file mode 100644 index 000000000..c33734ebb --- /dev/null +++ b/docs/js/node_modules/codemirror/src/util/feature_detection.js @@ -0,0 +1,84 @@ +import { elt, range, removeChildren, removeChildrenAndAdd } from "./dom.js" +import { ie, ie_version } from "./browser.js" + +// Detect drag-and-drop +export let dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) return false + let div = elt('div') + return "draggable" in div || "dragDrop" in div +}() + +let zwspSupported +export function zeroWidthElement(measure) { + if (zwspSupported == null) { + let test = elt("span", "\u200b") + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])) + if (measure.firstChild.offsetHeight != 0) + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8) + } + let node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px") + node.setAttribute("cm-text", "") + return node +} + +// Feature-detect IE's crummy client rect reporting for bidi text +let badBidiRects +export function hasBadBidiRects(measure) { + if (badBidiRects != null) return badBidiRects + let txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")) + let r0 = range(txt, 0, 1).getBoundingClientRect() + let r1 = range(txt, 1, 2).getBoundingClientRect() + removeChildren(measure) + if (!r0 || r0.left == r0.right) return false // Safari returns null in some cases (#2780) + return badBidiRects = (r1.right - r0.right < 3) +} + +// See if "".split is the broken IE version, if so, provide an +// alternative way to split lines. +export let splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? string => { + let pos = 0, result = [], l = string.length + while (pos <= l) { + let nl = string.indexOf("\n", pos) + if (nl == -1) nl = string.length + let line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl) + let rt = line.indexOf("\r") + if (rt != -1) { + result.push(line.slice(0, rt)) + pos += rt + 1 + } else { + result.push(line) + pos = nl + 1 + } + } + return result +} : string => string.split(/\r\n?|\n/) + +export let hasSelection = window.getSelection ? te => { + try { return te.selectionStart != te.selectionEnd } + catch(e) { return false } +} : te => { + let range + try {range = te.ownerDocument.selection.createRange()} + catch(e) {} + if (!range || range.parentElement() != te) return false + return range.compareEndPoints("StartToEnd", range) != 0 +} + +export let hasCopyEvent = (() => { + let e = elt("div") + if ("oncopy" in e) return true + e.setAttribute("oncopy", "return;") + return typeof e.oncopy == "function" +})() + +let badZoomedRects = null +export function hasBadZoomedRects(measure) { + if (badZoomedRects != null) return badZoomedRects + let node = removeChildrenAndAdd(measure, elt("span", "x")) + let normal = node.getBoundingClientRect() + let fromRange = range(node, 0, 1).getBoundingClientRect() + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 +} diff --git a/docs/js/node_modules/codemirror/src/util/misc.js b/docs/js/node_modules/codemirror/src/util/misc.js new file mode 100644 index 000000000..3337989bd --- /dev/null +++ b/docs/js/node_modules/codemirror/src/util/misc.js @@ -0,0 +1,168 @@ +export function bind(f) { + let args = Array.prototype.slice.call(arguments, 1) + return function(){return f.apply(null, args)} +} + +export function copyObj(obj, target, overwrite) { + if (!target) target = {} + for (let prop in obj) + if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + target[prop] = obj[prop] + return target +} + +// Counts the column offset in a string, taking tabs into account. +// Used mostly to find indentation. +export function countColumn(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/) + if (end == -1) end = string.length + } + for (let i = startIndex || 0, n = startValue || 0;;) { + let nextTab = string.indexOf("\t", i) + if (nextTab < 0 || nextTab >= end) + return n + (end - i) + n += nextTab - i + n += tabSize - (n % tabSize) + i = nextTab + 1 + } +} + +export class Delayed { + constructor() { + this.id = null + this.f = null + this.time = 0 + this.handler = bind(this.onTimeout, this) + } + onTimeout(self) { + self.id = 0 + if (self.time <= +new Date) { + self.f() + } else { + setTimeout(self.handler, self.time - +new Date) + } + } + set(ms, f) { + this.f = f + const time = +new Date + ms + if (!this.id || time < this.time) { + clearTimeout(this.id) + this.id = setTimeout(this.handler, ms) + this.time = time + } + } +} + +export function indexOf(array, elt) { + for (let i = 0; i < array.length; ++i) + if (array[i] == elt) return i + return -1 +} + +// Number of pixels added to scroller and sizer to hide scrollbar +export let scrollerGap = 30 + +// Returned or thrown by various protocols to signal 'I'm not +// handling this'. +export let Pass = {toString: function(){return "CodeMirror.Pass"}} + +// Reused option objects for setSelection & friends +export let sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"} + +// The inverse of countColumn -- find the offset that corresponds to +// a particular column. +export function findColumn(string, goal, tabSize) { + for (let pos = 0, col = 0;;) { + let nextTab = string.indexOf("\t", pos) + if (nextTab == -1) nextTab = string.length + let skipped = nextTab - pos + if (nextTab == string.length || col + skipped >= goal) + return pos + Math.min(skipped, goal - col) + col += nextTab - pos + col += tabSize - (col % tabSize) + pos = nextTab + 1 + if (col >= goal) return pos + } +} + +let spaceStrs = [""] +export function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " ") + return spaceStrs[n] +} + +export function lst(arr) { return arr[arr.length-1] } + +export function map(array, f) { + let out = [] + for (let i = 0; i < array.length; i++) out[i] = f(array[i], i) + return out +} + +export function insertSorted(array, value, score) { + let pos = 0, priority = score(value) + while (pos < array.length && score(array[pos]) <= priority) pos++ + array.splice(pos, 0, value) +} + +function nothing() {} + +export function createObj(base, props) { + let inst + if (Object.create) { + inst = Object.create(base) + } else { + nothing.prototype = base + inst = new nothing() + } + if (props) copyObj(props, inst) + return inst +} + +let nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/ +export function isWordCharBasic(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) +} +export function isWordChar(ch, helper) { + if (!helper) return isWordCharBasic(ch) + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true + return helper.test(ch) +} + +export function isEmpty(obj) { + for (let n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false + return true +} + +// Extending unicode characters. A series of a non-extending char + +// any number of extending chars is treated as a single unit as far +// as editing and measuring is concerned. This is not fully correct, +// since some scripts/fonts/browsers also treat other configurations +// of code points as a group. +let extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/ +export function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) } + +// Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range. +export function skipExtendingChars(str, pos, dir) { + while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) pos += dir + return pos +} + +// Returns the value from the range [`from`; `to`] that satisfies +// `pred` and is closest to `from`. Assumes that at least `to` +// satisfies `pred`. Supports `from` being greater than `to`. +export function findFirst(pred, from, to) { + // At any point we are certain `to` satisfies `pred`, don't know + // whether `from` does. + let dir = from > to ? -1 : 1 + for (;;) { + if (from == to) return from + let midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF) + if (mid == from) return pred(mid) ? from : to + if (pred(mid)) to = mid + else from = mid + dir + } +} diff --git a/docs/js/node_modules/codemirror/src/util/operation_group.js b/docs/js/node_modules/codemirror/src/util/operation_group.js new file mode 100644 index 000000000..f6815949d --- /dev/null +++ b/docs/js/node_modules/codemirror/src/util/operation_group.js @@ -0,0 +1,72 @@ +import { getHandlers } from "./event.js" + +let operationGroup = null + +export function pushOperation(op) { + if (operationGroup) { + operationGroup.ops.push(op) + } else { + op.ownsGroup = operationGroup = { + ops: [op], + delayedCallbacks: [] + } + } +} + +function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + let callbacks = group.delayedCallbacks, i = 0 + do { + for (; i < callbacks.length; i++) + callbacks[i].call(null) + for (let j = 0; j < group.ops.length; j++) { + let op = group.ops[j] + if (op.cursorActivityHandlers) + while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm) + } + } while (i < callbacks.length) +} + +export function finishOperation(op, endCb) { + let group = op.ownsGroup + if (!group) return + + try { fireCallbacksForOps(group) } + finally { + operationGroup = null + endCb(group) + } +} + +let orphanDelayedCallbacks = null + +// Often, we want to signal events at a point where we are in the +// middle of some work, but don't want the handler to start calling +// other methods on the editor, which might be in an inconsistent +// state or simply not expect any other events to happen. +// signalLater looks whether there are any handlers, and schedules +// them to be executed when the last operation ends, or, if no +// operation is active, when a timeout fires. +export function signalLater(emitter, type /*, values...*/) { + let arr = getHandlers(emitter, type) + if (!arr.length) return + let args = Array.prototype.slice.call(arguments, 2), list + if (operationGroup) { + list = operationGroup.delayedCallbacks + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks + } else { + list = orphanDelayedCallbacks = [] + setTimeout(fireOrphanDelayed, 0) + } + for (let i = 0; i < arr.length; ++i) + list.push(() => arr[i].apply(null, args)) +} + +function fireOrphanDelayed() { + let delayed = orphanDelayedCallbacks + orphanDelayedCallbacks = null + for (let i = 0; i < delayed.length; ++i) delayed[i]() +} diff --git a/docs/js/node_modules/codemirror/theme/3024-day.css b/docs/js/node_modules/codemirror/theme/3024-day.css new file mode 100644 index 000000000..713265530 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/3024-day.css @@ -0,0 +1,41 @@ +/* + + Name: 3024 day + Author: Jan T. Sott (http://github.com/idleberg) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-3024-day.CodeMirror { background: #f7f7f7; color: #3a3432; } +.cm-s-3024-day div.CodeMirror-selected { background: #d6d5d4; } + +.cm-s-3024-day .CodeMirror-line::selection, .cm-s-3024-day .CodeMirror-line > span::selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d6d5d4; } +.cm-s-3024-day .CodeMirror-line::-moz-selection, .cm-s-3024-day .CodeMirror-line > span::-moz-selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d9d9d9; } + +.cm-s-3024-day .CodeMirror-gutters { background: #f7f7f7; border-right: 0px; } +.cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20; } +.cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c; } +.cm-s-3024-day .CodeMirror-linenumber { color: #807d7c; } + +.cm-s-3024-day .CodeMirror-cursor { border-left: 1px solid #5c5855; } + +.cm-s-3024-day span.cm-comment { color: #cdab53; } +.cm-s-3024-day span.cm-atom { color: #a16a94; } +.cm-s-3024-day span.cm-number { color: #a16a94; } + +.cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute { color: #01a252; } +.cm-s-3024-day span.cm-keyword { color: #db2d20; } +.cm-s-3024-day span.cm-string { color: #fded02; } + +.cm-s-3024-day span.cm-variable { color: #01a252; } +.cm-s-3024-day span.cm-variable-2 { color: #01a0e4; } +.cm-s-3024-day span.cm-def { color: #e8bbd0; } +.cm-s-3024-day span.cm-bracket { color: #3a3432; } +.cm-s-3024-day span.cm-tag { color: #db2d20; } +.cm-s-3024-day span.cm-link { color: #a16a94; } +.cm-s-3024-day span.cm-error { background: #db2d20; color: #5c5855; } + +.cm-s-3024-day .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: #a16a94 !important; } diff --git a/docs/js/node_modules/codemirror/theme/3024-night.css b/docs/js/node_modules/codemirror/theme/3024-night.css new file mode 100644 index 000000000..adc5900ad --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/3024-night.css @@ -0,0 +1,39 @@ +/* + + Name: 3024 night + Author: Jan T. Sott (http://github.com/idleberg) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-3024-night.CodeMirror { background: #090300; color: #d6d5d4; } +.cm-s-3024-night div.CodeMirror-selected { background: #3a3432; } +.cm-s-3024-night .CodeMirror-line::selection, .cm-s-3024-night .CodeMirror-line > span::selection, .cm-s-3024-night .CodeMirror-line > span > span::selection { background: rgba(58, 52, 50, .99); } +.cm-s-3024-night .CodeMirror-line::-moz-selection, .cm-s-3024-night .CodeMirror-line > span::-moz-selection, .cm-s-3024-night .CodeMirror-line > span > span::-moz-selection { background: rgba(58, 52, 50, .99); } +.cm-s-3024-night .CodeMirror-gutters { background: #090300; border-right: 0px; } +.cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20; } +.cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855; } +.cm-s-3024-night .CodeMirror-linenumber { color: #5c5855; } + +.cm-s-3024-night .CodeMirror-cursor { border-left: 1px solid #807d7c; } + +.cm-s-3024-night span.cm-comment { color: #cdab53; } +.cm-s-3024-night span.cm-atom { color: #a16a94; } +.cm-s-3024-night span.cm-number { color: #a16a94; } + +.cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute { color: #01a252; } +.cm-s-3024-night span.cm-keyword { color: #db2d20; } +.cm-s-3024-night span.cm-string { color: #fded02; } + +.cm-s-3024-night span.cm-variable { color: #01a252; } +.cm-s-3024-night span.cm-variable-2 { color: #01a0e4; } +.cm-s-3024-night span.cm-def { color: #e8bbd0; } +.cm-s-3024-night span.cm-bracket { color: #d6d5d4; } +.cm-s-3024-night span.cm-tag { color: #db2d20; } +.cm-s-3024-night span.cm-link { color: #a16a94; } +.cm-s-3024-night span.cm-error { background: #db2d20; color: #807d7c; } + +.cm-s-3024-night .CodeMirror-activeline-background { background: #2F2F2F; } +.cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/docs/js/node_modules/codemirror/theme/abcdef.css b/docs/js/node_modules/codemirror/theme/abcdef.css new file mode 100644 index 000000000..cf9353094 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/abcdef.css @@ -0,0 +1,32 @@ +.cm-s-abcdef.CodeMirror { background: #0f0f0f; color: #defdef; } +.cm-s-abcdef div.CodeMirror-selected { background: #515151; } +.cm-s-abcdef .CodeMirror-line::selection, .cm-s-abcdef .CodeMirror-line > span::selection, .cm-s-abcdef .CodeMirror-line > span > span::selection { background: rgba(56, 56, 56, 0.99); } +.cm-s-abcdef .CodeMirror-line::-moz-selection, .cm-s-abcdef .CodeMirror-line > span::-moz-selection, .cm-s-abcdef .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 56, 56, 0.99); } +.cm-s-abcdef .CodeMirror-gutters { background: #555; border-right: 2px solid #314151; } +.cm-s-abcdef .CodeMirror-guttermarker { color: #222; } +.cm-s-abcdef .CodeMirror-guttermarker-subtle { color: azure; } +.cm-s-abcdef .CodeMirror-linenumber { color: #FFFFFF; } +.cm-s-abcdef .CodeMirror-cursor { border-left: 1px solid #00FF00; } + +.cm-s-abcdef span.cm-keyword { color: darkgoldenrod; font-weight: bold; } +.cm-s-abcdef span.cm-atom { color: #77F; } +.cm-s-abcdef span.cm-number { color: violet; } +.cm-s-abcdef span.cm-def { color: #fffabc; } +.cm-s-abcdef span.cm-variable { color: #abcdef; } +.cm-s-abcdef span.cm-variable-2 { color: #cacbcc; } +.cm-s-abcdef span.cm-variable-3, .cm-s-abcdef span.cm-type { color: #def; } +.cm-s-abcdef span.cm-property { color: #fedcba; } +.cm-s-abcdef span.cm-operator { color: #ff0; } +.cm-s-abcdef span.cm-comment { color: #7a7b7c; font-style: italic;} +.cm-s-abcdef span.cm-string { color: #2b4; } +.cm-s-abcdef span.cm-meta { color: #C9F; } +.cm-s-abcdef span.cm-qualifier { color: #FFF700; } +.cm-s-abcdef span.cm-builtin { color: #30aabc; } +.cm-s-abcdef span.cm-bracket { color: #8a8a8a; } +.cm-s-abcdef span.cm-tag { color: #FFDD44; } +.cm-s-abcdef span.cm-attribute { color: #DDFF00; } +.cm-s-abcdef span.cm-error { color: #FF0000; } +.cm-s-abcdef span.cm-header { color: aquamarine; font-weight: bold; } +.cm-s-abcdef span.cm-link { color: blueviolet; } + +.cm-s-abcdef .CodeMirror-activeline-background { background: #314151; } diff --git a/docs/js/node_modules/codemirror/theme/ambiance-mobile.css b/docs/js/node_modules/codemirror/theme/ambiance-mobile.css new file mode 100644 index 000000000..88d332e1a --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/ambiance-mobile.css @@ -0,0 +1,5 @@ +.cm-s-ambiance.CodeMirror { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} diff --git a/docs/js/node_modules/codemirror/theme/ambiance.css b/docs/js/node_modules/codemirror/theme/ambiance.css new file mode 100644 index 000000000..782fca43f --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/ambiance.css @@ -0,0 +1,74 @@ +/* ambiance theme for codemirror */ + +/* Color scheme */ + +.cm-s-ambiance .cm-header { color: blue; } +.cm-s-ambiance .cm-quote { color: #24C2C7; } + +.cm-s-ambiance .cm-keyword { color: #cda869; } +.cm-s-ambiance .cm-atom { color: #CF7EA9; } +.cm-s-ambiance .cm-number { color: #78CF8A; } +.cm-s-ambiance .cm-def { color: #aac6e3; } +.cm-s-ambiance .cm-variable { color: #ffb795; } +.cm-s-ambiance .cm-variable-2 { color: #eed1b3; } +.cm-s-ambiance .cm-variable-3, .cm-s-ambiance .cm-type { color: #faded3; } +.cm-s-ambiance .cm-property { color: #eed1b3; } +.cm-s-ambiance .cm-operator { color: #fa8d6a; } +.cm-s-ambiance .cm-comment { color: #555; font-style:italic; } +.cm-s-ambiance .cm-string { color: #8f9d6a; } +.cm-s-ambiance .cm-string-2 { color: #9d937c; } +.cm-s-ambiance .cm-meta { color: #D2A8A1; } +.cm-s-ambiance .cm-qualifier { color: yellow; } +.cm-s-ambiance .cm-builtin { color: #9999cc; } +.cm-s-ambiance .cm-bracket { color: #24C2C7; } +.cm-s-ambiance .cm-tag { color: #fee4ff; } +.cm-s-ambiance .cm-attribute { color: #9B859D; } +.cm-s-ambiance .cm-hr { color: pink; } +.cm-s-ambiance .cm-link { color: #F4C20B; } +.cm-s-ambiance .cm-special { color: #FF9D00; } +.cm-s-ambiance .cm-error { color: #AF2018; } + +.cm-s-ambiance .CodeMirror-matchingbracket { color: #0f0; } +.cm-s-ambiance .CodeMirror-nonmatchingbracket { color: #f22; } + +.cm-s-ambiance div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15); } +.cm-s-ambiance.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } +.cm-s-ambiance .CodeMirror-line::selection, .cm-s-ambiance .CodeMirror-line > span::selection, .cm-s-ambiance .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-ambiance .CodeMirror-line::-moz-selection, .cm-s-ambiance .CodeMirror-line > span::-moz-selection, .cm-s-ambiance .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } + +/* Editor styling */ + +.cm-s-ambiance.CodeMirror { + line-height: 1.40em; + color: #E6E1DC; + background-color: #202020; + -webkit-box-shadow: inset 0 0 10px black; + -moz-box-shadow: inset 0 0 10px black; + box-shadow: inset 0 0 10px black; +} + +.cm-s-ambiance .CodeMirror-gutters { + background: #3D3D3D; + border-right: 1px solid #4D4D4D; + box-shadow: 0 10px 20px black; +} + +.cm-s-ambiance .CodeMirror-linenumber { + text-shadow: 0px 1px 1px #4d4d4d; + color: #111; + padding: 0 5px; +} + +.cm-s-ambiance .CodeMirror-guttermarker { color: #aaa; } +.cm-s-ambiance .CodeMirror-guttermarker-subtle { color: #111; } + +.cm-s-ambiance .CodeMirror-cursor { border-left: 1px solid #7991E8; } + +.cm-s-ambiance .CodeMirror-activeline-background { + background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031); +} + +.cm-s-ambiance.CodeMirror, +.cm-s-ambiance .CodeMirror-gutters { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAQAAAAHUWYVAABFFUlEQVQYGbzBCeDVU/74/6fj9HIcx/FRHx9JCFmzMyGRURhLZIkUsoeRfUjS2FNDtr6WkMhO9sm+S8maJfu+Jcsg+/o/c+Z4z/t97/vezy3z+z8ekGlnYICG/o7gdk+wmSHZ1z4pJItqapjoKXWahm8NmV6eOTbWUOp6/6a/XIg6GQqmenJ2lDHyvCFZ2cBDbmtHA043VFhHwXxClWmeYAdLhV00Bd85go8VmaFCkbVkzlQENzfBDZ5gtN7HwF0KDrTwJ0dypSOzpaKCMwQHKTIreYIxlmhXTzTWkVm+LTynZhiSBT3RZQ7aGfjGEd3qyXQ1FDymqbKxpspERQN2MiRjNZlFFQXfCNFm9nM1zpAsoYjmtRTc5ajwuaXc5xrWskT97RaKzAGe5ARHhVUsDbjKklziiX5WROcJwSNCNI+9w1Jwv4Zb2r7lCMZ4oq5C0EdTx+2GzNuKpJ+iFf38JEWkHJn9DNF7mmBDITrWEg0VWL3pHU20tSZnuqWu+R3BtYa8XxV1HO7GyD32UkOpL/yDloINFTmvtId+nmAjxRw40VMwVKiwrKLE4bK5UOVntYwhOcSSXKrJHKPJedocpGjVz/ZMIbnYUPB10/eKCrs5apqpgVmWzBYWpmtKHecJPjaUuEgRDDaU0oZghCJ6zNMQ5ZhDYx05r5v2muQdM0EILtXUsaKiQX9WMEUotagQzFbUNN6NUPC2nm5pxEWGCjMc3GdJHjSU2kORLK/JGSrkfGEIjncU/CYUnOipoYemwj8tST9NsJmB7TUVXtbUtXATJVZXBMvYeTXJfobgJUPmGMP/yFaWonaa6BcFO3nqcIqCozSZoZoSr1g4zJOzuyGnxTEX3lUEJ7WcZgme8ddaWvWJo2AJR9DZU3CUIbhCSG6ybSwN6qtJVnCU2svDTP2ZInOw2cBTrqtQahtNZn9NcJ4l2NaSmSkkP1noZWnVwkLmdUPOwLZEwy2Z3S3R+4rIG9hcbpPXHFVWcQdZkn2FOta3cKWQnNRC5g1LsJah4GCzSVsKnCOY5OAFRTBekyyryeyilhFKva75r4Mc0aWanGEaThcy31s439KKxTzJYY5WTHPU1FtIHjQU3Oip4xlNzj/lBw23dYZVliQa7WAXf4shetcQfatI+jWRDBPmyNeW6A1P5kdDgyYJlba0BIM8BZu1JfrFwItyjcAMR3K0BWOIrtMEXyhyrlVEx3ui5dUBjmB/Q3CXW85R4mBD0s7B+4q5tKUjOlb9qqmhi5AZ6GFIC5HXtOobdYGlVdMVbNJ8toNTFcHxnoL+muBagcctjWnbNMuR00uI7nQESwg5q2qqrKWIfrNUmeQocY6HuyxJV02wj36w00yhpmUFenv4p6fUkZYqLyuinx2RGOjhCXYyJF84oiU00YMOOhhquNdfbOB7gU88pY4xJO8LVdp6/q2voeB4R04vIdhSE40xZObx1HGGJ/ja0LBthFInKaLPPFzuCaYaoj8JjPME8yoyxo6zlBqkiUZYgq00OYMswbWO5NGmq+xhipxHLRW29ARjNKXO0wRnear8XSg4XFPLKEPUS1GqvyLwiuBUoa7zpZ0l5xxFwWmWZC1H5h5FwU8eQ7K+g8UcVY6TMQreVQT/8uQ8Z+ALIXnSEa2pYZQneE9RZbSBNYXfWYJzW/h/4j4Dp1tYVcFIC5019Vyi4ThPqSFCzjGWaHQTBU8q6vrVwgxP9Lkm840imWKpcLCjYTtrKuwvsKSnrvHCXGkSMk9p6lhckfRpIeis+N2PiszT+mFLspyGleUhDwcLrZqmyeylxwjBcKHEapqkmyangyLZRVOijwOtCY5SsG5zL0OwlCJ4y5KznF3EUNDDrinwiyLZRzOXtlBbK5ITHFGLp8Q0R6ab6mS7enI2cFrxOyHvOCFaT1HThS1krjCwqWeurCkk+willhCC+RSZnRXBiZaC5RXRIZYKp2lyfrHwiKPKR0JDzrdU2EFgpidawlFDR6FgXUMNa+g1FY3bUQh2cLCwosRdnuQTS/S+JVrGLeWIvtQUvONJxlqSQYYKpwoN2kaocLjdVsis4Mk80ESF2YpSkzwldjHkjFCUutI/r+EHDU8oCs6yzL3PhWiEooZdFMkymlas4AcI3KmoMMNSQ3tHzjGWCrcJJdYyZC7QFGwjRL9p+MrRkAGWzIaWCn9W0F3TsK01c2ZvQw0byvxuQU0r1lM0qJO7wW0kRIMdDTtXEdzi4VIh+EoIHm0mWtAtpCixlabgn83fKTI7anJe9ST7WIK1DMGpQmYeA58ImV6ezOGOzK2Kgq01pd60cKWiUi9Lievb/0vIDPHQ05Kzt4ddPckQBQtoaurjyHnek/nKzpQLrVgKPjIkh2v4uyezpv+Xoo7fPFXaGFp1vaLKxQ4uUpQQS5VuQs7BCq4xRJv7fwpVvvFEB3j+620haOuocqMhWd6TTPAEx+mdFNGHdranFe95WrWmIvlY4F1Dle2ECgc6cto7SryuqGGGha0tFQ5V53migUKmg6XKAo4qS3mik+0OZpAhOLeZKicacgaYcyx5hypYQE02ZA4xi/pNhOQxR4klNKyqacj+mpxnLTnnGSo85++3ZCZq6lrZkXlGEX3o+C9FieccJbZWVFjC0Yo1FZnJhoYMFoI1hEZ9r6hwg75HwzBNhbZCdJEfJwTPGzJvaKImw1yYX1HDAmpXR+ZJQ/SmgqMNVQb5vgamGwLtt7VwvP7Qk1xpiM5x5Cyv93E06MZmgs0Nya2azIKOYKCGBQQW97RmhKNKF02JZqHEJ4o58qp7X5EcZmc56trXEqzjCBZ1MFGR87Ql2tSTs6CGxS05PTzRQorkbw7aKoKXFDXsYW42VJih/q+FP2BdTzDTwVqOYB13liM50vG7wy28qagyuIXMeQI/Oqq8bcn5wJI50xH00CRntyfpL1T4hydYpoXgNiFzoIUTDZnLNRzh4TBHwbYGDvZkxmlyJloyr6tRihpeUG94GnKtIznREF0tzJG/OOr73JBcrSh1k6WuTprgLU+mnSGnv6Zge0NNz+kTDdH8nuAuTdJDCNb21LCiIuqlYbqGzT3RAoZofQfjFazkqeNWdYaGvYTM001EW2oKPvVk1ldUGSgUtHFwjKM1h9jnFcmy5lChoLNaQMGGDsYbKixlaMBmmsx1QjCfflwTfO/gckW0ruZ3jugKR3R5W9hGUWqCgxuFgsuaCHorotGKzGaeZB9DMsaTnKCpMtwTvOzhYk0rdrArKCqcaWmVk1+F372ur1YkKxgatI8Qfe1gIX9wE9FgS8ESmuABIXnRUbCapcKe+nO7slClSZFzpV/LkLncEb1qiO42fS3R855Su2mCLh62t1SYZZYVmKwIHjREF2uihTzB20JOkz7dkxzYQnK0UOU494wh+VWRc6Un2kpTaVgLDFEkJ/uhzRcI0YKGgpGWOlocBU/a4fKoJ/pEaNV6jip3+Es9VXY078rGnmAdf7t9ylPXS34RBSuYPs1UecZTU78WanhBCHpZ5sAoTz0LGZKjPf9TRypqWEiTvOFglL1fCEY3wY/++rbk7C8bWebA6p6om6PgOL2kp44TFJlVNBXae2rqqdZztOJpT87GQsE9jqCPIe9VReZuQ/CIgacsyZdCpIScSYqcZk8r+nsyCzhyfhOqHGOIvrLknC8wTpFcaYiGC/RU1NRbUeUpocQOnkRpGOrIOcNRx+1uA0UrzhSSt+VyS3SJpnFWkzNDqOFGIWcfR86DnmARTQ1HKIL33ExPiemeOhYSSjzlSUZZuE4TveoJLnBUOFof6KiysCbnAEcZgcUNTDOwkqWu3RWtmGpZwlHhJENdZ3miGz0lJlsKnjbwqSHQjpxnFDlTLLwqJPMZMjd7KrzkSG7VsxXBZE+F8YZkb01Oe00yyRK9psh5SYh29ySPKBo2ylNht7ZkZnsKenjKNJu9PNEyZpaCHv4Kt6RQsLvAVp7M9kIimmCUwGeWqLMmGuIotYMmWNpSahkhZw9FqZsVnKJhsjAHvtHMsTM9fCI06Dx/u3vfUXCqfsKRc4oFY2jMsoo/7DJDwZ1CsIKnJu+J9ldkpmiCxQx1rWjI+T9FwcWWzOuaYH0Hj7klNRVWEQpmaqosakiGNTFHdjS/qnUdmf0NJW5xsL0HhimCCZZSRzmSPTXJQ4aaztAwtZnoabebJ+htCaZ7Cm535ByoqXKbX1WRc4Eh2MkRXWzImVc96Cj4VdOKVxR84VdQsIUM8Psoou2byVHyZFuq7O8otbSQ2UAoeEWTudATLGSpZzVLlXVkPU2Jc+27lsw2jmg5T5VhbeE3BT083K9WsTTkFU/Osi0rC5lRlpwRHUiesNS0sOvmqGML1aRbPAxTJD9ZKtxuob+hhl8cwYGWpJ8nub7t5p6coYbMovZ1BTdaKn1jYD6h4GFDNFyT/Kqe1XCXphXHOKLZmuRSRdBPEfVUXQzJm5YGPGGJdvAEr7hHNdGZnuBvrpciGmopOLf5N0uVMy0FfYToJk90uUCbJupaVpO53UJXR2bVpoU00V2KOo4zMFrBd0Jtz2pa0clT5Q5L8IpQ177mWQejPMEJhuQjS10ref6HHjdEhy1P1EYR7GtO0uSsKJQYLiTnG1rVScj5lyazpqWGl5uBbRWl7m6ixGOOnEsMJR7z8J0n6KMnCdxhiNYQCoZ6CmYLnO8omC3MkW3bktlPmEt/VQQHejL3+dOE5FlPdK/Mq8hZxxJtLyRrepLThYKbLZxkSb5W52vYxNOaOxUF0yxMUPwBTYqCzy01XayYK0sJyWBLqX0MwU5CzoymRzV0EjjeUeLgDpTo6ij42ZAzvD01dHUUTPLU96MdLbBME8nFBn7zJCMtJcZokn8YoqU0FS5WFKyniHobguMcmW8N0XkWZjkyN3hqOMtS08r+/xTBwpZSZ3qiVRX8SzMHHjfUNFjgHEPmY9PL3ykEzxkSre/1ZD6z/NuznuB0RcE1TWTm9zRgfUWVJiG6yrzgmWPXC8EAR4Wxhlad0ZbgQyEz3pG5RVEwwDJH2mgKpjcTiCOzn1lfUWANFbZ2BA8balnEweJC9J0iuaeZoI+ippFCztEKVvckR2iice1JvhVytrQwUAZpgsubCPaU7xUe9vWnaOpaSBEspalykhC9bUlOMpT42ZHca6hyrqKmw/wMR8H5ZmdFoBVJb03O4UL0tSNnvIeRmkrLWqrs78gcrEn2tpcboh0UPOW3UUR9PMk4T4nnNKWmCjlrefhCwxRNztfmIQVdDElvS4m1/WuOujoZCs5XVOjtKPGokJzsYCtFYoWonSPT21DheU/wWhM19FcElwqNGOsp9Q8N/cwXaiND1MmeL1Q5XROtYYgGeFq1aTMsoMmcrKjQrOFQTQ1fmBYhmW6o8Jkjc7iDJRTBIo5kgJD5yMEYA3srCg7VFKwiVJkmRCc5ohGOKhsYMn/XBLdo5taZjlb9YAlGWRimqbCsoY7HFAXLa5I1HPRxMMsQDHFkWtRNniqT9UEeNjcE7RUlrCJ4R2CSJuqlKHWvJXjAUNcITYkenuBRB84TbeepcqTj3zZyFJzgYQdHnqfgI0ddUwS6GqWpsKWhjq9cV0vBAEMN2znq+EBfIWT+pClYw5xsTlJU6GeIBsjGmmANTzJZiIYpgrM0Oa8ZMjd7NP87jxhqGOhJlnQtjuQpB+8aEE00wZFznSJPyHxgH3HkPOsJFvYk8zqCHzTs1BYOa4J3PFU+UVRZxlHDM4YavlNUuMoRveiZA2d7grMNc2g+RbSCEKzmgYsUmWmazFJyoiOZ4KnyhKOGRzWJa0+moyV4TVHDzn51Awtqaphfk/lRQ08FX1iiqxTB/kLwd0VynKfEvI6cd4XMV5bMhZ7gZUWVzYQ6Nm2BYzxJbw3bGthEUUMfgbGeorae6DxHtJoZ6alhZ0+ytiVoK1R4z5PTrOECT/SugseEOlb1MMNR4VRNcJy+V1Hg9ONClSZFZjdHlc6W6FBLdJja2MC5hhpu0DBYEY1TFGwiFAxRRCsYkiM9JRb0JNMVkW6CZYT/2EiTGWmo8k+h4FhDNE7BvppoTSFnmCV5xZKzvcCdDo7VVPnIU+I+Rc68juApC90MwcFCsJ5hDqxgScYKreruyQwTqrzoqDCmhWi4IbhB0Yrt3RGa6GfDv52rKXWhh28dyZaWUvcZeMTBaZoSGyiCtRU5J8iviioHaErs7Jkj61syVzTTgOcUOQ8buFBTYWdL5g3T4qlpe0+wvD63heAXRfCCIed9RbCsp2CiI7raUOYOTU13N8PNHvpaGvayo4a3LLT1lDrVEPT2zLUlheB1R+ZTRfKWJ+dcocLJfi11vyJ51lLqJ0WD7tRwryezjiV5W28uJO9qykzX8JDe2lHl/9oyBwa2UMfOngpXCixvKdXTk3wrsKmiVYdZIqsoWEERjbcUNDuiaQomGoIbFdEHmsyWnuR+IeriKDVLnlawlyNHKwKlSU631PKep8J4Q+ayjkSLKYLhalNHlYvttb6fHm0p6OApsZ4l2VfdqZkjuysy6ysKLlckf1KUutCTs39bmCgEyyoasIWlVaMF7mgmWtBT8Kol5xpH9IGllo8cJdopcvZ2sImlDmMIbtDk3KIpeNiS08lQw11NFPTwVFlPP6pJ2gvRfI7gQUfmNAtf6Gs0wQxDsKGlVBdF8rCa3jzdwMaGHOsItrZk7hAyOzpK9VS06j5F49b0VNGOOfKs3lDToMsMBe9ZWtHFEgxTJLs7qrygKZjUnmCYoeAqeU6jqWuLJup4WghOdvCYJnrSkSzoyRkm5M2StQwVltPkfCAk58tET/CSg+8MUecmotMEnhBKfWBIZsg2ihruMJQaoIm+tkTLKEqspMh00w95gvFCQRtDwTT1gVDDSEVdlwqZfxoQRbK0g+tbiBZxzKlpnpypejdDwTaeOvorMk/IJE10h9CqRe28hhLbe0pMsdSwv4ZbhKivo2BjDWfL8UKJgeavwlwb5KlwhyE4u4XkGE2ytZCznKLCDZZq42VzT8HLCrpruFbIfOIINmh/qCdZ1ZBc65kLHR1Bkyf5zn6pN3SvGKIlFNGplhrO9QSXanLOMQTLCa0YJCRrCZm/CZmrLTm7WzCK4GJDiWUdFeYx1LCFg3NMd0XmCuF3Y5rITLDUsYS9zoHVzwnJoYpSTQoObyEzr4cFBNqYTopoaU/wkyLZ2lPhX/5Y95ulxGTV7KjhWrOZgl8MyUUafjYraNjNU1N3IWcjT5WzWqjwtoarHSUObGYO3GCJZpsBlnJGPd6ZYLyl1GdCA2625IwwJDP8GUKymbzuyPlZlvTUsaUh5zFDhRWFzPKKZLAlWdcQbObgF9tOqOsmB1dqcqYJmWstFbZRRI9poolmqiLnU0POvxScpah2iSL5UJNzgScY5+AuIbpO0YD3NCW+dLMszFSdFCWGqG6eVq2uYVNDdICGD6W7EPRWZEY5gpsE9rUkS3mijzzJnm6UpUFXG1hCUeVoS5WfNcFpblELL2qqrCvMvRfd45oalvKU2tiQ6ePJOVMRXase9iTtLJztPxJKLWpo2CRDcJwn2sWSLKIO1WQWNTCvpVUvOZhgSC40JD0dOctaSqzkCRbXsKlb11Oip6PCJ0IwSJM31j3akRxlP7Rwn6aGaUL0qiLnJkvB3xWZ2+Q1TfCwpQH3G0o92UzmX4o/oJNQMMSQc547wVHhdk+VCw01DFYEnTxzZKAm74QmeNNR1w6WzEhNK15VJzuCdxQ53dRUDws5KvwgBMOEgpcVNe0hZI6RXT1Jd0cyj5nsaEAHgVmGaJIlWdsc5Ui2ElrRR6jrRAttNMEAIWrTDFubkZaok7/AkzfIwfuWVq0jHzuCK4QabtLUMVPB3kJ0oyHTSVFlqMALilJf2Rf8k5aaHtMfayocLBS8L89oKoxpJvnAkDPa0qp5DAUTHKWmCcnthlou8iCKaFFLHWcINd1nyIwXqrSxMNmSs6KmoL2QrKuWtlQ5V0120xQ5vRyZS1rgFkWwhiOwiuQbR0OOVhQM9iS3tiXp4RawRPMp5tDletOOBL95MpM01dZTBM9pkn5qF010rIeHFcFZhmSGpYpTsI6nwhqe5C9ynhlpp5ophuRb6WcJFldkVnVEwwxVfrVkvnWUuNLCg5bgboFHPDlDPDmnK7hUrWiIbjadDclujlZcaokOFup4Ri1kacV6jmrrK1hN9bGwpKEBQ4Q6DvIUXOmo6U5LqQM6EPyiKNjVkPnJkDPNEaxhiFay5ExW1NXVUGqcpYYdPcGiCq7z/TSlbhL4pplWXKd7NZO5QQFrefhRQW/NHOsqcIglc4UhWklR8K0QzbAw08CBDnpbgqXdeD/QUsM4RZXDFBW6WJKe/mFPdH0LtBgiq57wFLzlyQzz82qYx5D5WJP5yVJDW01BfyHnS6HKO/reZqId1WGa4Hkh2kWodJ8i6KoIPlAj2hPt76CzXsVR6koPRzWTfKqIentatYpQw2me4AA3y1Kind3SwoOKZDcFXTwl9tWU6mfgRk9d71sKtlNwrjnYw5tC5n5LdKiGry3JKNlHEd3oaMCFHrazBPMp/uNJ+V7IudcSbeOIdjUEdwl0VHCOZo5t6YluEuaC9mQeMgSfOyKnYGFHcIeQ84yQWbuJYJpZw5CzglDH7gKnWqqM9ZTaXcN0TeYhR84eQtJT76JJ1lREe7WnnvsMmRc9FQ7SBBM9mV3lCUdmHk/S2RAMt0QjFNFqQpWjDPQ01DXWUdDBkXziKPjGEP3VP+zIWU2t7im41FOloyWzn/L6dkUy3VLDaZ6appgDLHPjJEsyvJngWEPUyVBiAaHCTEXwrLvSEbV1e1gKJniicWorC1MUrVjB3uDhJE/wgSOzk1DXpk0k73qCM8xw2UvD5kJmDUfOomqMpWCkJRlvKXGmoeBm18USjVIk04SClxTB6YrgLAPLWYK9HLUt5cmc0vYES8GnTeRc6skZbQkWdxRsIcyBRzx1DbTk9FbU0caTPOgJHhJKnOGIVhQqvKmo0llRw9sabrZkDtdg3PqaKi9oatjY8B+G371paMg6+mZFNNtQ04mWBq3rYLOmtWWQp8KJnpy9DdFensyjdqZ+yY40VJlH8wcdLzC8PZnvHMFUTZUrDTkLyQaGus5X5LzpYAf3i+e/ZlhqGqWhh6Ou6xTR9Z6oi5AZZtp7Mj2EEm8oSpxiYZCHU/1fbGdNNNRRoZMhmilEb2gqHOEJDtXkHK/JnG6IrvbPCwV3NhONVdS1thBMs1T4QOBcTWa2IzhMk2nW5Kyn9tXUtpv9RsG2msxk+ZsQzRQacJncpgke0+T8y5Fzj8BiGo7XlJjaTIlpQs7KFjpqGnKuoyEPeIKnFMkZHvopgh81ySxNFWvJWcKRs70j2FOT012IllEEO1n4pD1513Yg2ssQPOThOkvyrqHUdEXOSEsihmBbTbKX1kLBPWqWkLOqJbjB3GBIZmoa8qWl4CG/iZ7oiA72ZL7TJNeZUY7kFQftDcHHluBzRbCegzMtrRjVQpX2lgoPKKLJAkcbMl01XK2p7yhL8pCBbQ3BN2avJgKvttcrWDK3CiUOVxQ8ZP+pqXKyIxnmBymCg5vJjNfkPK4+c8cIfK8ocVt7kmfd/I5SR1hKvCzUtb+lhgc00ZaO6CyhIQP1Uv4yIZjload72PXX0OIJvnFU+0Zf6MhsJwTfW0r0UwQfW4LNLZl5HK261JCZ4qnBaAreVAS3WrjV0LBnNDUNNDToCEeFfwgcb4gOEqLRhirWkexrCEYKVV711DLYEE1XBEsp5tpTGjorkomKYF9FDXv7fR3BGwbettSxnyL53MBPjsxDZjMh+VUW9NRxq1DhVk+FSxQcaGjV9Pawv6eGByw5qzoy7xk4RsOShqjJwWKe/1pEEfzkobeD/dQJmpqedcyBTy2sr4nGNRH0c0SPWTLrqAc0OQcb/gemKgqucQT7ySWKCn2EUotoCvpZct7RO2sy/QW0IWcXd7pQRQyZVwT2USRO87uhjioTLKV2brpMUcMQRbKH/N2T+UlTpaMls6cmc6CCNy3JdYYSUzzJQ4oSD3oKLncULOiJvjBEC2oqnCJkJluCYy2ZQ5so9YYlZ1VLlQU1mXEW1jZERwj/MUSRc24TdexlqLKfQBtDTScJUV8FszXBEY5ktpD5Ur9hYB4Nb1iikw3JoYpkKX+RodRKFt53MMuRnKSpY31PwYaGaILh3wxJGz9TkTPEETxoCWZrgvOlmyMzxFEwVJE5xZKzvyJ4WxEc16Gd4Xe3Weq4XH2jKRikqOkGQ87hQnC7wBmGYLAnesX3M+S87eFATauuN+Qcrh7xIxXJbUIdMw3JGE3ylCWzrieaqCn4zhGM19TQ3z1oH1AX+pWEqIc7wNGAkULBo/ZxRaV9NNyh4Br3rCHZzbzmSfawBL0dNRwpW1kK9mxPXR9povcdrGSZK9c2k0xwFGzjuniCtRSZCZ6ccZ7gaktmgAOtKbG/JnOkJrjcQTdFMsxRQ2cLY3WTIrlCw1eWKn8R6pvt4GFDso3QoL4a3nLk3G6JrtME3dSenpx7PNFTmga0EaJTLQ061sEeQoWXhSo9LTXsaSjoJQRXeZLtDclbCrYzfzHHeaKjHCVOUkQHO3JeEepr56mhiyaYYKjjNU+Fed1wS5VlhWSqI/hYUdDOkaxiKehoyOnrCV5yBHtbWFqTHCCwtpDcYolesVR5yUzTZBb3RNMd0d6WP+SvhuBmRcGxnuQzT95IC285cr41cLGQ6aJJhmi4TMGempxeimBRQw1tFKV+8jd6KuzoSTqqDxzRtpZkurvKEHxlqXKRIjjfUNNXQsNOsRScoWFLT+YeRZVD3GRN0MdQcKqQjHDMrdGGVu3iYJpQx3WGUvfbmxwFfR20WBq0oYY7LMFhhgYtr8jpaEnaOzjawWWaTP8mMr0t/EPDPoqcnxTBI5o58L7uoWnMrpoqPwgVrlAUWE+V+TQl9rawoyP6QGAlQw2TPRX+YSkxyBC8Z6jhHkXBgQL7WII3DVFnRfCrBfxewv9D6xsyjys4VkhWb9pUU627JllV0YDNHMku/ldNMMXDEo4aFnAkk4U6frNEU4XgZUPmEKHUl44KrzmYamjAbh0JFvGnaTLPu1s9jPCwjFpYiN7z1DTOk/nc07CfDFzmCf7i+bfNHXhDtLeBXzTBT5rkMvWOIxpl4EMh2LGJBu2syDnAEx2naEhHDWMMzPZEhygyS1mS5RTJr5ZkoKbEUoYqr2kqdDUE8ztK7OaIntJkFrIECwv8LJTaVx5XJE86go8dFeZ3FN3rjabCAYpoYEeC9zzJVULBbmZhDyd7ko09ydpNZ3nm2Kee4FPPXHnYEF1nqOFEC08LUVcDvYXkJHW8gTaKCk9YGOeIJhqiE4ToPEepdp7IWFjdwnWaufGMwJJCMtUTTBBK9BGCOy2tGGrJTHIwyEOzp6aPzNMOtlZkDvcEWpP5SVNhfkvDxhmSazTJXYrM9U1E0xwFVwqZQwzJxw6+kGGGUj2FglGGmnb1/G51udRSMNlTw6GGnCcUwVcOpmsqTHa06o72sw1RL02p9z0VbnMLOaIX3QKaYKSCFQzBKEUNHTSc48k53RH9wxGMtpQa5KjjW0W0n6XCCCG4yxNNdhQ4R4l1Ff+2sSd6UFHiIEOyqqFgT01mEUMD+joy75jPhOA+oVVLm309FR4yVOlp4RhLiScNmSmaYF5Pw0STrOIoWMSR2UkRXOMp+M4SHW8o8Zoi6OZgjKOaFar8zZDzkWzvKOjkKBjmCXby8JahhjXULY4KlzgKLvAwxVGhvyd4zxB1d9T0piazmKLCVZY5sKiD0y2ZSYrkUEPUbIk+dlQ4SJHTR50k1DPaUWIdTZW9NJwnJMOECgd7ou/MnppMJ02O1VT4Wsh85MnZzcFTngpXGKo84qmwgKbCL/orR/SzJ2crA+t6Mp94KvxJUeIbT3CQu1uIdlQEOzlKfS3UMcrTiFmOuroocrZrT2AcmamOKg8YomeEKm/rlT2sociMaybaUlFhuqHCM2qIJ+rg4EcDFymiDSxzaHdPcpE62pD5kyM5SBMoA1PaUtfIthS85ig1VPiPPYXgYEMNk4Qq7TXBgo7oT57gPUdwgCHzhIVFPFU6OYJzHAX9m5oNrVjeE61miDrqQ4VSa1oiURTsKHC0IfjNwU2WzK6eqK8jWln4g15TVBnqmDteCJ501PGAocJhhqjZdtBEB6lnhLreFJKxmlKbeGrqLiSThVIbCdGzloasa6lpMQXHCME2boLpJgT7yWaemu6wBONbqGNVRS0PKIL7LckbjmQtR7K8I5qtqel+T/ChJTNIKLjdUMNIRyvOEko9YYl2cwQveBikCNawJKcLBbc7+JM92mysNvd/Fqp8a0k6CNEe7cnZrxlW0wQXaXjaktnRwNOGZKYiONwS7a1JVheq3WgJHlQUGKHKmp4KAxXR/ULURcNgoa4zhKSLpZR3kxRRb0NmD0OFn+UCS7CzI1nbP6+o4x47QZE5xRCt3ZagnYcvmpYQktXdk5YKXTzBC57kKEe0VVuiSYqapssMS3C9p2CKkHOg8B8Pa8p5atrIw3qezIWanMGa5HRDNF6RM9wcacl0N+Q8Z8hsIkSnaIIdHRUOEebAPy1zbCkhM062FCJtif7PU+UtoVXzWKqM1PxXO8cfdruhFQ/a6x3JKYagvVDhQEtNiyiiSQ7OsuRsZUku0CRNDs4Sog6KKjsZgk2bYJqijgsEenoKeniinRXBn/U3lgpPdyDZynQx8IiioMnCep5Ky8mjGs6Wty0l1hUQTcNWswS3WRp2kCNZwJG8omG8JphPUaFbC8lEfabwP7VtM9yoaNCAjpR41VNhrD9LkbN722v0CoZMByFzhaW+MyzRYEWFDQwN2M4/JiT76PuljT3VU/A36eaIThb+R9oZGOAJ9tewkgGvqOMNRWYjT/Cwu99Q8LqDE4TgbLWxJ1jaDDAERsFOFrobgjUsBScaguXU8kKm2RL19tRypSHnHNlHiIZqgufs4opgQdVdwxBNNFBR6kVFqb8ogimOzB6a6HTzrlDHEpYaxjiiA4TMQobkDg2vejjfwJGWmnbVFAw3H3hq2NyQfG7hz4aC+w3BbwbesG0swYayvpAs6++Ri1Vfzx93mFChvyN5xVHTS+0p9aqCAxyZ6ZacZyw5+7uuQkFPR9DDk9NOiE7X1PCYJVjVUqq7JlrHwWALF5nfHNGjApdpqgzx5OwilDhCiDYTgnc9waGW4BdLNNUQvOtpzDOWHDH8D7TR/A/85KljEQu3NREc4Pl/6B1Hhc8Umb5CsKMmGC9EPcxoT2amwHNCmeOEnOPbklnMkbOgIvO5UMOpQrS9UGVdt6iH/fURjhI/WOpaW9OKLYRod6HCUEdOX000wpDZQ6hwg6LgZfOqo1RfT/CrJzjekXOGhpc1VW71ZLbXyyp+93ILbC1kPtIEYx0FIx1VDrLoVzXRKRYWk809yYlC9ImcrinxtabKnzRJk3lAU1OLEN1j2zrYzr2myHRXJFf4h4QKT1qSTzTB5+ZNTzTRkAxX8FcLV2uS8eoQQ2aAkFzvCM72sJIcJET3WPjRk5wi32uSS9rfZajpWEvj9hW42F4o5NytSXYy8IKHay10VYdrcl4SkqscrXpMwyGOgtkajheSxdQqmpxP1L3t4R5PqasFnrQEjytq6qgp9Y09Qx9o4S1FzhUCn1kyHSzBWLemoSGvOqLNhZyBjmCaAUYpMgt4Ck7wBBMMwWKWgjsUwTaGVsxWC1mYoKiyqqeGKYqonSIRQ3KIkHO0pmAxTdBHkbOvfllfr+AA+7gnc50huVKYK393FOyg7rbPO/izI7hE4CnHHHnJ0ogNPRUGeUpsrZZTBJcrovUcJe51BPsr6GkJdhCCsZ6aTtMEb2pqWkqeVtDXE/QVggsU/Nl86d9RMF3DxvZTA58agu810RWawCiSzzXBeU3MMW9oyJUedvNEvQyNu1f10BSMddR1vaLCYpYa/mGocLSiYDcLbQz8aMn5iyF4xBNMs1P0QEOV7o5gaWGuzSeLue4tt3ro7y4Tgm4G/mopdZgl6q0o6KzJWE3mMksNr3r+a6CbT8g5wZNzT9O7fi/zpaOmnz3BRoqos+tv9zMbdpxsqDBOEewtJLt7cg5wtKKbvldpSzRRCD43VFheCI7yZLppggMVBS/KMAdHODJvOwq2NQSbKKKPLdFWQs7Fqo+mpl01JXYRgq8dnGLhTiFzqmWsUMdpllZdbKlyvSdYxhI9YghOtxR8LgSLWHK62mGGVoxzBE8LNWzqH9CUesQzFy5RQzTc56mhi6fgXEWwpKfE5Z7M05ZgZUPmo6auiv8YKzDYwWBLMErIbKHJvOwIrvEdhOBcQ9JdU1NHQ7CXn2XIDFBKU2WAgcX9UAUzDXWd5alwuyJ41Z9rjKLCL4aCp4WarhPm2rH+SaHUYE001JDZ2ZAzXPjdMpZWvC9wmqIB2lLhQ01D5jO06hghWMndbM7yRJMsoCj1vYbnFQVrW9jak3OlEJ3s/96+p33dEPRV5GxiqaGjIthUU6FFEZyqCa5qJrpBdzSw95IUnOPIrCUUjRZQFrbw5PR0R1qiYx3cb6nrWUMrBmmiBQxVHtTew5ICP/ip6g4hed/Akob/32wvBHsIOX83cI8hGeNeNPCIkPmXe8fPKx84OMSRM1MTdXSwjCZ4S30jVGhvqTRak/OVhgGazHuOCud5onEO1lJr6ecVyaOK6H7zqlBlIaHE0oroCgfvGJIdPcmfLNGLjpz7hZwZQpUbFME0A1cIJa7VNORkgfsMBatbKgwwJM9bSvQXeNOvbIjelg6WWvo5kvbKaJJNHexkKNHL9xRyFlH8Ti2riB5wVPhUk7nGkJnoCe428LR/wRGdYIlmWebCyxou1rCk4g/ShugBDX0V0ZQWkh0dOVsagkM0yV6OoLd5ye+pRlsCr0n+KiQrGuq5yJDzrTAXHtLUMduTDBVKrSm3eHL+6ijxhFDX9Z5gVU/wliHYTMiMFpKLNMEywu80wd3meoFmt6VbRMPenhrOc6DVe4pgXU8DnnHakLOIIrlF4FZPIw6R+zxBP0dyq6OOZ4Q5sLKCcz084ok+VsMMyQhNZmmBgX5xIXOEJTmi7VsGTvMTNdHHhpzdbE8Du2oKxgvBqQKdDDnTFOylCFaxR1syz2iqrOI/FEpNc3C6f11/7+ASS6l2inq2ciTrCCzgyemrCL5SVPjQkdPZUmGy2c9Sw9FtR1sS30RmsKPCS4rkIC/2U0MduwucYolGaPjKEyhzmiPYXagyWbYz8LWBDdzRimAXzxx4z8K9hpzlhLq+NiQ97HuKorMUfK/OVvC2JfiHUPCQI/q7J2gjK+tTDNxkCc4TMssqCs4TGtLVwQihyoAWgj9bosU80XGW6Ac9TJGziaUh5+hnFcHOnlaM1iRn29NaqGENTTTSUHCH2tWTeV0osUhH6psuVLjRUmGWhm6OZEshGeNowABHcJ2Bpy2ZszRcKkRXd2QuKVEeXnbfaEq825FguqfgfE2whlChSRMdron+LATTPQ2Z369t4B9C5gs/ylzv+CMmepIDPclFQl13W0rspPd1JOcbghGOEutqCv5qacURQl3dDKyvyJlqKXGPgcM9FfawJAMVmdcspcYKOZc4GjDYkFlK05olNMHyHn4zFNykyOxt99RkHlfwmiHo60l2EKI+mhreEKp080Tbug08BVPcgoqC5zWt+NLDTZ7oNSF51N1qie7Va3uCCwyZbkINf/NED6jzOsBdZjFN8oqG3wxVunqCSYYKf3EdhJyf9YWGf7tRU2oH3VHgPr1fe5J9hOgHd7xQ0y7qBwXr23aGErP0cm64JVjZwsOGqL+mhNgZmhJLW2oY4UhedsyBgzrCKrq7BmcpNVhR6jBPq64Vgi+kn6XE68pp8J5/+0wRHGOpsKenQn9DZntPzjRLZpDAdD2fnSgkG9tmIXnUwQ6WVighs7Yi2MxQ0N3CqYaCXkJ0oyOztMDJjmSSpcpvlrk0RMMOjmArQ04PRV1DO1FwhCVaUVPpKUM03JK5SxPsIWRu8/CGHi8UHChiqGFDTbSRJWeYUDDcH6vJWUxR4k1FXbMUwV6e4AJFXS8oMqsZKqzvYQ9DDQdZckY4aGsIhtlubbd2r3j4QBMoTamdPZk7O/Bf62lacZwneNjQoGcdVU7zJOd7ghsUHOkosagic6cnWc8+4gg285R6zZP5s1/LUbCKIznTwK36PkdwlOrl4U1LwfdCCa+IrvFkmgw1PCAUXKWo0sURXWcI2muKJlgyFzhynCY4RBOsqCjoI1R5zREco0n2Vt09BQtYSizgKNHfUmUrQ5UOCh51BFcLmY7umhYqXKQomOop8bUnWNNQcIiBcYaC6xzMNOS8JQQfeqKBmmglB+97ok/lfk3ygaHSyZaCRTzRxQo6GzLfa2jWBPepw+UmT7SQEJyiyRkhBLMVOfcoMjcK0eZChfUNzFAUzCsEN5vP/X1uP/n/aoMX+K+nw/Hjr/9xOo7j7Pju61tLcgvJpTWXNbfN5jLpi6VfCOviTktKlFusQixdEKWmEBUKNaIpjZRSSOXSgzaaKLdabrm1/9nZ+/f+vd/vz/v9+Xy+zZ7PRorYoZqyLrCwQdEAixxVOEXNNnjX2nUSRlkqGmWowk8lxR50JPy9Bo6qJXaXwNvREBvnThPEPrewryLhcAnj5WE15Fqi8W7R1sAuEu86S4ENikItFN4xkv9Af4nXSnUVcLiA9xzesFpivRRVeFKtsMRaKBhuSbjOELnAUtlSQUpXgdfB4Z1oSbnFEetbQ0IrAe+Y+pqnDcEJFj6S8LDZzZHwY4e3XONNlARraomNEt2bkvGsosA3ioyHm+6jCMbI59wqt4eeara28IzEmyPgoRaUOEDhTVdEJhmCoTWfC0p8aNkCp0oYqih2iqGi4yXeMkOsn4LdLLnmKfh/YogjNsPebeFGR4m9BJHLzB61XQ3BtpISfS2FugsK9FAtLWX1dCRcrCnUp44CNzuCowUZmxSRgYaE6Za0W2u/E7CVXCiI/UOR8aAm1+OSyE3mOUcwyc1zBBeoX1kiKy0Zfxck1Gsyulti11i83QTBF5Kg3pDQThFMVHiPSlK+0cSedng/VaS8bOZbtsBcTcZAR8JP5KeqQ1OYKAi20njdNNRpgnsU//K+JnaXJaGTomr7aYIphoRn9aeShJWKEq9LcozSF7QleEfDI5LYm5bgVkFkRwVDBCVu0DDIkGupo8TZBq+/pMQURYErJQmPKGKjNDkWOLx7Jd5QizdUweIaKrlP7SwJDhZvONjLkOsBBX9UpGxnydhXkfBLQ8IxgojQbLFnJf81JytSljclYYyEFyx0kVBvKWOFJmONpshGAcsduQY5giVNCV51eOdJYo/pLhbvM0uDHSevNKRcrKZIqnCtJeEsO95RoqcgGK4ocZcho1tTYtcZvH41pNQ7vA0WrhIfOSraIIntIAi+NXWCErdbkvrWwjRLrt0NKUdL6KSOscTOdMSOUtBHwL6OLA0vNSdynaWQEnCpIvKaIrJJEbvHkmuNhn6OjM8VkSGSqn1uYJCGHnq9I3aLhNME3t6GjIkO7xrNFumpyTNX/NrwX7CrIRiqqWijI9JO4d1iieykyfiposQIQ8YjjsjlBh6oHWbwRjgYJQn2NgSnNycmJAk3NiXhx44Sxykihxm8ybUwT1OVKySc7vi3OXVkdBJ4AyXBeksDXG0IhgtYY0lY5ahCD0ehborIk5aUWRJviMA7Xt5kyRjonrXENkm8yYqgs8VzgrJmClK20uMM3jRJ0FiQICQF9hdETlLQWRIb5ki6WDfWRPobvO6a4GP5mcOrNzDFELtTkONLh9dXE8xypEg7z8A9jkhrQ6Fhjlg/QVktJXxt4WXzT/03Q8IaQWSqIuEvloQ2mqC9Jfi7wRul4RX3pSPlzpoVlmCtI2jvKHCFhjcM3sN6lqF6HxnKelLjXWbwrpR4xzuCrTUZx2qq9oAh8p6ixCUGr78g8oyjRAtB5CZFwi80VerVpI0h+IeBxa6Zg6kWvpDHaioYYuEsRbDC3eOmC2JvGYLeioxGknL2UATNJN6hmtj1DlpLvDVmocYbrGCVJKOrg4X6DgddLA203BKMFngdJJFtFd7vJLm6KEpc5yjQrkk7M80SGe34X24nSex1Ra5Omgb71JKyg8SrU3i/kARKwWpH0kOGhKkObyfd0ZGjvyXlAkVZ4xRbYJ2irFMkFY1SwyWxr2oo4zlNiV+7zmaweFpT4kR3kaDAFW6xpSqzJay05FtYR4HmZhc9UxKbbfF2V8RG1MBmSaE+kmC6JnaRXK9gsiXhJHl/U0qM0WTcbyhwkYIvFGwjSbjfwhiJt8ZSQU+Bd5+marPMOkVkD0muxYLIfEuhh60x/J92itguihJSEMySVPQnTewnEm+620rTQEMsOfo4/kP/0ARvWjitlpSX7GxBgcMEsd3EEeYWvdytd+Saawi6aCIj1CkGb6Aj9rwhx16Cf3vAwFy5pyLhVonXzy51FDpdEblbkdJbUcEPDEFzQ8qNmhzzLTmmKWKbFCXeEuRabp6rxbvAtLF442QjQ+wEA9eL1xSR7Q0JXzlSHjJ4exq89yR0laScJ/FW6z4a73pFMEfDiRZvuvijIt86RaSFOl01riV2mD1UEvxGk/Geg5aWwGki1zgKPG9J2U8PEg8qYvMsZeytiTRXBMslCU8JSlxi8EabjwUldlDNLfzTUmCgxWsjqWCOHavYAqsknKFIO0yQ61VL5AVFxk6WhEaCAkdJgt9aSkzXlKNX2jEa79waYuc7gq0N3GDJGCBhoiTXUEPsdknCUE1CK0fwsiaylSF2uiDyO4XX3pFhNd7R4itFGc0k/ElBZwWvq+GC6szVeEoS/MZ+qylwpKNKv9Z469UOjqCjwlusicyTxG6VpNxcQ8IncoR4RhLbR+NdpGGmJWOcIzJGUuKPGpQg8rrG21dOMqQssJQ4RxH5jaUqnZuQ0F4Q+cjxLwPtpZbIAk3QTJHQWBE5S1BokoVtDd6lhqr9UpHSUxMcIYl9pojsb8h4SBOsMQcqvOWC2E8EVehqiJ1hrrAEbQxeK0NGZ0Gkq+guSRgniM23bIHVkqwx4hiHd7smaOyglyIyQuM978j4VS08J/A2G1KeMBRo4fBaSNhKUEZfQewVQ/C1I+MgfbEleEzCUw7mKXI0M3hd1EESVji8x5uQ41nxs1q4RMJCCXs7Iq9acpxn22oSDnQ/sJTxsCbHIYZiLyhY05TY0ZLIOQrGaSJDDN4t8pVaIrsqqFdEegtizc1iTew5Q4ayBDMUsQMkXocaYkc0hZua412siZ1rSXlR460zRJ5SlHGe5j801RLMlJTxtaOM3Q1pvxJ45zUlWFD7rsAbpfEm1JHxG0eh8w2R7QQVzBUw28FhFp5QZzq8t2rx2joqulYTWSuJdTYfWwqMFMcovFmSyJPNyLhE4E10pHzYjOC3huArRa571ZsGajQpQx38SBP5pyZB6lMU3khDnp0MBV51BE9o2E+TY5Ml2E8S7C0o6w1xvCZjf0HkVEHCzFoyNmqC+9wdcqN+Tp7jSDheE9ws8Y5V0NJCn2bk2tqSY4okdrEhx1iDN8cSudwepWmAGXKcJXK65H9to8jYQRH7SBF01ESUJdd0TayVInaWhLkOjlXE5irKGOnI6GSWGCJa482zBI9rCr0jyTVcEuzriC1vcr6mwFGSiqy5zMwxBH/TJHwjSPhL8+01kaaSUuMFKTcLEvaUePcrSmwn8DZrgikWb7CGPxkSjhQwrRk57tctmxLsb9sZvL9LSlyuSLlWkqOjwduo8b6Uv1DkmudIeFF2dHCgxVtk8dpIvHpBxhEOdhKk7OLIUSdJ+cSRY57B+0DgGUUlNfpthTfGkauzxrvTsUUaCVhlKeteTXCoJDCa2NOKhOmC4G1H8JBd4OBZReSRGkqcb/CO1PyLJTLB4j1q8JYaIutEjSLX8YKM+a6phdMsdLFUoV5RTm9JSkuDN8WcIon0NZMNZWh1q8C7SJEwV5HxrmnnTrf3KoJBlmCYI2ilSLlfEvlE4011NNgjgthzEua0oKK7JLE7HZHlEl60BLMVFewg4EWNt0ThrVNEVkkiTwpKXSWJzdRENgvKGq4IhjsiezgSFtsfCUq8qki5S1LRQeYQQ4nemmCkImWMw3tFUoUBZk4NOeZYEp4XRKTGa6wJjrWNHBVJR4m3FCnbuD6aak2WsMTh3SZImGCIPKNgsDpVwnsa70K31lCFJZYcwwSMFcQulGTsZuEaSdBXkPGZhu0FsdUO73RHjq8MPGGIfaGIbVTk6iuI3GFgucHrIQkmWSJdBd7BBu+uOryWAhY7+Lki9rK5wtEQzWwvtbqGhIMFwWRJsElsY4m9IIg9L6lCX0VklaPAYkfkZEGDnOWowlBJjtMUkcGK4Lg6EtoZInMUBVYLgn0UsdmCyCz7gIGHFfk+k1QwTh5We7A9x+IdJ6CvIkEagms0hR50eH9UnTQJ+2oiKyVlLFUE+8gBGu8MQ3CppUHesnjTHN4QB/UGPhCTHLFPHMFrCqa73gqObUJGa03wgbhHkrCfpEpzNLE7JDS25FMKhlhKKWKfCgqstLCPu1zBXy0J2ztwjtixBu8UTRn9LVtkmCN2iyFhtME70JHRQ1KVZXqKI/KNIKYMCYs1GUMEKbM1bKOI9LDXC7zbHS+bt+1MTWS9odA9DtrYtpbImQJ2VHh/lisEwaHqUk1kjKTAKknkBEXkbkdMGwq0dnhzLJF3NJH3JVwrqOB4Sca2hti75nmJN0WzxS6UxDYoEpxpa4htVlRjkYE7DZGzJVU72uC9IyhQL4i8YfGWSYLLNcHXloyz7QhNifmKSE9JgfGmuyLhc403Xm9vqcp6gXe3xuuv8F6VJNxkyTHEkHG2g0aKXL0MsXc1bGfgas2//dCONXiNLCX+5mB7eZIl1kHh7ajwpikyzlUUWOVOsjSQlsS+M0R+pPje/dzBXRZGO0rMtgQrLLG9VSu9n6CMXS3BhwYmSoIBhsjNBmZbgusE9BCPCP5triU4VhNbJfE+swSP27aayE8tuTpYYjtrYjMVGZdp2NpS1s6aBnKSHDsbKuplKbHM4a0wMFd/5/DmGyKrJSUaW4IBrqUhx0vyfzTBBLPIUcnZdrAkNsKR0sWRspumSns6Ch0v/qqIbBYUWKvPU/CFoyrDJGwSNFhbA/MlzKqjrO80hRbpKx0Jewsi/STftwGSlKc1JZyAzx05dhLEdnfQvhZOqiHWWEAHC7+30FuRcZUgaO5gpaIK+xsiHRUsqaPElTV40xQZQ107Q9BZE1nryDVGU9ZSQ47bmhBpLcYpUt7S+xuK/FiT8qKjwXYw5ypS2iuCv7q1gtgjhuBuB8LCFY5cUuCNtsQOFcT+4Ih9JX+k8Ea6v0iCIRZOtCT0Et00JW5UeC85Cg0ScK0k411HcG1zKtre3SeITBRk7WfwDhEvaYLTHP9le0m8By0JDwn4TlLW/aJOvGHxdjYUes+ScZigCkYQdNdEOhkiezgShqkx8ueKjI8lDfK2oNiOFvrZH1hS+tk7NV7nOmLHicGWEgubkXKdwdtZknCLJXaCpkrjZBtLZFsDP9CdxWsSr05Sxl6CMmoFbCOgryX40uDtamB7SVmXW4Ihlgpmq+00tBKUUa83WbjLUNkzDmY7cow1JDygyPGlhgGKYKz4vcV7QBNbJIgM11TUqZaMdwTeSguH6rOaw1JRKzaaGyxVm2EJ/uCIrVWUcZUkcp2grMsEjK+DMwS59jQk3Kd6SEq1d0S6uVmO4Bc1lDXTUcHjluCXEq+1OlBDj1pi9zgiXxnKuE0SqTXwhqbETW6RggMEnGl/q49UT2iCzgJvRwVXS2K/d6+ZkyUl7jawSVLit46EwxVljDZwoSQ20sDBihztHfk2yA8NVZghiXwrYHQdfKAOtzsayjhY9bY0yE2CWEeJ9xfzO423xhL5syS2TFJofO2pboHob0nY4GiAgRrvGQEDa/FWSsoaaYl0syRsEt3kWoH3B01shCXhTUWe9w3Bt44SC9QCh3eShQctwbaK2ApLroGCMlZrYqvlY3qYhM0aXpFkPOuoqJ3Dm6fxXrGwVF9gCWZagjPqznfkuMKQ8DPTQRO8ZqG1hPGKEm9IgpGW4DZDgTNriTxvFiq+Lz+0cKfp4wj6OCK9JSnzNSn9LFU7UhKZZMnYwcJ8s8yRsECScK4j5UOB95HFO0CzhY4xJxuCix0lDlEUeMdS6EZBkTsUkZ4K74dugyTXS7aNgL8aqjDfkCE0ZbwkCXpaWCKhl8P7VD5jxykivSyxyZrYERbe168LYu9ZYh86IkscgVLE7tWPKmJv11CgoyJltMEbrohtVAQfO4ImltiHEroYEs7RxAarVpY8AwXMcMReFOTYWe5iiLRQxJ5Q8DtJ8LQhWOhIeFESPGsILhbNDRljNbHzNRlTFbk2S3L0NOS6V1KFJYKUbSTcIIhM0wQ/s2TM0SRMNcQmSap3jCH4yhJZKSkwyRHpYYgsFeQ4U7xoCB7VVOExhXepo9ABBsYbvGWKXPME3lyH95YioZ0gssQRWWbI+FaSMkXijZXwgiTlYdPdkNLaETxlyDVIwqeaEus0aTcYcg0RVOkpR3CSJqIddK+90JCxzsDVloyrFd5ZAr4TBKfaWa6boEA7C7s6EpYaeFPjveooY72mjIccLHJ9HUwVlDhKkmutJDJBwnp1rvulJZggKDRfbXAkvC/4l3ozQOG9a8lxjx0i7nV4jSXc7vhe3OwIxjgSHjdEhhsif9YkPGlus3iLFDnWOFhtCZbJg0UbQcIaR67JjthoCyMEZRwhiXWyxO5QxI6w5NhT4U1WsJvDO60J34fW9hwzwlKij6ZAW9ne4L0s8C6XeBMEkd/LQy1VucBRot6QMlbivaBhoBgjqGiCJNhsqVp/S2SsG6DIONCR0dXhvWbJ+MRRZJkkuEjgDXJjFQW6SSL7GXK8Z2CZg7cVsbWGoKmEpzQ5elpiy8Ryg7dMkLLUEauzeO86CuwlSOlgYLojZWeJ9xM3S1PWfEfKl5ISLQ0MEKR8YOB2QfCxJBjrKPCN4f9MkaSsqoVXJBmP7EpFZ9UQfOoOFwSzBN4MQ8LsGrymlipcJQhmy0GaQjPqCHaXRwuCZwRbqK2Fg9wlClZqYicrIgMdZfxTQ0c7TBIbrChxmuzoKG8XRaSrIhhiyNFJkrC7oIAWMEOQa5aBekPCRknCo4IKPrYkvCDI8aYmY7WFtprgekcJZ3oLIqssCSMtFbQTJKwXYy3BY5oCh2iKPCpJOE+zRdpYgi6O2KmOAgvVCYaU4ySRek1sgyFhJ403QFHiVEmJHwtybO1gs8Hr5+BETQX3War0qZngYGgtVZtoqd6vFSk/UwdZElYqyjrF4HXUeFspIi9IGKf4j92pKGAdCYMVsbcV3kRF0N+R8LUd5PCsIGWoxDtBkCI0nKofdJQxT+LtZflvuc8Q3CjwWkq8KwUpHzkK/NmSsclCL0nseQdj5FRH5CNHSgtLiW80Of5HU9Hhlsga9bnBq3fEVltKfO5IaSTmGjjc4J0otcP7QsJUSQM8pEj5/wCuUuC2DWz8AAAAAElFTkSuQmCC"); +} diff --git a/docs/js/node_modules/codemirror/theme/base16-dark.css b/docs/js/node_modules/codemirror/theme/base16-dark.css new file mode 100644 index 000000000..026a81689 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/base16-dark.css @@ -0,0 +1,38 @@ +/* + + Name: Base16 Default Dark + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-base16-dark.CodeMirror { background: #151515; color: #e0e0e0; } +.cm-s-base16-dark div.CodeMirror-selected { background: #303030; } +.cm-s-base16-dark .CodeMirror-line::selection, .cm-s-base16-dark .CodeMirror-line > span::selection, .cm-s-base16-dark .CodeMirror-line > span > span::selection { background: rgba(48, 48, 48, .99); } +.cm-s-base16-dark .CodeMirror-line::-moz-selection, .cm-s-base16-dark .CodeMirror-line > span::-moz-selection, .cm-s-base16-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(48, 48, 48, .99); } +.cm-s-base16-dark .CodeMirror-gutters { background: #151515; border-right: 0px; } +.cm-s-base16-dark .CodeMirror-guttermarker { color: #ac4142; } +.cm-s-base16-dark .CodeMirror-guttermarker-subtle { color: #505050; } +.cm-s-base16-dark .CodeMirror-linenumber { color: #505050; } +.cm-s-base16-dark .CodeMirror-cursor { border-left: 1px solid #b0b0b0; } + +.cm-s-base16-dark span.cm-comment { color: #8f5536; } +.cm-s-base16-dark span.cm-atom { color: #aa759f; } +.cm-s-base16-dark span.cm-number { color: #aa759f; } + +.cm-s-base16-dark span.cm-property, .cm-s-base16-dark span.cm-attribute { color: #90a959; } +.cm-s-base16-dark span.cm-keyword { color: #ac4142; } +.cm-s-base16-dark span.cm-string { color: #f4bf75; } + +.cm-s-base16-dark span.cm-variable { color: #90a959; } +.cm-s-base16-dark span.cm-variable-2 { color: #6a9fb5; } +.cm-s-base16-dark span.cm-def { color: #d28445; } +.cm-s-base16-dark span.cm-bracket { color: #e0e0e0; } +.cm-s-base16-dark span.cm-tag { color: #ac4142; } +.cm-s-base16-dark span.cm-link { color: #aa759f; } +.cm-s-base16-dark span.cm-error { background: #ac4142; color: #b0b0b0; } + +.cm-s-base16-dark .CodeMirror-activeline-background { background: #202020; } +.cm-s-base16-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/docs/js/node_modules/codemirror/theme/base16-light.css b/docs/js/node_modules/codemirror/theme/base16-light.css new file mode 100644 index 000000000..1d5f582f6 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/base16-light.css @@ -0,0 +1,38 @@ +/* + + Name: Base16 Default Light + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-base16-light.CodeMirror { background: #f5f5f5; color: #202020; } +.cm-s-base16-light div.CodeMirror-selected { background: #e0e0e0; } +.cm-s-base16-light .CodeMirror-line::selection, .cm-s-base16-light .CodeMirror-line > span::selection, .cm-s-base16-light .CodeMirror-line > span > span::selection { background: #e0e0e0; } +.cm-s-base16-light .CodeMirror-line::-moz-selection, .cm-s-base16-light .CodeMirror-line > span::-moz-selection, .cm-s-base16-light .CodeMirror-line > span > span::-moz-selection { background: #e0e0e0; } +.cm-s-base16-light .CodeMirror-gutters { background: #f5f5f5; border-right: 0px; } +.cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142; } +.cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0; } +.cm-s-base16-light .CodeMirror-linenumber { color: #b0b0b0; } +.cm-s-base16-light .CodeMirror-cursor { border-left: 1px solid #505050; } + +.cm-s-base16-light span.cm-comment { color: #8f5536; } +.cm-s-base16-light span.cm-atom { color: #aa759f; } +.cm-s-base16-light span.cm-number { color: #aa759f; } + +.cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute { color: #90a959; } +.cm-s-base16-light span.cm-keyword { color: #ac4142; } +.cm-s-base16-light span.cm-string { color: #f4bf75; } + +.cm-s-base16-light span.cm-variable { color: #90a959; } +.cm-s-base16-light span.cm-variable-2 { color: #6a9fb5; } +.cm-s-base16-light span.cm-def { color: #d28445; } +.cm-s-base16-light span.cm-bracket { color: #202020; } +.cm-s-base16-light span.cm-tag { color: #ac4142; } +.cm-s-base16-light span.cm-link { color: #aa759f; } +.cm-s-base16-light span.cm-error { background: #ac4142; color: #505050; } + +.cm-s-base16-light .CodeMirror-activeline-background { background: #DDDCDC; } +.cm-s-base16-light .CodeMirror-matchingbracket { color: #f5f5f5 !important; background-color: #6A9FB5 !important} diff --git a/docs/js/node_modules/codemirror/theme/bespin.css b/docs/js/node_modules/codemirror/theme/bespin.css new file mode 100644 index 000000000..60913ba93 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/bespin.css @@ -0,0 +1,34 @@ +/* + + Name: Bespin + Author: Mozilla / Jan T. Sott + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-bespin.CodeMirror {background: #28211c; color: #9d9b97;} +.cm-s-bespin div.CodeMirror-selected {background: #36312e !important;} +.cm-s-bespin .CodeMirror-gutters {background: #28211c; border-right: 0px;} +.cm-s-bespin .CodeMirror-linenumber {color: #666666;} +.cm-s-bespin .CodeMirror-cursor {border-left: 1px solid #797977 !important;} + +.cm-s-bespin span.cm-comment {color: #937121;} +.cm-s-bespin span.cm-atom {color: #9b859d;} +.cm-s-bespin span.cm-number {color: #9b859d;} + +.cm-s-bespin span.cm-property, .cm-s-bespin span.cm-attribute {color: #54be0d;} +.cm-s-bespin span.cm-keyword {color: #cf6a4c;} +.cm-s-bespin span.cm-string {color: #f9ee98;} + +.cm-s-bespin span.cm-variable {color: #54be0d;} +.cm-s-bespin span.cm-variable-2 {color: #5ea6ea;} +.cm-s-bespin span.cm-def {color: #cf7d34;} +.cm-s-bespin span.cm-error {background: #cf6a4c; color: #797977;} +.cm-s-bespin span.cm-bracket {color: #9d9b97;} +.cm-s-bespin span.cm-tag {color: #cf6a4c;} +.cm-s-bespin span.cm-link {color: #9b859d;} + +.cm-s-bespin .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} +.cm-s-bespin .CodeMirror-activeline-background { background: #404040; } diff --git a/docs/js/node_modules/codemirror/theme/blackboard.css b/docs/js/node_modules/codemirror/theme/blackboard.css new file mode 100644 index 000000000..b6eaedb18 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/blackboard.css @@ -0,0 +1,32 @@ +/* Port of TextMate's Blackboard theme */ + +.cm-s-blackboard.CodeMirror { background: #0C1021; color: #F8F8F8; } +.cm-s-blackboard div.CodeMirror-selected { background: #253B76; } +.cm-s-blackboard .CodeMirror-line::selection, .cm-s-blackboard .CodeMirror-line > span::selection, .cm-s-blackboard .CodeMirror-line > span > span::selection { background: rgba(37, 59, 118, .99); } +.cm-s-blackboard .CodeMirror-line::-moz-selection, .cm-s-blackboard .CodeMirror-line > span::-moz-selection, .cm-s-blackboard .CodeMirror-line > span > span::-moz-selection { background: rgba(37, 59, 118, .99); } +.cm-s-blackboard .CodeMirror-gutters { background: #0C1021; border-right: 0; } +.cm-s-blackboard .CodeMirror-guttermarker { color: #FBDE2D; } +.cm-s-blackboard .CodeMirror-guttermarker-subtle { color: #888; } +.cm-s-blackboard .CodeMirror-linenumber { color: #888; } +.cm-s-blackboard .CodeMirror-cursor { border-left: 1px solid #A7A7A7; } + +.cm-s-blackboard .cm-keyword { color: #FBDE2D; } +.cm-s-blackboard .cm-atom { color: #D8FA3C; } +.cm-s-blackboard .cm-number { color: #D8FA3C; } +.cm-s-blackboard .cm-def { color: #8DA6CE; } +.cm-s-blackboard .cm-variable { color: #FF6400; } +.cm-s-blackboard .cm-operator { color: #FBDE2D; } +.cm-s-blackboard .cm-comment { color: #AEAEAE; } +.cm-s-blackboard .cm-string { color: #61CE3C; } +.cm-s-blackboard .cm-string-2 { color: #61CE3C; } +.cm-s-blackboard .cm-meta { color: #D8FA3C; } +.cm-s-blackboard .cm-builtin { color: #8DA6CE; } +.cm-s-blackboard .cm-tag { color: #8DA6CE; } +.cm-s-blackboard .cm-attribute { color: #8DA6CE; } +.cm-s-blackboard .cm-header { color: #FF6400; } +.cm-s-blackboard .cm-hr { color: #AEAEAE; } +.cm-s-blackboard .cm-link { color: #8DA6CE; } +.cm-s-blackboard .cm-error { background: #9D1E15; color: #F8F8F8; } + +.cm-s-blackboard .CodeMirror-activeline-background { background: #3C3636; } +.cm-s-blackboard .CodeMirror-matchingbracket { outline:1px solid grey;color:white !important; } diff --git a/docs/js/node_modules/codemirror/theme/cobalt.css b/docs/js/node_modules/codemirror/theme/cobalt.css new file mode 100644 index 000000000..bbbda3b54 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/cobalt.css @@ -0,0 +1,25 @@ +.cm-s-cobalt.CodeMirror { background: #002240; color: white; } +.cm-s-cobalt div.CodeMirror-selected { background: #b36539; } +.cm-s-cobalt .CodeMirror-line::selection, .cm-s-cobalt .CodeMirror-line > span::selection, .cm-s-cobalt .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99); } +.cm-s-cobalt .CodeMirror-line::-moz-selection, .cm-s-cobalt .CodeMirror-line > span::-moz-selection, .cm-s-cobalt .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99); } +.cm-s-cobalt .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } +.cm-s-cobalt .CodeMirror-guttermarker { color: #ffee80; } +.cm-s-cobalt .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-cobalt .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-cobalt span.cm-comment { color: #08f; } +.cm-s-cobalt span.cm-atom { color: #845dc4; } +.cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; } +.cm-s-cobalt span.cm-keyword { color: #ffee80; } +.cm-s-cobalt span.cm-string { color: #3ad900; } +.cm-s-cobalt span.cm-meta { color: #ff9d00; } +.cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; } +.cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def, .cm-s-cobalt .cm-type { color: white; } +.cm-s-cobalt span.cm-bracket { color: #d8d8d8; } +.cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; } +.cm-s-cobalt span.cm-link { color: #845dc4; } +.cm-s-cobalt span.cm-error { color: #9d1e15; } + +.cm-s-cobalt .CodeMirror-activeline-background { background: #002D57; } +.cm-s-cobalt .CodeMirror-matchingbracket { outline:1px solid grey;color:white !important; } diff --git a/docs/js/node_modules/codemirror/theme/colorforth.css b/docs/js/node_modules/codemirror/theme/colorforth.css new file mode 100644 index 000000000..19095e41d --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/colorforth.css @@ -0,0 +1,33 @@ +.cm-s-colorforth.CodeMirror { background: #000000; color: #f8f8f8; } +.cm-s-colorforth .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } +.cm-s-colorforth .CodeMirror-guttermarker { color: #FFBD40; } +.cm-s-colorforth .CodeMirror-guttermarker-subtle { color: #78846f; } +.cm-s-colorforth .CodeMirror-linenumber { color: #bababa; } +.cm-s-colorforth .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-colorforth span.cm-comment { color: #ededed; } +.cm-s-colorforth span.cm-def { color: #ff1c1c; font-weight:bold; } +.cm-s-colorforth span.cm-keyword { color: #ffd900; } +.cm-s-colorforth span.cm-builtin { color: #00d95a; } +.cm-s-colorforth span.cm-variable { color: #73ff00; } +.cm-s-colorforth span.cm-string { color: #007bff; } +.cm-s-colorforth span.cm-number { color: #00c4ff; } +.cm-s-colorforth span.cm-atom { color: #606060; } + +.cm-s-colorforth span.cm-variable-2 { color: #EEE; } +.cm-s-colorforth span.cm-variable-3, .cm-s-colorforth span.cm-type { color: #DDD; } +.cm-s-colorforth span.cm-property {} +.cm-s-colorforth span.cm-operator {} + +.cm-s-colorforth span.cm-meta { color: yellow; } +.cm-s-colorforth span.cm-qualifier { color: #FFF700; } +.cm-s-colorforth span.cm-bracket { color: #cc7; } +.cm-s-colorforth span.cm-tag { color: #FFBD40; } +.cm-s-colorforth span.cm-attribute { color: #FFF700; } +.cm-s-colorforth span.cm-error { color: #f00; } + +.cm-s-colorforth div.CodeMirror-selected { background: #333d53; } + +.cm-s-colorforth span.cm-compilation { background: rgba(255, 255, 255, 0.12); } + +.cm-s-colorforth .CodeMirror-activeline-background { background: #253540; } diff --git a/docs/js/node_modules/codemirror/theme/darcula.css b/docs/js/node_modules/codemirror/theme/darcula.css new file mode 100644 index 000000000..2ec81a355 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/darcula.css @@ -0,0 +1,53 @@ +/** + Name: IntelliJ IDEA darcula theme + From IntelliJ IDEA by JetBrains + */ + +.cm-s-darcula { font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif;} +.cm-s-darcula.CodeMirror { background: #2B2B2B; color: #A9B7C6; } + +.cm-s-darcula span.cm-meta { color: #BBB529; } +.cm-s-darcula span.cm-number { color: #6897BB; } +.cm-s-darcula span.cm-keyword { color: #CC7832; line-height: 1em; font-weight: bold; } +.cm-s-darcula span.cm-def { color: #A9B7C6; font-style: italic; } +.cm-s-darcula span.cm-variable { color: #A9B7C6; } +.cm-s-darcula span.cm-variable-2 { color: #A9B7C6; } +.cm-s-darcula span.cm-variable-3 { color: #9876AA; } +.cm-s-darcula span.cm-type { color: #AABBCC; font-weight: bold; } +.cm-s-darcula span.cm-property { color: #FFC66D; } +.cm-s-darcula span.cm-operator { color: #A9B7C6; } +.cm-s-darcula span.cm-string { color: #6A8759; } +.cm-s-darcula span.cm-string-2 { color: #6A8759; } +.cm-s-darcula span.cm-comment { color: #61A151; font-style: italic; } +.cm-s-darcula span.cm-link { color: #CC7832; } +.cm-s-darcula span.cm-atom { color: #CC7832; } +.cm-s-darcula span.cm-error { color: #BC3F3C; } +.cm-s-darcula span.cm-tag { color: #629755; font-weight: bold; font-style: italic; text-decoration: underline; } +.cm-s-darcula span.cm-attribute { color: #6897bb; } +.cm-s-darcula span.cm-qualifier { color: #6A8759; } +.cm-s-darcula span.cm-bracket { color: #A9B7C6; } +.cm-s-darcula span.cm-builtin { color: #FF9E59; } +.cm-s-darcula span.cm-special { color: #FF9E59; } +.cm-s-darcula span.cm-matchhighlight { color: #FFFFFF; background-color: rgba(50, 89, 48, .7); font-weight: normal;} +.cm-s-darcula span.cm-searching { color: #FFFFFF; background-color: rgba(61, 115, 59, .7); font-weight: normal;} + +.cm-s-darcula .CodeMirror-cursor { border-left: 1px solid #A9B7C6; } +.cm-s-darcula .CodeMirror-activeline-background { background: #323232; } +.cm-s-darcula .CodeMirror-gutters { background: #313335; border-right: 1px solid #313335; } +.cm-s-darcula .CodeMirror-guttermarker { color: #FFEE80; } +.cm-s-darcula .CodeMirror-guttermarker-subtle { color: #D0D0D0; } +.cm-s-darcula .CodeMirrir-linenumber { color: #606366; } +.cm-s-darcula .CodeMirror-matchingbracket { background-color: #3B514D; color: #FFEF28 !important; font-weight: bold; } + +.cm-s-darcula div.CodeMirror-selected { background: #214283; } + +.CodeMirror-hints.darcula { + font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; + color: #9C9E9E; + background-color: #3B3E3F !important; +} + +.CodeMirror-hints.darcula .CodeMirror-hint-active { + background-color: #494D4E !important; + color: #9C9E9E !important; +} diff --git a/docs/js/node_modules/codemirror/theme/dracula.css b/docs/js/node_modules/codemirror/theme/dracula.css new file mode 100644 index 000000000..253133efe --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/dracula.css @@ -0,0 +1,40 @@ +/* + + Name: dracula + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme) + +*/ + + +.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters { + background-color: #282a36 !important; + color: #f8f8f2 !important; + border: none; +} +.cm-s-dracula .CodeMirror-gutters { color: #282a36; } +.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; } +.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; } +.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } +.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-dracula span.cm-comment { color: #6272a4; } +.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; } +.cm-s-dracula span.cm-number { color: #bd93f9; } +.cm-s-dracula span.cm-variable { color: #50fa7b; } +.cm-s-dracula span.cm-variable-2 { color: white; } +.cm-s-dracula span.cm-def { color: #50fa7b; } +.cm-s-dracula span.cm-operator { color: #ff79c6; } +.cm-s-dracula span.cm-keyword { color: #ff79c6; } +.cm-s-dracula span.cm-atom { color: #bd93f9; } +.cm-s-dracula span.cm-meta { color: #f8f8f2; } +.cm-s-dracula span.cm-tag { color: #ff79c6; } +.cm-s-dracula span.cm-attribute { color: #50fa7b; } +.cm-s-dracula span.cm-qualifier { color: #50fa7b; } +.cm-s-dracula span.cm-property { color: #66d9ef; } +.cm-s-dracula span.cm-builtin { color: #50fa7b; } +.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; } + +.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); } +.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/docs/js/node_modules/codemirror/theme/duotone-dark.css b/docs/js/node_modules/codemirror/theme/duotone-dark.css new file mode 100644 index 000000000..88fdc76c8 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/duotone-dark.css @@ -0,0 +1,35 @@ +/* +Name: DuoTone-Dark +Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) + +CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/) +*/ + +.cm-s-duotone-dark.CodeMirror { background: #2a2734; color: #6c6783; } +.cm-s-duotone-dark div.CodeMirror-selected { background: #545167!important; } +.cm-s-duotone-dark .CodeMirror-gutters { background: #2a2734; border-right: 0px; } +.cm-s-duotone-dark .CodeMirror-linenumber { color: #545167; } + +/* begin cursor */ +.cm-s-duotone-dark .CodeMirror-cursor { border-left: 1px solid #ffad5c; /* border-left: 1px solid #ffad5c80; */ border-right: .5em solid #ffad5c; /* border-right: .5em solid #ffad5c80; */ opacity: .5; } +.cm-s-duotone-dark .CodeMirror-activeline-background { background: #363342; /* background: #36334280; */ opacity: .5;} +.cm-s-duotone-dark .cm-fat-cursor .CodeMirror-cursor { background: #ffad5c; /* background: #ffad5c80; */ opacity: .5;} +/* end cursor */ + +.cm-s-duotone-dark span.cm-atom, .cm-s-duotone-dark span.cm-number, .cm-s-duotone-dark span.cm-keyword, .cm-s-duotone-dark span.cm-variable, .cm-s-duotone-dark span.cm-attribute, .cm-s-duotone-dark span.cm-quote, .cm-s-duotone-dark span.cm-hr, .cm-s-duotone-dark span.cm-link { color: #ffcc99; } + +.cm-s-duotone-dark span.cm-property { color: #9a86fd; } +.cm-s-duotone-dark span.cm-punctuation, .cm-s-duotone-dark span.cm-unit, .cm-s-duotone-dark span.cm-negative { color: #e09142; } +.cm-s-duotone-dark span.cm-string { color: #ffb870; } +.cm-s-duotone-dark span.cm-operator { color: #ffad5c; } +.cm-s-duotone-dark span.cm-positive { color: #6a51e6; } + +.cm-s-duotone-dark span.cm-variable-2, .cm-s-duotone-dark span.cm-variable-3, .cm-s-duotone-dark span.cm-type, .cm-s-duotone-dark span.cm-string-2, .cm-s-duotone-dark span.cm-url { color: #7a63ee; } +.cm-s-duotone-dark span.cm-def, .cm-s-duotone-dark span.cm-tag, .cm-s-duotone-dark span.cm-builtin, .cm-s-duotone-dark span.cm-qualifier, .cm-s-duotone-dark span.cm-header, .cm-s-duotone-dark span.cm-em { color: #eeebff; } +.cm-s-duotone-dark span.cm-bracket, .cm-s-duotone-dark span.cm-comment { color: #6c6783; } + +/* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */ +.cm-s-duotone-dark span.cm-error, .cm-s-duotone-dark span.cm-invalidchar { color: #f00; } + +.cm-s-duotone-dark span.cm-header { font-weight: normal; } +.cm-s-duotone-dark .CodeMirror-matchingbracket { text-decoration: underline; color: #eeebff !important; } diff --git a/docs/js/node_modules/codemirror/theme/duotone-light.css b/docs/js/node_modules/codemirror/theme/duotone-light.css new file mode 100644 index 000000000..d99480f7c --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/duotone-light.css @@ -0,0 +1,36 @@ +/* +Name: DuoTone-Light +Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) + +CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/) +*/ + +.cm-s-duotone-light.CodeMirror { background: #faf8f5; color: #b29762; } +.cm-s-duotone-light div.CodeMirror-selected { background: #e3dcce !important; } +.cm-s-duotone-light .CodeMirror-gutters { background: #faf8f5; border-right: 0px; } +.cm-s-duotone-light .CodeMirror-linenumber { color: #cdc4b1; } + +/* begin cursor */ +.cm-s-duotone-light .CodeMirror-cursor { border-left: 1px solid #93abdc; /* border-left: 1px solid #93abdc80; */ border-right: .5em solid #93abdc; /* border-right: .5em solid #93abdc80; */ opacity: .5; } +.cm-s-duotone-light .CodeMirror-activeline-background { background: #e3dcce; /* background: #e3dcce80; */ opacity: .5; } +.cm-s-duotone-light .cm-fat-cursor .CodeMirror-cursor { background: #93abdc; /* #93abdc80; */ opacity: .5; } +/* end cursor */ + +.cm-s-duotone-light span.cm-atom, .cm-s-duotone-light span.cm-number, .cm-s-duotone-light span.cm-keyword, .cm-s-duotone-light span.cm-variable, .cm-s-duotone-light span.cm-attribute, .cm-s-duotone-light span.cm-quote, .cm-s-duotone-light-light span.cm-hr, .cm-s-duotone-light-light span.cm-link { color: #063289; } + +.cm-s-duotone-light span.cm-property { color: #b29762; } +.cm-s-duotone-light span.cm-punctuation, .cm-s-duotone-light span.cm-unit, .cm-s-duotone-light span.cm-negative { color: #063289; } +.cm-s-duotone-light span.cm-string, .cm-s-duotone-light span.cm-operator { color: #1659df; } +.cm-s-duotone-light span.cm-positive { color: #896724; } + +.cm-s-duotone-light span.cm-variable-2, .cm-s-duotone-light span.cm-variable-3, .cm-s-duotone-light span.cm-type, .cm-s-duotone-light span.cm-string-2, .cm-s-duotone-light span.cm-url { color: #896724; } +.cm-s-duotone-light span.cm-def, .cm-s-duotone-light span.cm-tag, .cm-s-duotone-light span.cm-builtin, .cm-s-duotone-light span.cm-qualifier, .cm-s-duotone-light span.cm-header, .cm-s-duotone-light span.cm-em { color: #2d2006; } +.cm-s-duotone-light span.cm-bracket, .cm-s-duotone-light span.cm-comment { color: #b6ad9a; } + +/* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */ +/* .cm-s-duotone-light span.cm-error { background: #896724; color: #728fcb; } */ +.cm-s-duotone-light span.cm-error, .cm-s-duotone-light span.cm-invalidchar { color: #f00; } + +.cm-s-duotone-light span.cm-header { font-weight: normal; } +.cm-s-duotone-light .CodeMirror-matchingbracket { text-decoration: underline; color: #faf8f5 !important; } + diff --git a/docs/js/node_modules/codemirror/theme/eclipse.css b/docs/js/node_modules/codemirror/theme/eclipse.css new file mode 100644 index 000000000..800d603f6 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/eclipse.css @@ -0,0 +1,23 @@ +.cm-s-eclipse span.cm-meta { color: #FF1717; } +.cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; } +.cm-s-eclipse span.cm-atom { color: #219; } +.cm-s-eclipse span.cm-number { color: #164; } +.cm-s-eclipse span.cm-def { color: #00f; } +.cm-s-eclipse span.cm-variable { color: black; } +.cm-s-eclipse span.cm-variable-2 { color: #0000C0; } +.cm-s-eclipse span.cm-variable-3, .cm-s-eclipse span.cm-type { color: #0000C0; } +.cm-s-eclipse span.cm-property { color: black; } +.cm-s-eclipse span.cm-operator { color: black; } +.cm-s-eclipse span.cm-comment { color: #3F7F5F; } +.cm-s-eclipse span.cm-string { color: #2A00FF; } +.cm-s-eclipse span.cm-string-2 { color: #f50; } +.cm-s-eclipse span.cm-qualifier { color: #555; } +.cm-s-eclipse span.cm-builtin { color: #30a; } +.cm-s-eclipse span.cm-bracket { color: #cc7; } +.cm-s-eclipse span.cm-tag { color: #170; } +.cm-s-eclipse span.cm-attribute { color: #00c; } +.cm-s-eclipse span.cm-link { color: #219; } +.cm-s-eclipse span.cm-error { color: #f00; } + +.cm-s-eclipse .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-eclipse .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } diff --git a/docs/js/node_modules/codemirror/theme/elegant.css b/docs/js/node_modules/codemirror/theme/elegant.css new file mode 100644 index 000000000..45b3ea655 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/elegant.css @@ -0,0 +1,13 @@ +.cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom { color: #762; } +.cm-s-elegant span.cm-comment { color: #262; font-style: italic; line-height: 1em; } +.cm-s-elegant span.cm-meta { color: #555; font-style: italic; line-height: 1em; } +.cm-s-elegant span.cm-variable { color: black; } +.cm-s-elegant span.cm-variable-2 { color: #b11; } +.cm-s-elegant span.cm-qualifier { color: #555; } +.cm-s-elegant span.cm-keyword { color: #730; } +.cm-s-elegant span.cm-builtin { color: #30a; } +.cm-s-elegant span.cm-link { color: #762; } +.cm-s-elegant span.cm-error { background-color: #fdd; } + +.cm-s-elegant .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-elegant .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } diff --git a/docs/js/node_modules/codemirror/theme/erlang-dark.css b/docs/js/node_modules/codemirror/theme/erlang-dark.css new file mode 100644 index 000000000..8c8a4171a --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/erlang-dark.css @@ -0,0 +1,34 @@ +.cm-s-erlang-dark.CodeMirror { background: #002240; color: white; } +.cm-s-erlang-dark div.CodeMirror-selected { background: #b36539; } +.cm-s-erlang-dark .CodeMirror-line::selection, .cm-s-erlang-dark .CodeMirror-line > span::selection, .cm-s-erlang-dark .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99); } +.cm-s-erlang-dark .CodeMirror-line::-moz-selection, .cm-s-erlang-dark .CodeMirror-line > span::-moz-selection, .cm-s-erlang-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99); } +.cm-s-erlang-dark .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } +.cm-s-erlang-dark .CodeMirror-guttermarker { color: white; } +.cm-s-erlang-dark .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-erlang-dark .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-erlang-dark .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-erlang-dark span.cm-quote { color: #ccc; } +.cm-s-erlang-dark span.cm-atom { color: #f133f1; } +.cm-s-erlang-dark span.cm-attribute { color: #ff80e1; } +.cm-s-erlang-dark span.cm-bracket { color: #ff9d00; } +.cm-s-erlang-dark span.cm-builtin { color: #eaa; } +.cm-s-erlang-dark span.cm-comment { color: #77f; } +.cm-s-erlang-dark span.cm-def { color: #e7a; } +.cm-s-erlang-dark span.cm-keyword { color: #ffee80; } +.cm-s-erlang-dark span.cm-meta { color: #50fefe; } +.cm-s-erlang-dark span.cm-number { color: #ffd0d0; } +.cm-s-erlang-dark span.cm-operator { color: #d55; } +.cm-s-erlang-dark span.cm-property { color: #ccc; } +.cm-s-erlang-dark span.cm-qualifier { color: #ccc; } +.cm-s-erlang-dark span.cm-special { color: #ffbbbb; } +.cm-s-erlang-dark span.cm-string { color: #3ad900; } +.cm-s-erlang-dark span.cm-string-2 { color: #ccc; } +.cm-s-erlang-dark span.cm-tag { color: #9effff; } +.cm-s-erlang-dark span.cm-variable { color: #50fe50; } +.cm-s-erlang-dark span.cm-variable-2 { color: #e0e; } +.cm-s-erlang-dark span.cm-variable-3, .cm-s-erlang-dark span.cm-type { color: #ccc; } +.cm-s-erlang-dark span.cm-error { color: #9d1e15; } + +.cm-s-erlang-dark .CodeMirror-activeline-background { background: #013461; } +.cm-s-erlang-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/docs/js/node_modules/codemirror/theme/gruvbox-dark.css b/docs/js/node_modules/codemirror/theme/gruvbox-dark.css new file mode 100644 index 000000000..ded215f57 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/gruvbox-dark.css @@ -0,0 +1,37 @@ +/* + + Name: gruvbox-dark + Author: kRkk (https://github.com/krkk) + + Original gruvbox color scheme by Pavel Pertsev (https://github.com/morhetz/gruvbox) + +*/ + +.cm-s-gruvbox-dark.CodeMirror, .cm-s-gruvbox-dark .CodeMirror-gutters { background-color: #282828; color: #bdae93; } +.cm-s-gruvbox-dark .CodeMirror-gutters {background: #282828; border-right: 0px;} +.cm-s-gruvbox-dark .CodeMirror-linenumber {color: #7c6f64;} +.cm-s-gruvbox-dark .CodeMirror-cursor { border-left: 1px solid #ebdbb2; } +.cm-s-gruvbox-dark div.CodeMirror-selected { background: #928374; } +.cm-s-gruvbox-dark span.cm-meta { color: #83a598; } + +.cm-s-gruvbox-dark span.cm-comment { color: #928374; } +.cm-s-gruvbox-dark span.cm-number, span.cm-atom { color: #d3869b; } +.cm-s-gruvbox-dark span.cm-keyword { color: #f84934; } + +.cm-s-gruvbox-dark span.cm-variable { color: #ebdbb2; } +.cm-s-gruvbox-dark span.cm-variable-2 { color: #ebdbb2; } +.cm-s-gruvbox-dark span.cm-variable-3, .cm-s-gruvbox-dark span.cm-type { color: #fabd2f; } +.cm-s-gruvbox-dark span.cm-operator { color: #ebdbb2; } +.cm-s-gruvbox-dark span.cm-callee { color: #ebdbb2; } +.cm-s-gruvbox-dark span.cm-def { color: #ebdbb2; } +.cm-s-gruvbox-dark span.cm-property { color: #ebdbb2; } +.cm-s-gruvbox-dark span.cm-string { color: #b8bb26; } +.cm-s-gruvbox-dark span.cm-string-2 { color: #8ec07c; } +.cm-s-gruvbox-dark span.cm-qualifier { color: #8ec07c; } +.cm-s-gruvbox-dark span.cm-attribute { color: #8ec07c; } + +.cm-s-gruvbox-dark .CodeMirror-activeline-background { background: #3c3836; } +.cm-s-gruvbox-dark .CodeMirror-matchingbracket { background: #928374; color:#282828 !important; } + +.cm-s-gruvbox-dark span.cm-builtin { color: #fe8019; } +.cm-s-gruvbox-dark span.cm-tag { color: #fe8019; } diff --git a/docs/js/node_modules/codemirror/theme/hopscotch.css b/docs/js/node_modules/codemirror/theme/hopscotch.css new file mode 100644 index 000000000..7d05431bd --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/hopscotch.css @@ -0,0 +1,34 @@ +/* + + Name: Hopscotch + Author: Jan T. Sott + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-hopscotch.CodeMirror {background: #322931; color: #d5d3d5;} +.cm-s-hopscotch div.CodeMirror-selected {background: #433b42 !important;} +.cm-s-hopscotch .CodeMirror-gutters {background: #322931; border-right: 0px;} +.cm-s-hopscotch .CodeMirror-linenumber {color: #797379;} +.cm-s-hopscotch .CodeMirror-cursor {border-left: 1px solid #989498 !important;} + +.cm-s-hopscotch span.cm-comment {color: #b33508;} +.cm-s-hopscotch span.cm-atom {color: #c85e7c;} +.cm-s-hopscotch span.cm-number {color: #c85e7c;} + +.cm-s-hopscotch span.cm-property, .cm-s-hopscotch span.cm-attribute {color: #8fc13e;} +.cm-s-hopscotch span.cm-keyword {color: #dd464c;} +.cm-s-hopscotch span.cm-string {color: #fdcc59;} + +.cm-s-hopscotch span.cm-variable {color: #8fc13e;} +.cm-s-hopscotch span.cm-variable-2 {color: #1290bf;} +.cm-s-hopscotch span.cm-def {color: #fd8b19;} +.cm-s-hopscotch span.cm-error {background: #dd464c; color: #989498;} +.cm-s-hopscotch span.cm-bracket {color: #d5d3d5;} +.cm-s-hopscotch span.cm-tag {color: #dd464c;} +.cm-s-hopscotch span.cm-link {color: #c85e7c;} + +.cm-s-hopscotch .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} +.cm-s-hopscotch .CodeMirror-activeline-background { background: #302020; } diff --git a/docs/js/node_modules/codemirror/theme/icecoder.css b/docs/js/node_modules/codemirror/theme/icecoder.css new file mode 100644 index 000000000..5440fbe27 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/icecoder.css @@ -0,0 +1,43 @@ +/* +ICEcoder default theme by Matt Pass, used in code editor available at https://icecoder.net +*/ + +.cm-s-icecoder { color: #666; background: #1d1d1b; } + +.cm-s-icecoder span.cm-keyword { color: #eee; font-weight:bold; } /* off-white 1 */ +.cm-s-icecoder span.cm-atom { color: #e1c76e; } /* yellow */ +.cm-s-icecoder span.cm-number { color: #6cb5d9; } /* blue */ +.cm-s-icecoder span.cm-def { color: #b9ca4a; } /* green */ + +.cm-s-icecoder span.cm-variable { color: #6cb5d9; } /* blue */ +.cm-s-icecoder span.cm-variable-2 { color: #cc1e5c; } /* pink */ +.cm-s-icecoder span.cm-variable-3, .cm-s-icecoder span.cm-type { color: #f9602c; } /* orange */ + +.cm-s-icecoder span.cm-property { color: #eee; } /* off-white 1 */ +.cm-s-icecoder span.cm-operator { color: #9179bb; } /* purple */ +.cm-s-icecoder span.cm-comment { color: #97a3aa; } /* grey-blue */ + +.cm-s-icecoder span.cm-string { color: #b9ca4a; } /* green */ +.cm-s-icecoder span.cm-string-2 { color: #6cb5d9; } /* blue */ + +.cm-s-icecoder span.cm-meta { color: #555; } /* grey */ + +.cm-s-icecoder span.cm-qualifier { color: #555; } /* grey */ +.cm-s-icecoder span.cm-builtin { color: #214e7b; } /* bright blue */ +.cm-s-icecoder span.cm-bracket { color: #cc7; } /* grey-yellow */ + +.cm-s-icecoder span.cm-tag { color: #e8e8e8; } /* off-white 2 */ +.cm-s-icecoder span.cm-attribute { color: #099; } /* teal */ + +.cm-s-icecoder span.cm-header { color: #6a0d6a; } /* purple-pink */ +.cm-s-icecoder span.cm-quote { color: #186718; } /* dark green */ +.cm-s-icecoder span.cm-hr { color: #888; } /* mid-grey */ +.cm-s-icecoder span.cm-link { color: #e1c76e; } /* yellow */ +.cm-s-icecoder span.cm-error { color: #d00; } /* red */ + +.cm-s-icecoder .CodeMirror-cursor { border-left: 1px solid white; } +.cm-s-icecoder div.CodeMirror-selected { color: #fff; background: #037; } +.cm-s-icecoder .CodeMirror-gutters { background: #1d1d1b; min-width: 41px; border-right: 0; } +.cm-s-icecoder .CodeMirror-linenumber { color: #555; cursor: default; } +.cm-s-icecoder .CodeMirror-matchingbracket { color: #fff !important; background: #555 !important; } +.cm-s-icecoder .CodeMirror-activeline-background { background: #000; } diff --git a/docs/js/node_modules/codemirror/theme/idea.css b/docs/js/node_modules/codemirror/theme/idea.css new file mode 100644 index 000000000..eab36717a --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/idea.css @@ -0,0 +1,42 @@ +/** + Name: IDEA default theme + From IntelliJ IDEA by JetBrains + */ + +.cm-s-idea span.cm-meta { color: #808000; } +.cm-s-idea span.cm-number { color: #0000FF; } +.cm-s-idea span.cm-keyword { line-height: 1em; font-weight: bold; color: #000080; } +.cm-s-idea span.cm-atom { font-weight: bold; color: #000080; } +.cm-s-idea span.cm-def { color: #000000; } +.cm-s-idea span.cm-variable { color: black; } +.cm-s-idea span.cm-variable-2 { color: black; } +.cm-s-idea span.cm-variable-3, .cm-s-idea span.cm-type { color: black; } +.cm-s-idea span.cm-property { color: black; } +.cm-s-idea span.cm-operator { color: black; } +.cm-s-idea span.cm-comment { color: #808080; } +.cm-s-idea span.cm-string { color: #008000; } +.cm-s-idea span.cm-string-2 { color: #008000; } +.cm-s-idea span.cm-qualifier { color: #555; } +.cm-s-idea span.cm-error { color: #FF0000; } +.cm-s-idea span.cm-attribute { color: #0000FF; } +.cm-s-idea span.cm-tag { color: #000080; } +.cm-s-idea span.cm-link { color: #0000FF; } +.cm-s-idea .CodeMirror-activeline-background { background: #FFFAE3; } + +.cm-s-idea span.cm-builtin { color: #30a; } +.cm-s-idea span.cm-bracket { color: #cc7; } +.cm-s-idea { font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;} + + +.cm-s-idea .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } + +.CodeMirror-hints.idea { + font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; + color: #616569; + background-color: #ebf3fd !important; +} + +.CodeMirror-hints.idea .CodeMirror-hint-active { + background-color: #a2b8c9 !important; + color: #5c6065 !important; +} \ No newline at end of file diff --git a/docs/js/node_modules/codemirror/theme/isotope.css b/docs/js/node_modules/codemirror/theme/isotope.css new file mode 100644 index 000000000..d0d6263cf --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/isotope.css @@ -0,0 +1,34 @@ +/* + + Name: Isotope + Author: David Desandro / Jan T. Sott + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-isotope.CodeMirror {background: #000000; color: #e0e0e0;} +.cm-s-isotope div.CodeMirror-selected {background: #404040 !important;} +.cm-s-isotope .CodeMirror-gutters {background: #000000; border-right: 0px;} +.cm-s-isotope .CodeMirror-linenumber {color: #808080;} +.cm-s-isotope .CodeMirror-cursor {border-left: 1px solid #c0c0c0 !important;} + +.cm-s-isotope span.cm-comment {color: #3300ff;} +.cm-s-isotope span.cm-atom {color: #cc00ff;} +.cm-s-isotope span.cm-number {color: #cc00ff;} + +.cm-s-isotope span.cm-property, .cm-s-isotope span.cm-attribute {color: #33ff00;} +.cm-s-isotope span.cm-keyword {color: #ff0000;} +.cm-s-isotope span.cm-string {color: #ff0099;} + +.cm-s-isotope span.cm-variable {color: #33ff00;} +.cm-s-isotope span.cm-variable-2 {color: #0066ff;} +.cm-s-isotope span.cm-def {color: #ff9900;} +.cm-s-isotope span.cm-error {background: #ff0000; color: #c0c0c0;} +.cm-s-isotope span.cm-bracket {color: #e0e0e0;} +.cm-s-isotope span.cm-tag {color: #ff0000;} +.cm-s-isotope span.cm-link {color: #cc00ff;} + +.cm-s-isotope .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} +.cm-s-isotope .CodeMirror-activeline-background { background: #202020; } diff --git a/docs/js/node_modules/codemirror/theme/lesser-dark.css b/docs/js/node_modules/codemirror/theme/lesser-dark.css new file mode 100644 index 000000000..f96bf430c --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/lesser-dark.css @@ -0,0 +1,47 @@ +/* +http://lesscss.org/ dark theme +Ported to CodeMirror by Peter Kroon +*/ +.cm-s-lesser-dark { + line-height: 1.3em; +} +.cm-s-lesser-dark.CodeMirror { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; } +.cm-s-lesser-dark div.CodeMirror-selected { background: #45443B; } /* 33322B*/ +.cm-s-lesser-dark .CodeMirror-line::selection, .cm-s-lesser-dark .CodeMirror-line > span::selection, .cm-s-lesser-dark .CodeMirror-line > span > span::selection { background: rgba(69, 68, 59, .99); } +.cm-s-lesser-dark .CodeMirror-line::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(69, 68, 59, .99); } +.cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white; } +.cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/ + +.cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/ + +.cm-s-lesser-dark .CodeMirror-gutters { background: #262626; border-right:1px solid #aaa; } +.cm-s-lesser-dark .CodeMirror-guttermarker { color: #599eff; } +.cm-s-lesser-dark .CodeMirror-guttermarker-subtle { color: #777; } +.cm-s-lesser-dark .CodeMirror-linenumber { color: #777; } + +.cm-s-lesser-dark span.cm-header { color: #a0a; } +.cm-s-lesser-dark span.cm-quote { color: #090; } +.cm-s-lesser-dark span.cm-keyword { color: #599eff; } +.cm-s-lesser-dark span.cm-atom { color: #C2B470; } +.cm-s-lesser-dark span.cm-number { color: #B35E4D; } +.cm-s-lesser-dark span.cm-def { color: white; } +.cm-s-lesser-dark span.cm-variable { color:#D9BF8C; } +.cm-s-lesser-dark span.cm-variable-2 { color: #669199; } +.cm-s-lesser-dark span.cm-variable-3, .cm-s-lesser-dark span.cm-type { color: white; } +.cm-s-lesser-dark span.cm-property { color: #92A75C; } +.cm-s-lesser-dark span.cm-operator { color: #92A75C; } +.cm-s-lesser-dark span.cm-comment { color: #666; } +.cm-s-lesser-dark span.cm-string { color: #BCD279; } +.cm-s-lesser-dark span.cm-string-2 { color: #f50; } +.cm-s-lesser-dark span.cm-meta { color: #738C73; } +.cm-s-lesser-dark span.cm-qualifier { color: #555; } +.cm-s-lesser-dark span.cm-builtin { color: #ff9e59; } +.cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; } +.cm-s-lesser-dark span.cm-tag { color: #669199; } +.cm-s-lesser-dark span.cm-attribute { color: #81a4d5; } +.cm-s-lesser-dark span.cm-hr { color: #999; } +.cm-s-lesser-dark span.cm-link { color: #7070E6; } +.cm-s-lesser-dark span.cm-error { color: #9d1e15; } + +.cm-s-lesser-dark .CodeMirror-activeline-background { background: #3C3A3A; } +.cm-s-lesser-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/docs/js/node_modules/codemirror/theme/liquibyte.css b/docs/js/node_modules/codemirror/theme/liquibyte.css new file mode 100644 index 000000000..393825e02 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/liquibyte.css @@ -0,0 +1,95 @@ +.cm-s-liquibyte.CodeMirror { + background-color: #000; + color: #fff; + line-height: 1.2em; + font-size: 1em; +} +.cm-s-liquibyte .CodeMirror-focused .cm-matchhighlight { + text-decoration: underline; + text-decoration-color: #0f0; + text-decoration-style: wavy; +} +.cm-s-liquibyte .cm-trailingspace { + text-decoration: line-through; + text-decoration-color: #f00; + text-decoration-style: dotted; +} +.cm-s-liquibyte .cm-tab { + text-decoration: line-through; + text-decoration-color: #404040; + text-decoration-style: dotted; +} +.cm-s-liquibyte .CodeMirror-gutters { background-color: #262626; border-right: 1px solid #505050; padding-right: 0.8em; } +.cm-s-liquibyte .CodeMirror-gutter-elt div { font-size: 1.2em; } +.cm-s-liquibyte .CodeMirror-guttermarker { } +.cm-s-liquibyte .CodeMirror-guttermarker-subtle { } +.cm-s-liquibyte .CodeMirror-linenumber { color: #606060; padding-left: 0; } +.cm-s-liquibyte .CodeMirror-cursor { border-left: 1px solid #eee; } + +.cm-s-liquibyte span.cm-comment { color: #008000; } +.cm-s-liquibyte span.cm-def { color: #ffaf40; font-weight: bold; } +.cm-s-liquibyte span.cm-keyword { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-builtin { color: #ffaf40; font-weight: bold; } +.cm-s-liquibyte span.cm-variable { color: #5967ff; font-weight: bold; } +.cm-s-liquibyte span.cm-string { color: #ff8000; } +.cm-s-liquibyte span.cm-number { color: #0f0; font-weight: bold; } +.cm-s-liquibyte span.cm-atom { color: #bf3030; font-weight: bold; } + +.cm-s-liquibyte span.cm-variable-2 { color: #007f7f; font-weight: bold; } +.cm-s-liquibyte span.cm-variable-3, .cm-s-liquibyte span.cm-type { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-property { color: #999; font-weight: bold; } +.cm-s-liquibyte span.cm-operator { color: #fff; } + +.cm-s-liquibyte span.cm-meta { color: #0f0; } +.cm-s-liquibyte span.cm-qualifier { color: #fff700; font-weight: bold; } +.cm-s-liquibyte span.cm-bracket { color: #cc7; } +.cm-s-liquibyte span.cm-tag { color: #ff0; font-weight: bold; } +.cm-s-liquibyte span.cm-attribute { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-error { color: #f00; } + +.cm-s-liquibyte div.CodeMirror-selected { background-color: rgba(255, 0, 0, 0.25); } + +.cm-s-liquibyte span.cm-compilation { background-color: rgba(255, 255, 255, 0.12); } + +.cm-s-liquibyte .CodeMirror-activeline-background { background-color: rgba(0, 255, 0, 0.15); } + +/* Default styles for common addons */ +.cm-s-liquibyte .CodeMirror span.CodeMirror-matchingbracket { color: #0f0; font-weight: bold; } +.cm-s-liquibyte .CodeMirror span.CodeMirror-nonmatchingbracket { color: #f00; font-weight: bold; } +.CodeMirror-matchingtag { background-color: rgba(150, 255, 0, .3); } +/* Scrollbars */ +/* Simple */ +.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div:hover, .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div:hover { + background-color: rgba(80, 80, 80, .7); +} +.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div, .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div { + background-color: rgba(80, 80, 80, .3); + border: 1px solid #404040; + border-radius: 5px; +} +.cm-s-liquibyte div.CodeMirror-simplescroll-vertical div { + border-top: 1px solid #404040; + border-bottom: 1px solid #404040; +} +.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div { + border-left: 1px solid #404040; + border-right: 1px solid #404040; +} +.cm-s-liquibyte div.CodeMirror-simplescroll-vertical { + background-color: #262626; +} +.cm-s-liquibyte div.CodeMirror-simplescroll-horizontal { + background-color: #262626; + border-top: 1px solid #404040; +} +/* Overlay */ +.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div, div.CodeMirror-overlayscroll-vertical div { + background-color: #404040; + border-radius: 5px; +} +.cm-s-liquibyte div.CodeMirror-overlayscroll-vertical div { + border: 1px solid #404040; +} +.cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div { + border: 1px solid #404040; +} diff --git a/docs/js/node_modules/codemirror/theme/lucario.css b/docs/js/node_modules/codemirror/theme/lucario.css new file mode 100644 index 000000000..17a155103 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/lucario.css @@ -0,0 +1,37 @@ +/* + Name: lucario + Author: Raphael Amorim + + Original Lucario color scheme (https://github.com/raphamorim/lucario) +*/ + +.cm-s-lucario.CodeMirror, .cm-s-lucario .CodeMirror-gutters { + background-color: #2b3e50 !important; + color: #f8f8f2 !important; + border: none; +} +.cm-s-lucario .CodeMirror-gutters { color: #2b3e50; } +.cm-s-lucario .CodeMirror-cursor { border-left: solid thin #E6C845; } +.cm-s-lucario .CodeMirror-linenumber { color: #f8f8f2; } +.cm-s-lucario .CodeMirror-selected { background: #243443; } +.cm-s-lucario .CodeMirror-line::selection, .cm-s-lucario .CodeMirror-line > span::selection, .cm-s-lucario .CodeMirror-line > span > span::selection { background: #243443; } +.cm-s-lucario .CodeMirror-line::-moz-selection, .cm-s-lucario .CodeMirror-line > span::-moz-selection, .cm-s-lucario .CodeMirror-line > span > span::-moz-selection { background: #243443; } +.cm-s-lucario span.cm-comment { color: #5c98cd; } +.cm-s-lucario span.cm-string, .cm-s-lucario span.cm-string-2 { color: #E6DB74; } +.cm-s-lucario span.cm-number { color: #ca94ff; } +.cm-s-lucario span.cm-variable { color: #f8f8f2; } +.cm-s-lucario span.cm-variable-2 { color: #f8f8f2; } +.cm-s-lucario span.cm-def { color: #72C05D; } +.cm-s-lucario span.cm-operator { color: #66D9EF; } +.cm-s-lucario span.cm-keyword { color: #ff6541; } +.cm-s-lucario span.cm-atom { color: #bd93f9; } +.cm-s-lucario span.cm-meta { color: #f8f8f2; } +.cm-s-lucario span.cm-tag { color: #ff6541; } +.cm-s-lucario span.cm-attribute { color: #66D9EF; } +.cm-s-lucario span.cm-qualifier { color: #72C05D; } +.cm-s-lucario span.cm-property { color: #f8f8f2; } +.cm-s-lucario span.cm-builtin { color: #72C05D; } +.cm-s-lucario span.cm-variable-3, .cm-s-lucario span.cm-type { color: #ffb86c; } + +.cm-s-lucario .CodeMirror-activeline-background { background: #243443; } +.cm-s-lucario .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/docs/js/node_modules/codemirror/theme/material-darker.css b/docs/js/node_modules/codemirror/theme/material-darker.css new file mode 100644 index 000000000..45b64efb2 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/material-darker.css @@ -0,0 +1,135 @@ +/* + Name: material + Author: Mattia Astorino (http://github.com/equinusocio) + Website: https://material-theme.site/ +*/ + +.cm-s-material-darker.CodeMirror { + background-color: #212121; + color: #EEFFFF; +} + +.cm-s-material-darker .CodeMirror-gutters { + background: #212121; + color: #545454; + border: none; +} + +.cm-s-material-darker .CodeMirror-guttermarker, +.cm-s-material-darker .CodeMirror-guttermarker-subtle, +.cm-s-material-darker .CodeMirror-linenumber { + color: #545454; +} + +.cm-s-material-darker .CodeMirror-cursor { + border-left: 1px solid #FFCC00; +} + +.cm-s-material-darker div.CodeMirror-selected { + background: rgba(97, 97, 97, 0.2); +} + +.cm-s-material-darker.CodeMirror-focused div.CodeMirror-selected { + background: rgba(97, 97, 97, 0.2); +} + +.cm-s-material-darker .CodeMirror-line::selection, +.cm-s-material-darker .CodeMirror-line>span::selection, +.cm-s-material-darker .CodeMirror-line>span>span::selection { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-material-darker .CodeMirror-line::-moz-selection, +.cm-s-material-darker .CodeMirror-line>span::-moz-selection, +.cm-s-material-darker .CodeMirror-line>span>span::-moz-selection { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-material-darker .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.5); +} + +.cm-s-material-darker .cm-keyword { + color: #C792EA; +} + +.cm-s-material-darker .cm-operator { + color: #89DDFF; +} + +.cm-s-material-darker .cm-variable-2 { + color: #EEFFFF; +} + +.cm-s-material-darker .cm-variable-3, +.cm-s-material-darker .cm-type { + color: #f07178; +} + +.cm-s-material-darker .cm-builtin { + color: #FFCB6B; +} + +.cm-s-material-darker .cm-atom { + color: #F78C6C; +} + +.cm-s-material-darker .cm-number { + color: #FF5370; +} + +.cm-s-material-darker .cm-def { + color: #82AAFF; +} + +.cm-s-material-darker .cm-string { + color: #C3E88D; +} + +.cm-s-material-darker .cm-string-2 { + color: #f07178; +} + +.cm-s-material-darker .cm-comment { + color: #545454; +} + +.cm-s-material-darker .cm-variable { + color: #f07178; +} + +.cm-s-material-darker .cm-tag { + color: #FF5370; +} + +.cm-s-material-darker .cm-meta { + color: #FFCB6B; +} + +.cm-s-material-darker .cm-attribute { + color: #C792EA; +} + +.cm-s-material-darker .cm-property { + color: #C792EA; +} + +.cm-s-material-darker .cm-qualifier { + color: #DECB6B; +} + +.cm-s-material-darker .cm-variable-3, +.cm-s-material-darker .cm-type { + color: #DECB6B; +} + + +.cm-s-material-darker .cm-error { + color: rgba(255, 255, 255, 1.0); + background-color: #FF5370; +} + +.cm-s-material-darker .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} \ No newline at end of file diff --git a/docs/js/node_modules/codemirror/theme/material-ocean.css b/docs/js/node_modules/codemirror/theme/material-ocean.css new file mode 100644 index 000000000..86a6f3cd5 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/material-ocean.css @@ -0,0 +1,135 @@ +/* + Name: material + Author: Mattia Astorino (http://github.com/equinusocio) + Website: https://material-theme.site/ +*/ + +.cm-s-material-ocean.CodeMirror { + background-color: #0F111A; + color: #8F93A2; +} + +.cm-s-material-ocean .CodeMirror-gutters { + background: #0F111A; + color: #464B5D; + border: none; +} + +.cm-s-material-ocean .CodeMirror-guttermarker, +.cm-s-material-ocean .CodeMirror-guttermarker-subtle, +.cm-s-material-ocean .CodeMirror-linenumber { + color: #464B5D; +} + +.cm-s-material-ocean .CodeMirror-cursor { + border-left: 1px solid #FFCC00; +} + +.cm-s-material-ocean div.CodeMirror-selected { + background: rgba(113, 124, 180, 0.2); +} + +.cm-s-material-ocean.CodeMirror-focused div.CodeMirror-selected { + background: rgba(113, 124, 180, 0.2); +} + +.cm-s-material-ocean .CodeMirror-line::selection, +.cm-s-material-ocean .CodeMirror-line>span::selection, +.cm-s-material-ocean .CodeMirror-line>span>span::selection { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-material-ocean .CodeMirror-line::-moz-selection, +.cm-s-material-ocean .CodeMirror-line>span::-moz-selection, +.cm-s-material-ocean .CodeMirror-line>span>span::-moz-selection { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-material-ocean .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.5); +} + +.cm-s-material-ocean .cm-keyword { + color: #C792EA; +} + +.cm-s-material-ocean .cm-operator { + color: #89DDFF; +} + +.cm-s-material-ocean .cm-variable-2 { + color: #EEFFFF; +} + +.cm-s-material-ocean .cm-variable-3, +.cm-s-material-ocean .cm-type { + color: #f07178; +} + +.cm-s-material-ocean .cm-builtin { + color: #FFCB6B; +} + +.cm-s-material-ocean .cm-atom { + color: #F78C6C; +} + +.cm-s-material-ocean .cm-number { + color: #FF5370; +} + +.cm-s-material-ocean .cm-def { + color: #82AAFF; +} + +.cm-s-material-ocean .cm-string { + color: #C3E88D; +} + +.cm-s-material-ocean .cm-string-2 { + color: #f07178; +} + +.cm-s-material-ocean .cm-comment { + color: #464B5D; +} + +.cm-s-material-ocean .cm-variable { + color: #f07178; +} + +.cm-s-material-ocean .cm-tag { + color: #FF5370; +} + +.cm-s-material-ocean .cm-meta { + color: #FFCB6B; +} + +.cm-s-material-ocean .cm-attribute { + color: #C792EA; +} + +.cm-s-material-ocean .cm-property { + color: #C792EA; +} + +.cm-s-material-ocean .cm-qualifier { + color: #DECB6B; +} + +.cm-s-material-ocean .cm-variable-3, +.cm-s-material-ocean .cm-type { + color: #DECB6B; +} + + +.cm-s-material-ocean .cm-error { + color: rgba(255, 255, 255, 1.0); + background-color: #FF5370; +} + +.cm-s-material-ocean .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} \ No newline at end of file diff --git a/docs/js/node_modules/codemirror/theme/material-palenight.css b/docs/js/node_modules/codemirror/theme/material-palenight.css new file mode 100644 index 000000000..66d53dd39 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/material-palenight.css @@ -0,0 +1,135 @@ +/* + Name: material + Author: Mattia Astorino (http://github.com/equinusocio) + Website: https://material-theme.site/ +*/ + +.cm-s-material-palenight.CodeMirror { + background-color: #292D3E; + color: #A6ACCD; +} + +.cm-s-material-palenight .CodeMirror-gutters { + background: #292D3E; + color: #676E95; + border: none; +} + +.cm-s-material-palenight .CodeMirror-guttermarker, +.cm-s-material-palenight .CodeMirror-guttermarker-subtle, +.cm-s-material-palenight .CodeMirror-linenumber { + color: #676E95; +} + +.cm-s-material-palenight .CodeMirror-cursor { + border-left: 1px solid #FFCC00; +} + +.cm-s-material-palenight div.CodeMirror-selected { + background: rgba(113, 124, 180, 0.2); +} + +.cm-s-material-palenight.CodeMirror-focused div.CodeMirror-selected { + background: rgba(113, 124, 180, 0.2); +} + +.cm-s-material-palenight .CodeMirror-line::selection, +.cm-s-material-palenight .CodeMirror-line>span::selection, +.cm-s-material-palenight .CodeMirror-line>span>span::selection { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-material-palenight .CodeMirror-line::-moz-selection, +.cm-s-material-palenight .CodeMirror-line>span::-moz-selection, +.cm-s-material-palenight .CodeMirror-line>span>span::-moz-selection { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-material-palenight .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.5); +} + +.cm-s-material-palenight .cm-keyword { + color: #C792EA; +} + +.cm-s-material-palenight .cm-operator { + color: #89DDFF; +} + +.cm-s-material-palenight .cm-variable-2 { + color: #EEFFFF; +} + +.cm-s-material-palenight .cm-variable-3, +.cm-s-material-palenight .cm-type { + color: #f07178; +} + +.cm-s-material-palenight .cm-builtin { + color: #FFCB6B; +} + +.cm-s-material-palenight .cm-atom { + color: #F78C6C; +} + +.cm-s-material-palenight .cm-number { + color: #FF5370; +} + +.cm-s-material-palenight .cm-def { + color: #82AAFF; +} + +.cm-s-material-palenight .cm-string { + color: #C3E88D; +} + +.cm-s-material-palenight .cm-string-2 { + color: #f07178; +} + +.cm-s-material-palenight .cm-comment { + color: #676E95; +} + +.cm-s-material-palenight .cm-variable { + color: #f07178; +} + +.cm-s-material-palenight .cm-tag { + color: #FF5370; +} + +.cm-s-material-palenight .cm-meta { + color: #FFCB6B; +} + +.cm-s-material-palenight .cm-attribute { + color: #C792EA; +} + +.cm-s-material-palenight .cm-property { + color: #C792EA; +} + +.cm-s-material-palenight .cm-qualifier { + color: #DECB6B; +} + +.cm-s-material-palenight .cm-variable-3, +.cm-s-material-palenight .cm-type { + color: #DECB6B; +} + + +.cm-s-material-palenight .cm-error { + color: rgba(255, 255, 255, 1.0); + background-color: #FF5370; +} + +.cm-s-material-palenight .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} \ No newline at end of file diff --git a/docs/js/node_modules/codemirror/theme/material.css b/docs/js/node_modules/codemirror/theme/material.css new file mode 100644 index 000000000..9ac17a367 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/material.css @@ -0,0 +1,135 @@ +/* + Name: material + Author: Mattia Astorino (http://github.com/equinusocio) + Website: https://material-theme.site/ +*/ + +.cm-s-material.CodeMirror { + background-color: #263238; + color: #EEFFFF; +} + +.cm-s-material .CodeMirror-gutters { + background: #263238; + color: #546E7A; + border: none; +} + +.cm-s-material .CodeMirror-guttermarker, +.cm-s-material .CodeMirror-guttermarker-subtle, +.cm-s-material .CodeMirror-linenumber { + color: #546E7A; +} + +.cm-s-material .CodeMirror-cursor { + border-left: 1px solid #FFCC00; +} + +.cm-s-material div.CodeMirror-selected { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-material.CodeMirror-focused div.CodeMirror-selected { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-material .CodeMirror-line::selection, +.cm-s-material .CodeMirror-line>span::selection, +.cm-s-material .CodeMirror-line>span>span::selection { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-material .CodeMirror-line::-moz-selection, +.cm-s-material .CodeMirror-line>span::-moz-selection, +.cm-s-material .CodeMirror-line>span>span::-moz-selection { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-material .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.5); +} + +.cm-s-material .cm-keyword { + color: #C792EA; +} + +.cm-s-material .cm-operator { + color: #89DDFF; +} + +.cm-s-material .cm-variable-2 { + color: #EEFFFF; +} + +.cm-s-material .cm-variable-3, +.cm-s-material .cm-type { + color: #f07178; +} + +.cm-s-material .cm-builtin { + color: #FFCB6B; +} + +.cm-s-material .cm-atom { + color: #F78C6C; +} + +.cm-s-material .cm-number { + color: #FF5370; +} + +.cm-s-material .cm-def { + color: #82AAFF; +} + +.cm-s-material .cm-string { + color: #C3E88D; +} + +.cm-s-material .cm-string-2 { + color: #f07178; +} + +.cm-s-material .cm-comment { + color: #546E7A; +} + +.cm-s-material .cm-variable { + color: #f07178; +} + +.cm-s-material .cm-tag { + color: #FF5370; +} + +.cm-s-material .cm-meta { + color: #FFCB6B; +} + +.cm-s-material .cm-attribute { + color: #C792EA; +} + +.cm-s-material .cm-property { + color: #C792EA; +} + +.cm-s-material .cm-qualifier { + color: #DECB6B; +} + +.cm-s-material .cm-variable-3, +.cm-s-material .cm-type { + color: #DECB6B; +} + + +.cm-s-material .cm-error { + color: rgba(255, 255, 255, 1.0); + background-color: #FF5370; +} + +.cm-s-material .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} \ No newline at end of file diff --git a/docs/js/node_modules/codemirror/theme/mbo.css b/docs/js/node_modules/codemirror/theme/mbo.css new file mode 100644 index 000000000..e164fcf42 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/mbo.css @@ -0,0 +1,37 @@ +/****************************************************************/ +/* Based on mbonaci's Brackets mbo theme */ +/* https://github.com/mbonaci/global/blob/master/Mbo.tmTheme */ +/* Create your own: http://tmtheme-editor.herokuapp.com */ +/****************************************************************/ + +.cm-s-mbo.CodeMirror { background: #2c2c2c; color: #ffffec; } +.cm-s-mbo div.CodeMirror-selected { background: #716C62; } +.cm-s-mbo .CodeMirror-line::selection, .cm-s-mbo .CodeMirror-line > span::selection, .cm-s-mbo .CodeMirror-line > span > span::selection { background: rgba(113, 108, 98, .99); } +.cm-s-mbo .CodeMirror-line::-moz-selection, .cm-s-mbo .CodeMirror-line > span::-moz-selection, .cm-s-mbo .CodeMirror-line > span > span::-moz-selection { background: rgba(113, 108, 98, .99); } +.cm-s-mbo .CodeMirror-gutters { background: #4e4e4e; border-right: 0px; } +.cm-s-mbo .CodeMirror-guttermarker { color: white; } +.cm-s-mbo .CodeMirror-guttermarker-subtle { color: grey; } +.cm-s-mbo .CodeMirror-linenumber { color: #dadada; } +.cm-s-mbo .CodeMirror-cursor { border-left: 1px solid #ffffec; } + +.cm-s-mbo span.cm-comment { color: #95958a; } +.cm-s-mbo span.cm-atom { color: #00a8c6; } +.cm-s-mbo span.cm-number { color: #00a8c6; } + +.cm-s-mbo span.cm-property, .cm-s-mbo span.cm-attribute { color: #9ddfe9; } +.cm-s-mbo span.cm-keyword { color: #ffb928; } +.cm-s-mbo span.cm-string { color: #ffcf6c; } +.cm-s-mbo span.cm-string.cm-property { color: #ffffec; } + +.cm-s-mbo span.cm-variable { color: #ffffec; } +.cm-s-mbo span.cm-variable-2 { color: #00a8c6; } +.cm-s-mbo span.cm-def { color: #ffffec; } +.cm-s-mbo span.cm-bracket { color: #fffffc; font-weight: bold; } +.cm-s-mbo span.cm-tag { color: #9ddfe9; } +.cm-s-mbo span.cm-link { color: #f54b07; } +.cm-s-mbo span.cm-error { border-bottom: #636363; color: #ffffec; } +.cm-s-mbo span.cm-qualifier { color: #ffffec; } + +.cm-s-mbo .CodeMirror-activeline-background { background: #494b41; } +.cm-s-mbo .CodeMirror-matchingbracket { color: #ffb928 !important; } +.cm-s-mbo .CodeMirror-matchingtag { background: rgba(255, 255, 255, .37); } diff --git a/docs/js/node_modules/codemirror/theme/mdn-like.css b/docs/js/node_modules/codemirror/theme/mdn-like.css new file mode 100644 index 000000000..622ed3efb --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/mdn-like.css @@ -0,0 +1,46 @@ +/* + MDN-LIKE Theme - Mozilla + Ported to CodeMirror by Peter Kroon + Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues + GitHub: @peterkroon + + The mdn-like theme is inspired on the displayed code examples at: https://developer.mozilla.org/en-US/docs/Web/CSS/animation + +*/ +.cm-s-mdn-like.CodeMirror { color: #999; background-color: #fff; } +.cm-s-mdn-like div.CodeMirror-selected { background: #cfc; } +.cm-s-mdn-like .CodeMirror-line::selection, .cm-s-mdn-like .CodeMirror-line > span::selection, .cm-s-mdn-like .CodeMirror-line > span > span::selection { background: #cfc; } +.cm-s-mdn-like .CodeMirror-line::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span > span::-moz-selection { background: #cfc; } + +.cm-s-mdn-like .CodeMirror-gutters { background: #f8f8f8; border-left: 6px solid rgba(0,83,159,0.65); color: #333; } +.cm-s-mdn-like .CodeMirror-linenumber { color: #aaa; padding-left: 8px; } +.cm-s-mdn-like .CodeMirror-cursor { border-left: 2px solid #222; } + +.cm-s-mdn-like .cm-keyword { color: #6262FF; } +.cm-s-mdn-like .cm-atom { color: #F90; } +.cm-s-mdn-like .cm-number { color: #ca7841; } +.cm-s-mdn-like .cm-def { color: #8DA6CE; } +.cm-s-mdn-like span.cm-variable-2, .cm-s-mdn-like span.cm-tag { color: #690; } +.cm-s-mdn-like span.cm-variable-3, .cm-s-mdn-like span.cm-def, .cm-s-mdn-like span.cm-type { color: #07a; } + +.cm-s-mdn-like .cm-variable { color: #07a; } +.cm-s-mdn-like .cm-property { color: #905; } +.cm-s-mdn-like .cm-qualifier { color: #690; } + +.cm-s-mdn-like .cm-operator { color: #cda869; } +.cm-s-mdn-like .cm-comment { color:#777; font-weight:normal; } +.cm-s-mdn-like .cm-string { color:#07a; font-style:italic; } +.cm-s-mdn-like .cm-string-2 { color:#bd6b18; } /*?*/ +.cm-s-mdn-like .cm-meta { color: #000; } /*?*/ +.cm-s-mdn-like .cm-builtin { color: #9B7536; } /*?*/ +.cm-s-mdn-like .cm-tag { color: #997643; } +.cm-s-mdn-like .cm-attribute { color: #d6bb6d; } /*?*/ +.cm-s-mdn-like .cm-header { color: #FF6400; } +.cm-s-mdn-like .cm-hr { color: #AEAEAE; } +.cm-s-mdn-like .cm-link { color:#ad9361; font-style:italic; text-decoration:none; } +.cm-s-mdn-like .cm-error { border-bottom: 1px solid red; } + +div.cm-s-mdn-like .CodeMirror-activeline-background { background: #efefff; } +div.cm-s-mdn-like span.CodeMirror-matchingbracket { outline:1px solid grey; color: inherit; } + +.cm-s-mdn-like.CodeMirror { background-image: url(data:image/png;base64,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); } diff --git a/docs/js/node_modules/codemirror/theme/midnight.css b/docs/js/node_modules/codemirror/theme/midnight.css new file mode 100644 index 000000000..fc26474a4 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/midnight.css @@ -0,0 +1,39 @@ +/* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */ + +/**/ +.cm-s-midnight .CodeMirror-activeline-background { background: #253540; } + +.cm-s-midnight.CodeMirror { + background: #0F192A; + color: #D1EDFF; +} + +.cm-s-midnight div.CodeMirror-selected { background: #314D67; } +.cm-s-midnight .CodeMirror-line::selection, .cm-s-midnight .CodeMirror-line > span::selection, .cm-s-midnight .CodeMirror-line > span > span::selection { background: rgba(49, 77, 103, .99); } +.cm-s-midnight .CodeMirror-line::-moz-selection, .cm-s-midnight .CodeMirror-line > span::-moz-selection, .cm-s-midnight .CodeMirror-line > span > span::-moz-selection { background: rgba(49, 77, 103, .99); } +.cm-s-midnight .CodeMirror-gutters { background: #0F192A; border-right: 1px solid; } +.cm-s-midnight .CodeMirror-guttermarker { color: white; } +.cm-s-midnight .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-midnight .CodeMirror-linenumber { color: #D0D0D0; } +.cm-s-midnight .CodeMirror-cursor { border-left: 1px solid #F8F8F0; } + +.cm-s-midnight span.cm-comment { color: #428BDD; } +.cm-s-midnight span.cm-atom { color: #AE81FF; } +.cm-s-midnight span.cm-number { color: #D1EDFF; } + +.cm-s-midnight span.cm-property, .cm-s-midnight span.cm-attribute { color: #A6E22E; } +.cm-s-midnight span.cm-keyword { color: #E83737; } +.cm-s-midnight span.cm-string { color: #1DC116; } + +.cm-s-midnight span.cm-variable { color: #FFAA3E; } +.cm-s-midnight span.cm-variable-2 { color: #FFAA3E; } +.cm-s-midnight span.cm-def { color: #4DD; } +.cm-s-midnight span.cm-bracket { color: #D1EDFF; } +.cm-s-midnight span.cm-tag { color: #449; } +.cm-s-midnight span.cm-link { color: #AE81FF; } +.cm-s-midnight span.cm-error { background: #F92672; color: #F8F8F0; } + +.cm-s-midnight .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} diff --git a/docs/js/node_modules/codemirror/theme/monokai.css b/docs/js/node_modules/codemirror/theme/monokai.css new file mode 100644 index 000000000..cd4cd5572 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/monokai.css @@ -0,0 +1,41 @@ +/* Based on Sublime Text's Monokai theme */ + +.cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; } +.cm-s-monokai div.CodeMirror-selected { background: #49483E; } +.cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); } +.cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); } +.cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; } +.cm-s-monokai .CodeMirror-guttermarker { color: white; } +.cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } + +.cm-s-monokai span.cm-comment { color: #75715e; } +.cm-s-monokai span.cm-atom { color: #ae81ff; } +.cm-s-monokai span.cm-number { color: #ae81ff; } + +.cm-s-monokai span.cm-comment.cm-attribute { color: #97b757; } +.cm-s-monokai span.cm-comment.cm-def { color: #bc9262; } +.cm-s-monokai span.cm-comment.cm-tag { color: #bc6283; } +.cm-s-monokai span.cm-comment.cm-type { color: #5998a6; } + +.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; } +.cm-s-monokai span.cm-keyword { color: #f92672; } +.cm-s-monokai span.cm-builtin { color: #66d9ef; } +.cm-s-monokai span.cm-string { color: #e6db74; } + +.cm-s-monokai span.cm-variable { color: #f8f8f2; } +.cm-s-monokai span.cm-variable-2 { color: #9effff; } +.cm-s-monokai span.cm-variable-3, .cm-s-monokai span.cm-type { color: #66d9ef; } +.cm-s-monokai span.cm-def { color: #fd971f; } +.cm-s-monokai span.cm-bracket { color: #f8f8f2; } +.cm-s-monokai span.cm-tag { color: #f92672; } +.cm-s-monokai span.cm-header { color: #ae81ff; } +.cm-s-monokai span.cm-link { color: #ae81ff; } +.cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; } + +.cm-s-monokai .CodeMirror-activeline-background { background: #373831; } +.cm-s-monokai .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} diff --git a/docs/js/node_modules/codemirror/theme/moxer.css b/docs/js/node_modules/codemirror/theme/moxer.css new file mode 100644 index 000000000..b3ca35e38 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/moxer.css @@ -0,0 +1,143 @@ +/* + Name: Moxer Theme + Author: Mattia Astorino (http://github.com/equinusocio) + Website: https://github.com/moxer-theme/moxer-code +*/ + +.cm-s-moxer.CodeMirror { + background-color: #090A0F; + color: #8E95B4; + line-height: 1.8; +} + +.cm-s-moxer .CodeMirror-gutters { + background: #090A0F; + color: #35394B; + border: none; +} + +.cm-s-moxer .CodeMirror-guttermarker, +.cm-s-moxer .CodeMirror-guttermarker-subtle, +.cm-s-moxer .CodeMirror-linenumber { + color: #35394B; +} + + +.cm-s-moxer .CodeMirror-cursor { + border-left: 1px solid #FFCC00; +} + +.cm-s-moxer div.CodeMirror-selected { + background: rgba(128, 203, 196, 0.2); +} + +.cm-s-moxer.CodeMirror-focused div.CodeMirror-selected { + background: #212431; +} + +.cm-s-moxer .CodeMirror-line::selection, +.cm-s-moxer .CodeMirror-line>span::selection, +.cm-s-moxer .CodeMirror-line>span>span::selection { + background: #212431; +} + +.cm-s-moxer .CodeMirror-line::-moz-selection, +.cm-s-moxer .CodeMirror-line>span::-moz-selection, +.cm-s-moxer .CodeMirror-line>span>span::-moz-selection { + background: #212431; +} + +.cm-s-moxer .CodeMirror-activeline-background, +.cm-s-moxer .CodeMirror-activeline-gutter .CodeMirror-linenumber { + background: rgba(33, 36, 49, 0.5); +} + +.cm-s-moxer .cm-keyword { + color: #D46C6C; +} + +.cm-s-moxer .cm-operator { + color: #D46C6C; +} + +.cm-s-moxer .cm-variable-2 { + color: #81C5DA; +} + + +.cm-s-moxer .cm-variable-3, +.cm-s-moxer .cm-type { + color: #f07178; +} + +.cm-s-moxer .cm-builtin { + color: #FFCB6B; +} + +.cm-s-moxer .cm-atom { + color: #A99BE2; +} + +.cm-s-moxer .cm-number { + color: #7CA4C0; +} + +.cm-s-moxer .cm-def { + color: #F5DFA5; +} + +.cm-s-moxer .CodeMirror-line .cm-def ~ .cm-def { + color: #81C5DA; +} + +.cm-s-moxer .cm-string { + color: #B2E4AE; +} + +.cm-s-moxer .cm-string-2 { + color: #f07178; +} + +.cm-s-moxer .cm-comment { + color: #3F445A; +} + +.cm-s-moxer .cm-variable { + color: #8E95B4; +} + +.cm-s-moxer .cm-tag { + color: #FF5370; +} + +.cm-s-moxer .cm-meta { + color: #FFCB6B; +} + +.cm-s-moxer .cm-attribute { + color: #C792EA; +} + +.cm-s-moxer .cm-property { + color: #81C5DA; +} + +.cm-s-moxer .cm-qualifier { + color: #DECB6B; +} + +.cm-s-moxer .cm-variable-3, +.cm-s-moxer .cm-type { + color: #DECB6B; +} + + +.cm-s-moxer .cm-error { + color: rgba(255, 255, 255, 1.0); + background-color: #FF5370; +} + +.cm-s-moxer .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} \ No newline at end of file diff --git a/docs/js/node_modules/codemirror/theme/neat.css b/docs/js/node_modules/codemirror/theme/neat.css new file mode 100644 index 000000000..4267b1a37 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/neat.css @@ -0,0 +1,12 @@ +.cm-s-neat span.cm-comment { color: #a86; } +.cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; } +.cm-s-neat span.cm-string { color: #a22; } +.cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; } +.cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; } +.cm-s-neat span.cm-variable { color: black; } +.cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; } +.cm-s-neat span.cm-meta { color: #555; } +.cm-s-neat span.cm-link { color: #3a3; } + +.cm-s-neat .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-neat .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } diff --git a/docs/js/node_modules/codemirror/theme/neo.css b/docs/js/node_modules/codemirror/theme/neo.css new file mode 100644 index 000000000..b28d5c65f --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/neo.css @@ -0,0 +1,43 @@ +/* neo theme for codemirror */ + +/* Color scheme */ + +.cm-s-neo.CodeMirror { + background-color:#ffffff; + color:#2e383c; + line-height:1.4375; +} +.cm-s-neo .cm-comment { color:#75787b; } +.cm-s-neo .cm-keyword, .cm-s-neo .cm-property { color:#1d75b3; } +.cm-s-neo .cm-atom,.cm-s-neo .cm-number { color:#75438a; } +.cm-s-neo .cm-node,.cm-s-neo .cm-tag { color:#9c3328; } +.cm-s-neo .cm-string { color:#b35e14; } +.cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier { color:#047d65; } + + +/* Editor styling */ + +.cm-s-neo pre { + padding:0; +} + +.cm-s-neo .CodeMirror-gutters { + border:none; + border-right:10px solid transparent; + background-color:transparent; +} + +.cm-s-neo .CodeMirror-linenumber { + padding:0; + color:#e0e2e5; +} + +.cm-s-neo .CodeMirror-guttermarker { color: #1d75b3; } +.cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5; } + +.cm-s-neo .CodeMirror-cursor { + width: auto; + border: 0; + background: rgba(155,157,162,0.37); + z-index: 1; +} diff --git a/docs/js/node_modules/codemirror/theme/night.css b/docs/js/node_modules/codemirror/theme/night.css new file mode 100644 index 000000000..f631bf42c --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/night.css @@ -0,0 +1,27 @@ +/* Loosely based on the Midnight Textmate theme */ + +.cm-s-night.CodeMirror { background: #0a001f; color: #f8f8f8; } +.cm-s-night div.CodeMirror-selected { background: #447; } +.cm-s-night .CodeMirror-line::selection, .cm-s-night .CodeMirror-line > span::selection, .cm-s-night .CodeMirror-line > span > span::selection { background: rgba(68, 68, 119, .99); } +.cm-s-night .CodeMirror-line::-moz-selection, .cm-s-night .CodeMirror-line > span::-moz-selection, .cm-s-night .CodeMirror-line > span > span::-moz-selection { background: rgba(68, 68, 119, .99); } +.cm-s-night .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } +.cm-s-night .CodeMirror-guttermarker { color: white; } +.cm-s-night .CodeMirror-guttermarker-subtle { color: #bbb; } +.cm-s-night .CodeMirror-linenumber { color: #f8f8f8; } +.cm-s-night .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-night span.cm-comment { color: #8900d1; } +.cm-s-night span.cm-atom { color: #845dc4; } +.cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; } +.cm-s-night span.cm-keyword { color: #599eff; } +.cm-s-night span.cm-string { color: #37f14a; } +.cm-s-night span.cm-meta { color: #7678e2; } +.cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; } +.cm-s-night span.cm-variable-3, .cm-s-night span.cm-def, .cm-s-night span.cm-type { color: white; } +.cm-s-night span.cm-bracket { color: #8da6ce; } +.cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; } +.cm-s-night span.cm-link { color: #845dc4; } +.cm-s-night span.cm-error { color: #9d1e15; } + +.cm-s-night .CodeMirror-activeline-background { background: #1C005A; } +.cm-s-night .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/docs/js/node_modules/codemirror/theme/nord.css b/docs/js/node_modules/codemirror/theme/nord.css new file mode 100644 index 000000000..41a8ad778 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/nord.css @@ -0,0 +1,42 @@ +/* Based on arcticicestudio's Nord theme */ +/* https://github.com/arcticicestudio/nord */ + +.cm-s-nord.CodeMirror { background: #2e3440; color: #d8dee9; } +.cm-s-nord div.CodeMirror-selected { background: #434c5e; } +.cm-s-nord .CodeMirror-line::selection, .cm-s-nord .CodeMirror-line > span::selection, .cm-s-nord .CodeMirror-line > span > span::selection { background: #3b4252; } +.cm-s-nord .CodeMirror-line::-moz-selection, .cm-s-nord .CodeMirror-line > span::-moz-selection, .cm-s-nord .CodeMirror-line > span > span::-moz-selection { background: #3b4252; } +.cm-s-nord .CodeMirror-gutters { background: #2e3440; border-right: 0px; } +.cm-s-nord .CodeMirror-guttermarker { color: #4c566a; } +.cm-s-nord .CodeMirror-guttermarker-subtle { color: #4c566a; } +.cm-s-nord .CodeMirror-linenumber { color: #4c566a; } +.cm-s-nord .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } + +.cm-s-nord span.cm-comment { color: #4c566a; } +.cm-s-nord span.cm-atom { color: #b48ead; } +.cm-s-nord span.cm-number { color: #b48ead; } + +.cm-s-nord span.cm-comment.cm-attribute { color: #97b757; } +.cm-s-nord span.cm-comment.cm-def { color: #bc9262; } +.cm-s-nord span.cm-comment.cm-tag { color: #bc6283; } +.cm-s-nord span.cm-comment.cm-type { color: #5998a6; } + +.cm-s-nord span.cm-property, .cm-s-nord span.cm-attribute { color: #8FBCBB; } +.cm-s-nord span.cm-keyword { color: #81A1C1; } +.cm-s-nord span.cm-builtin { color: #81A1C1; } +.cm-s-nord span.cm-string { color: #A3BE8C; } + +.cm-s-nord span.cm-variable { color: #d8dee9; } +.cm-s-nord span.cm-variable-2 { color: #d8dee9; } +.cm-s-nord span.cm-variable-3, .cm-s-nord span.cm-type { color: #d8dee9; } +.cm-s-nord span.cm-def { color: #8FBCBB; } +.cm-s-nord span.cm-bracket { color: #81A1C1; } +.cm-s-nord span.cm-tag { color: #bf616a; } +.cm-s-nord span.cm-header { color: #b48ead; } +.cm-s-nord span.cm-link { color: #b48ead; } +.cm-s-nord span.cm-error { background: #bf616a; color: #f8f8f0; } + +.cm-s-nord .CodeMirror-activeline-background { background: #3b4252; } +.cm-s-nord .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} diff --git a/docs/js/node_modules/codemirror/theme/oceanic-next.css b/docs/js/node_modules/codemirror/theme/oceanic-next.css new file mode 100644 index 000000000..296277ba0 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/oceanic-next.css @@ -0,0 +1,44 @@ +/* + + Name: oceanic-next + Author: Filype Pereira (https://github.com/fpereira1) + + Original oceanic-next color scheme by Dmitri Voronianski (https://github.com/voronianski/oceanic-next-color-scheme) + +*/ + +.cm-s-oceanic-next.CodeMirror { background: #304148; color: #f8f8f2; } +.cm-s-oceanic-next div.CodeMirror-selected { background: rgba(101, 115, 126, 0.33); } +.cm-s-oceanic-next .CodeMirror-line::selection, .cm-s-oceanic-next .CodeMirror-line > span::selection, .cm-s-oceanic-next .CodeMirror-line > span > span::selection { background: rgba(101, 115, 126, 0.33); } +.cm-s-oceanic-next .CodeMirror-line::-moz-selection, .cm-s-oceanic-next .CodeMirror-line > span::-moz-selection, .cm-s-oceanic-next .CodeMirror-line > span > span::-moz-selection { background: rgba(101, 115, 126, 0.33); } +.cm-s-oceanic-next .CodeMirror-gutters { background: #304148; border-right: 10px; } +.cm-s-oceanic-next .CodeMirror-guttermarker { color: white; } +.cm-s-oceanic-next .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-oceanic-next .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-oceanic-next .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } + +.cm-s-oceanic-next span.cm-comment { color: #65737E; } +.cm-s-oceanic-next span.cm-atom { color: #C594C5; } +.cm-s-oceanic-next span.cm-number { color: #F99157; } + +.cm-s-oceanic-next span.cm-property { color: #99C794; } +.cm-s-oceanic-next span.cm-attribute, +.cm-s-oceanic-next span.cm-keyword { color: #C594C5; } +.cm-s-oceanic-next span.cm-builtin { color: #66d9ef; } +.cm-s-oceanic-next span.cm-string { color: #99C794; } + +.cm-s-oceanic-next span.cm-variable, +.cm-s-oceanic-next span.cm-variable-2, +.cm-s-oceanic-next span.cm-variable-3 { color: #f8f8f2; } +.cm-s-oceanic-next span.cm-def { color: #6699CC; } +.cm-s-oceanic-next span.cm-bracket { color: #5FB3B3; } +.cm-s-oceanic-next span.cm-tag { color: #C594C5; } +.cm-s-oceanic-next span.cm-header { color: #C594C5; } +.cm-s-oceanic-next span.cm-link { color: #C594C5; } +.cm-s-oceanic-next span.cm-error { background: #C594C5; color: #f8f8f0; } + +.cm-s-oceanic-next .CodeMirror-activeline-background { background: rgba(101, 115, 126, 0.33); } +.cm-s-oceanic-next .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} diff --git a/docs/js/node_modules/codemirror/theme/panda-syntax.css b/docs/js/node_modules/codemirror/theme/panda-syntax.css new file mode 100644 index 000000000..de14e9112 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/panda-syntax.css @@ -0,0 +1,85 @@ +/* + Name: Panda Syntax + Author: Siamak Mokhtari (http://github.com/siamak/) + CodeMirror template by Siamak Mokhtari (https://github.com/siamak/atom-panda-syntax) +*/ +.cm-s-panda-syntax { + background: #292A2B; + color: #E6E6E6; + line-height: 1.5; + font-family: 'Operator Mono', 'Source Code Pro', Menlo, Monaco, Consolas, Courier New, monospace; +} +.cm-s-panda-syntax .CodeMirror-cursor { border-color: #ff2c6d; } +.cm-s-panda-syntax .CodeMirror-activeline-background { + background: rgba(99, 123, 156, 0.1); +} +.cm-s-panda-syntax .CodeMirror-selected { + background: #FFF; +} +.cm-s-panda-syntax .cm-comment { + font-style: italic; + color: #676B79; +} +.cm-s-panda-syntax .cm-operator { + color: #f3f3f3; +} +.cm-s-panda-syntax .cm-string { + color: #19F9D8; +} +.cm-s-panda-syntax .cm-string-2 { + color: #FFB86C; +} + +.cm-s-panda-syntax .cm-tag { + color: #ff2c6d; +} +.cm-s-panda-syntax .cm-meta { + color: #b084eb; +} + +.cm-s-panda-syntax .cm-number { + color: #FFB86C; +} +.cm-s-panda-syntax .cm-atom { + color: #ff2c6d; +} +.cm-s-panda-syntax .cm-keyword { + color: #FF75B5; +} +.cm-s-panda-syntax .cm-variable { + color: #ffb86c; +} +.cm-s-panda-syntax .cm-variable-2 { + color: #ff9ac1; +} +.cm-s-panda-syntax .cm-variable-3, .cm-s-panda-syntax .cm-type { + color: #ff9ac1; +} + +.cm-s-panda-syntax .cm-def { + color: #e6e6e6; +} +.cm-s-panda-syntax .cm-property { + color: #f3f3f3; +} +.cm-s-panda-syntax .cm-unit { + color: #ffb86c; +} + +.cm-s-panda-syntax .cm-attribute { + color: #ffb86c; +} + +.cm-s-panda-syntax .CodeMirror-matchingbracket { + border-bottom: 1px dotted #19F9D8; + padding-bottom: 2px; + color: #e6e6e6; +} +.cm-s-panda-syntax .CodeMirror-gutters { + background: #292a2b; + border-right-color: rgba(255, 255, 255, 0.1); +} +.cm-s-panda-syntax .CodeMirror-linenumber { + color: #e6e6e6; + opacity: 0.6; +} diff --git a/docs/js/node_modules/codemirror/theme/paraiso-dark.css b/docs/js/node_modules/codemirror/theme/paraiso-dark.css new file mode 100644 index 000000000..aa9d207e6 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/paraiso-dark.css @@ -0,0 +1,38 @@ +/* + + Name: Paraíso (Dark) + Author: Jan T. Sott + + Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) + +*/ + +.cm-s-paraiso-dark.CodeMirror { background: #2f1e2e; color: #b9b6b0; } +.cm-s-paraiso-dark div.CodeMirror-selected { background: #41323f; } +.cm-s-paraiso-dark .CodeMirror-line::selection, .cm-s-paraiso-dark .CodeMirror-line > span::selection, .cm-s-paraiso-dark .CodeMirror-line > span > span::selection { background: rgba(65, 50, 63, .99); } +.cm-s-paraiso-dark .CodeMirror-line::-moz-selection, .cm-s-paraiso-dark .CodeMirror-line > span::-moz-selection, .cm-s-paraiso-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(65, 50, 63, .99); } +.cm-s-paraiso-dark .CodeMirror-gutters { background: #2f1e2e; border-right: 0px; } +.cm-s-paraiso-dark .CodeMirror-guttermarker { color: #ef6155; } +.cm-s-paraiso-dark .CodeMirror-guttermarker-subtle { color: #776e71; } +.cm-s-paraiso-dark .CodeMirror-linenumber { color: #776e71; } +.cm-s-paraiso-dark .CodeMirror-cursor { border-left: 1px solid #8d8687; } + +.cm-s-paraiso-dark span.cm-comment { color: #e96ba8; } +.cm-s-paraiso-dark span.cm-atom { color: #815ba4; } +.cm-s-paraiso-dark span.cm-number { color: #815ba4; } + +.cm-s-paraiso-dark span.cm-property, .cm-s-paraiso-dark span.cm-attribute { color: #48b685; } +.cm-s-paraiso-dark span.cm-keyword { color: #ef6155; } +.cm-s-paraiso-dark span.cm-string { color: #fec418; } + +.cm-s-paraiso-dark span.cm-variable { color: #48b685; } +.cm-s-paraiso-dark span.cm-variable-2 { color: #06b6ef; } +.cm-s-paraiso-dark span.cm-def { color: #f99b15; } +.cm-s-paraiso-dark span.cm-bracket { color: #b9b6b0; } +.cm-s-paraiso-dark span.cm-tag { color: #ef6155; } +.cm-s-paraiso-dark span.cm-link { color: #815ba4; } +.cm-s-paraiso-dark span.cm-error { background: #ef6155; color: #8d8687; } + +.cm-s-paraiso-dark .CodeMirror-activeline-background { background: #4D344A; } +.cm-s-paraiso-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/docs/js/node_modules/codemirror/theme/paraiso-light.css b/docs/js/node_modules/codemirror/theme/paraiso-light.css new file mode 100644 index 000000000..ae0c755f8 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/paraiso-light.css @@ -0,0 +1,38 @@ +/* + + Name: Paraíso (Light) + Author: Jan T. Sott + + Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) + +*/ + +.cm-s-paraiso-light.CodeMirror { background: #e7e9db; color: #41323f; } +.cm-s-paraiso-light div.CodeMirror-selected { background: #b9b6b0; } +.cm-s-paraiso-light .CodeMirror-line::selection, .cm-s-paraiso-light .CodeMirror-line > span::selection, .cm-s-paraiso-light .CodeMirror-line > span > span::selection { background: #b9b6b0; } +.cm-s-paraiso-light .CodeMirror-line::-moz-selection, .cm-s-paraiso-light .CodeMirror-line > span::-moz-selection, .cm-s-paraiso-light .CodeMirror-line > span > span::-moz-selection { background: #b9b6b0; } +.cm-s-paraiso-light .CodeMirror-gutters { background: #e7e9db; border-right: 0px; } +.cm-s-paraiso-light .CodeMirror-guttermarker { color: black; } +.cm-s-paraiso-light .CodeMirror-guttermarker-subtle { color: #8d8687; } +.cm-s-paraiso-light .CodeMirror-linenumber { color: #8d8687; } +.cm-s-paraiso-light .CodeMirror-cursor { border-left: 1px solid #776e71; } + +.cm-s-paraiso-light span.cm-comment { color: #e96ba8; } +.cm-s-paraiso-light span.cm-atom { color: #815ba4; } +.cm-s-paraiso-light span.cm-number { color: #815ba4; } + +.cm-s-paraiso-light span.cm-property, .cm-s-paraiso-light span.cm-attribute { color: #48b685; } +.cm-s-paraiso-light span.cm-keyword { color: #ef6155; } +.cm-s-paraiso-light span.cm-string { color: #fec418; } + +.cm-s-paraiso-light span.cm-variable { color: #48b685; } +.cm-s-paraiso-light span.cm-variable-2 { color: #06b6ef; } +.cm-s-paraiso-light span.cm-def { color: #f99b15; } +.cm-s-paraiso-light span.cm-bracket { color: #41323f; } +.cm-s-paraiso-light span.cm-tag { color: #ef6155; } +.cm-s-paraiso-light span.cm-link { color: #815ba4; } +.cm-s-paraiso-light span.cm-error { background: #ef6155; color: #776e71; } + +.cm-s-paraiso-light .CodeMirror-activeline-background { background: #CFD1C4; } +.cm-s-paraiso-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/docs/js/node_modules/codemirror/theme/pastel-on-dark.css b/docs/js/node_modules/codemirror/theme/pastel-on-dark.css new file mode 100644 index 000000000..60435dd15 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/pastel-on-dark.css @@ -0,0 +1,52 @@ +/** + * Pastel On Dark theme ported from ACE editor + * @license MIT + * @copyright AtomicPages LLC 2014 + * @author Dennis Thompson, AtomicPages LLC + * @version 1.1 + * @source https://github.com/atomicpages/codemirror-pastel-on-dark-theme + */ + +.cm-s-pastel-on-dark.CodeMirror { + background: #2c2827; + color: #8F938F; + line-height: 1.5; +} +.cm-s-pastel-on-dark div.CodeMirror-selected { background: rgba(221,240,255,0.2); } +.cm-s-pastel-on-dark .CodeMirror-line::selection, .cm-s-pastel-on-dark .CodeMirror-line > span::selection, .cm-s-pastel-on-dark .CodeMirror-line > span > span::selection { background: rgba(221,240,255,0.2); } +.cm-s-pastel-on-dark .CodeMirror-line::-moz-selection, .cm-s-pastel-on-dark .CodeMirror-line > span::-moz-selection, .cm-s-pastel-on-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(221,240,255,0.2); } + +.cm-s-pastel-on-dark .CodeMirror-gutters { + background: #34302f; + border-right: 0px; + padding: 0 3px; +} +.cm-s-pastel-on-dark .CodeMirror-guttermarker { color: white; } +.cm-s-pastel-on-dark .CodeMirror-guttermarker-subtle { color: #8F938F; } +.cm-s-pastel-on-dark .CodeMirror-linenumber { color: #8F938F; } +.cm-s-pastel-on-dark .CodeMirror-cursor { border-left: 1px solid #A7A7A7; } +.cm-s-pastel-on-dark span.cm-comment { color: #A6C6FF; } +.cm-s-pastel-on-dark span.cm-atom { color: #DE8E30; } +.cm-s-pastel-on-dark span.cm-number { color: #CCCCCC; } +.cm-s-pastel-on-dark span.cm-property { color: #8F938F; } +.cm-s-pastel-on-dark span.cm-attribute { color: #a6e22e; } +.cm-s-pastel-on-dark span.cm-keyword { color: #AEB2F8; } +.cm-s-pastel-on-dark span.cm-string { color: #66A968; } +.cm-s-pastel-on-dark span.cm-variable { color: #AEB2F8; } +.cm-s-pastel-on-dark span.cm-variable-2 { color: #BEBF55; } +.cm-s-pastel-on-dark span.cm-variable-3, .cm-s-pastel-on-dark span.cm-type { color: #DE8E30; } +.cm-s-pastel-on-dark span.cm-def { color: #757aD8; } +.cm-s-pastel-on-dark span.cm-bracket { color: #f8f8f2; } +.cm-s-pastel-on-dark span.cm-tag { color: #C1C144; } +.cm-s-pastel-on-dark span.cm-link { color: #ae81ff; } +.cm-s-pastel-on-dark span.cm-qualifier,.cm-s-pastel-on-dark span.cm-builtin { color: #C1C144; } +.cm-s-pastel-on-dark span.cm-error { + background: #757aD8; + color: #f8f8f0; +} +.cm-s-pastel-on-dark .CodeMirror-activeline-background { background: rgba(255, 255, 255, 0.031); } +.cm-s-pastel-on-dark .CodeMirror-matchingbracket { + border: 1px solid rgba(255,255,255,0.25); + color: #8F938F !important; + margin: -1px -1px 0 -1px; +} diff --git a/docs/js/node_modules/codemirror/theme/railscasts.css b/docs/js/node_modules/codemirror/theme/railscasts.css new file mode 100644 index 000000000..aeff0449d --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/railscasts.css @@ -0,0 +1,34 @@ +/* + + Name: Railscasts + Author: Ryan Bates (http://railscasts.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-railscasts.CodeMirror {background: #2b2b2b; color: #f4f1ed;} +.cm-s-railscasts div.CodeMirror-selected {background: #272935 !important;} +.cm-s-railscasts .CodeMirror-gutters {background: #2b2b2b; border-right: 0px;} +.cm-s-railscasts .CodeMirror-linenumber {color: #5a647e;} +.cm-s-railscasts .CodeMirror-cursor {border-left: 1px solid #d4cfc9 !important;} + +.cm-s-railscasts span.cm-comment {color: #bc9458;} +.cm-s-railscasts span.cm-atom {color: #b6b3eb;} +.cm-s-railscasts span.cm-number {color: #b6b3eb;} + +.cm-s-railscasts span.cm-property, .cm-s-railscasts span.cm-attribute {color: #a5c261;} +.cm-s-railscasts span.cm-keyword {color: #da4939;} +.cm-s-railscasts span.cm-string {color: #ffc66d;} + +.cm-s-railscasts span.cm-variable {color: #a5c261;} +.cm-s-railscasts span.cm-variable-2 {color: #6d9cbe;} +.cm-s-railscasts span.cm-def {color: #cc7833;} +.cm-s-railscasts span.cm-error {background: #da4939; color: #d4cfc9;} +.cm-s-railscasts span.cm-bracket {color: #f4f1ed;} +.cm-s-railscasts span.cm-tag {color: #da4939;} +.cm-s-railscasts span.cm-link {color: #b6b3eb;} + +.cm-s-railscasts .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} +.cm-s-railscasts .CodeMirror-activeline-background { background: #303040; } diff --git a/docs/js/node_modules/codemirror/theme/rubyblue.css b/docs/js/node_modules/codemirror/theme/rubyblue.css new file mode 100644 index 000000000..1f181b06e --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/rubyblue.css @@ -0,0 +1,25 @@ +.cm-s-rubyblue.CodeMirror { background: #112435; color: white; } +.cm-s-rubyblue div.CodeMirror-selected { background: #38566F; } +.cm-s-rubyblue .CodeMirror-line::selection, .cm-s-rubyblue .CodeMirror-line > span::selection, .cm-s-rubyblue .CodeMirror-line > span > span::selection { background: rgba(56, 86, 111, 0.99); } +.cm-s-rubyblue .CodeMirror-line::-moz-selection, .cm-s-rubyblue .CodeMirror-line > span::-moz-selection, .cm-s-rubyblue .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 86, 111, 0.99); } +.cm-s-rubyblue .CodeMirror-gutters { background: #1F4661; border-right: 7px solid #3E7087; } +.cm-s-rubyblue .CodeMirror-guttermarker { color: white; } +.cm-s-rubyblue .CodeMirror-guttermarker-subtle { color: #3E7087; } +.cm-s-rubyblue .CodeMirror-linenumber { color: white; } +.cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-rubyblue span.cm-comment { color: #999; font-style:italic; line-height: 1em; } +.cm-s-rubyblue span.cm-atom { color: #F4C20B; } +.cm-s-rubyblue span.cm-number, .cm-s-rubyblue span.cm-attribute { color: #82C6E0; } +.cm-s-rubyblue span.cm-keyword { color: #F0F; } +.cm-s-rubyblue span.cm-string { color: #F08047; } +.cm-s-rubyblue span.cm-meta { color: #F0F; } +.cm-s-rubyblue span.cm-variable-2, .cm-s-rubyblue span.cm-tag { color: #7BD827; } +.cm-s-rubyblue span.cm-variable-3, .cm-s-rubyblue span.cm-def, .cm-s-rubyblue span.cm-type { color: white; } +.cm-s-rubyblue span.cm-bracket { color: #F0F; } +.cm-s-rubyblue span.cm-link { color: #F4C20B; } +.cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; } +.cm-s-rubyblue span.cm-builtin, .cm-s-rubyblue span.cm-special { color: #FF9D00; } +.cm-s-rubyblue span.cm-error { color: #AF2018; } + +.cm-s-rubyblue .CodeMirror-activeline-background { background: #173047; } diff --git a/docs/js/node_modules/codemirror/theme/seti.css b/docs/js/node_modules/codemirror/theme/seti.css new file mode 100644 index 000000000..814f76f7d --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/seti.css @@ -0,0 +1,44 @@ +/* + + Name: seti + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original seti color scheme by Jesse Weed (https://github.com/jesseweed/seti-syntax) + +*/ + + +.cm-s-seti.CodeMirror { + background-color: #151718 !important; + color: #CFD2D1 !important; + border: none; +} +.cm-s-seti .CodeMirror-gutters { + color: #404b53; + background-color: #0E1112; + border: none; +} +.cm-s-seti .CodeMirror-cursor { border-left: solid thin #f8f8f0; } +.cm-s-seti .CodeMirror-linenumber { color: #6D8A88; } +.cm-s-seti.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } +.cm-s-seti .CodeMirror-line::selection, .cm-s-seti .CodeMirror-line > span::selection, .cm-s-seti .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-seti .CodeMirror-line::-moz-selection, .cm-s-seti .CodeMirror-line > span::-moz-selection, .cm-s-seti .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-seti span.cm-comment { color: #41535b; } +.cm-s-seti span.cm-string, .cm-s-seti span.cm-string-2 { color: #55b5db; } +.cm-s-seti span.cm-number { color: #cd3f45; } +.cm-s-seti span.cm-variable { color: #55b5db; } +.cm-s-seti span.cm-variable-2 { color: #a074c4; } +.cm-s-seti span.cm-def { color: #55b5db; } +.cm-s-seti span.cm-keyword { color: #ff79c6; } +.cm-s-seti span.cm-operator { color: #9fca56; } +.cm-s-seti span.cm-keyword { color: #e6cd69; } +.cm-s-seti span.cm-atom { color: #cd3f45; } +.cm-s-seti span.cm-meta { color: #55b5db; } +.cm-s-seti span.cm-tag { color: #55b5db; } +.cm-s-seti span.cm-attribute { color: #9fca56; } +.cm-s-seti span.cm-qualifier { color: #9fca56; } +.cm-s-seti span.cm-property { color: #a074c4; } +.cm-s-seti span.cm-variable-3, .cm-s-seti span.cm-type { color: #9fca56; } +.cm-s-seti span.cm-builtin { color: #9fca56; } +.cm-s-seti .CodeMirror-activeline-background { background: #101213; } +.cm-s-seti .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/docs/js/node_modules/codemirror/theme/shadowfox.css b/docs/js/node_modules/codemirror/theme/shadowfox.css new file mode 100644 index 000000000..32d59b139 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/shadowfox.css @@ -0,0 +1,52 @@ +/* + + Name: shadowfox + Author: overdodactyl (http://github.com/overdodactyl) + + Original shadowfox color scheme by Firefox + +*/ + +.cm-s-shadowfox.CodeMirror { background: #2a2a2e; color: #b1b1b3; } +.cm-s-shadowfox div.CodeMirror-selected { background: #353B48; } +.cm-s-shadowfox .CodeMirror-line::selection, .cm-s-shadowfox .CodeMirror-line > span::selection, .cm-s-shadowfox .CodeMirror-line > span > span::selection { background: #353B48; } +.cm-s-shadowfox .CodeMirror-line::-moz-selection, .cm-s-shadowfox .CodeMirror-line > span::-moz-selection, .cm-s-shadowfox .CodeMirror-line > span > span::-moz-selection { background: #353B48; } +.cm-s-shadowfox .CodeMirror-gutters { background: #0c0c0d ; border-right: 1px solid #0c0c0d; } +.cm-s-shadowfox .CodeMirror-guttermarker { color: #555; } +.cm-s-shadowfox .CodeMirror-linenumber { color: #939393; } +.cm-s-shadowfox .CodeMirror-cursor { border-left: 1px solid #fff; } + +.cm-s-shadowfox span.cm-comment { color: #939393; } +.cm-s-shadowfox span.cm-atom { color: #FF7DE9; } +.cm-s-shadowfox span.cm-quote { color: #FF7DE9; } +.cm-s-shadowfox span.cm-builtin { color: #FF7DE9; } +.cm-s-shadowfox span.cm-attribute { color: #FF7DE9; } +.cm-s-shadowfox span.cm-keyword { color: #FF7DE9; } +.cm-s-shadowfox span.cm-error { color: #FF7DE9; } + +.cm-s-shadowfox span.cm-number { color: #6B89FF; } +.cm-s-shadowfox span.cm-string { color: #6B89FF; } +.cm-s-shadowfox span.cm-string-2 { color: #6B89FF; } + +.cm-s-shadowfox span.cm-meta { color: #939393; } +.cm-s-shadowfox span.cm-hr { color: #939393; } + +.cm-s-shadowfox span.cm-header { color: #75BFFF; } +.cm-s-shadowfox span.cm-qualifier { color: #75BFFF; } +.cm-s-shadowfox span.cm-variable-2 { color: #75BFFF; } + +.cm-s-shadowfox span.cm-property { color: #86DE74; } + +.cm-s-shadowfox span.cm-def { color: #75BFFF; } +.cm-s-shadowfox span.cm-bracket { color: #75BFFF; } +.cm-s-shadowfox span.cm-tag { color: #75BFFF; } +.cm-s-shadowfox span.cm-link:visited { color: #75BFFF; } + +.cm-s-shadowfox span.cm-variable { color: #B98EFF; } +.cm-s-shadowfox span.cm-variable-3 { color: #d7d7db; } +.cm-s-shadowfox span.cm-link { color: #737373; } +.cm-s-shadowfox span.cm-operator { color: #b1b1b3; } +.cm-s-shadowfox span.cm-special { color: #d7d7db; } + +.cm-s-shadowfox .CodeMirror-activeline-background { background: rgba(185, 215, 253, .15) } +.cm-s-shadowfox .CodeMirror-matchingbracket { outline: solid 1px rgba(255, 255, 255, .25); color: white !important; } diff --git a/docs/js/node_modules/codemirror/theme/solarized.css b/docs/js/node_modules/codemirror/theme/solarized.css new file mode 100644 index 000000000..fcd1d70de --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/solarized.css @@ -0,0 +1,168 @@ +/* +Solarized theme for code-mirror +http://ethanschoonover.com/solarized +*/ + +/* +Solarized color palette +http://ethanschoonover.com/solarized/img/solarized-palette.png +*/ + +.solarized.base03 { color: #002b36; } +.solarized.base02 { color: #073642; } +.solarized.base01 { color: #586e75; } +.solarized.base00 { color: #657b83; } +.solarized.base0 { color: #839496; } +.solarized.base1 { color: #93a1a1; } +.solarized.base2 { color: #eee8d5; } +.solarized.base3 { color: #fdf6e3; } +.solarized.solar-yellow { color: #b58900; } +.solarized.solar-orange { color: #cb4b16; } +.solarized.solar-red { color: #dc322f; } +.solarized.solar-magenta { color: #d33682; } +.solarized.solar-violet { color: #6c71c4; } +.solarized.solar-blue { color: #268bd2; } +.solarized.solar-cyan { color: #2aa198; } +.solarized.solar-green { color: #859900; } + +/* Color scheme for code-mirror */ + +.cm-s-solarized { + line-height: 1.45em; + color-profile: sRGB; + rendering-intent: auto; +} +.cm-s-solarized.cm-s-dark { + color: #839496; + background-color: #002b36; + text-shadow: #002b36 0 1px; +} +.cm-s-solarized.cm-s-light { + background-color: #fdf6e3; + color: #657b83; + text-shadow: #eee8d5 0 1px; +} + +.cm-s-solarized .CodeMirror-widget { + text-shadow: none; +} + +.cm-s-solarized .cm-header { color: #586e75; } +.cm-s-solarized .cm-quote { color: #93a1a1; } + +.cm-s-solarized .cm-keyword { color: #cb4b16; } +.cm-s-solarized .cm-atom { color: #d33682; } +.cm-s-solarized .cm-number { color: #d33682; } +.cm-s-solarized .cm-def { color: #2aa198; } + +.cm-s-solarized .cm-variable { color: #839496; } +.cm-s-solarized .cm-variable-2 { color: #b58900; } +.cm-s-solarized .cm-variable-3, .cm-s-solarized .cm-type { color: #6c71c4; } + +.cm-s-solarized .cm-property { color: #2aa198; } +.cm-s-solarized .cm-operator { color: #6c71c4; } + +.cm-s-solarized .cm-comment { color: #586e75; font-style:italic; } + +.cm-s-solarized .cm-string { color: #859900; } +.cm-s-solarized .cm-string-2 { color: #b58900; } + +.cm-s-solarized .cm-meta { color: #859900; } +.cm-s-solarized .cm-qualifier { color: #b58900; } +.cm-s-solarized .cm-builtin { color: #d33682; } +.cm-s-solarized .cm-bracket { color: #cb4b16; } +.cm-s-solarized .CodeMirror-matchingbracket { color: #859900; } +.cm-s-solarized .CodeMirror-nonmatchingbracket { color: #dc322f; } +.cm-s-solarized .cm-tag { color: #93a1a1; } +.cm-s-solarized .cm-attribute { color: #2aa198; } +.cm-s-solarized .cm-hr { + color: transparent; + border-top: 1px solid #586e75; + display: block; +} +.cm-s-solarized .cm-link { color: #93a1a1; cursor: pointer; } +.cm-s-solarized .cm-special { color: #6c71c4; } +.cm-s-solarized .cm-em { + color: #999; + text-decoration: underline; + text-decoration-style: dotted; +} +.cm-s-solarized .cm-error, +.cm-s-solarized .cm-invalidchar { + color: #586e75; + border-bottom: 1px dotted #dc322f; +} + +.cm-s-solarized.cm-s-dark div.CodeMirror-selected { background: #073642; } +.cm-s-solarized.cm-s-dark.CodeMirror ::selection { background: rgba(7, 54, 66, 0.99); } +.cm-s-solarized.cm-s-dark .CodeMirror-line::-moz-selection, .cm-s-dark .CodeMirror-line > span::-moz-selection, .cm-s-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(7, 54, 66, 0.99); } + +.cm-s-solarized.cm-s-light div.CodeMirror-selected { background: #eee8d5; } +.cm-s-solarized.cm-s-light .CodeMirror-line::selection, .cm-s-light .CodeMirror-line > span::selection, .cm-s-light .CodeMirror-line > span > span::selection { background: #eee8d5; } +.cm-s-solarized.cm-s-light .CodeMirror-line::-moz-selection, .cm-s-ligh .CodeMirror-line > span::-moz-selection, .cm-s-ligh .CodeMirror-line > span > span::-moz-selection { background: #eee8d5; } + +/* Editor styling */ + + + +/* Little shadow on the view-port of the buffer view */ +.cm-s-solarized.CodeMirror { + -moz-box-shadow: inset 7px 0 12px -6px #000; + -webkit-box-shadow: inset 7px 0 12px -6px #000; + box-shadow: inset 7px 0 12px -6px #000; +} + +/* Remove gutter border */ +.cm-s-solarized .CodeMirror-gutters { + border-right: 0; +} + +/* Gutter colors and line number styling based of color scheme (dark / light) */ + +/* Dark */ +.cm-s-solarized.cm-s-dark .CodeMirror-gutters { + background-color: #073642; +} + +.cm-s-solarized.cm-s-dark .CodeMirror-linenumber { + color: #586e75; + text-shadow: #021014 0 -1px; +} + +/* Light */ +.cm-s-solarized.cm-s-light .CodeMirror-gutters { + background-color: #eee8d5; +} + +.cm-s-solarized.cm-s-light .CodeMirror-linenumber { + color: #839496; +} + +/* Common */ +.cm-s-solarized .CodeMirror-linenumber { + padding: 0 5px; +} +.cm-s-solarized .CodeMirror-guttermarker-subtle { color: #586e75; } +.cm-s-solarized.cm-s-dark .CodeMirror-guttermarker { color: #ddd; } +.cm-s-solarized.cm-s-light .CodeMirror-guttermarker { color: #cb4b16; } + +.cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text { + color: #586e75; +} + +/* Cursor */ +.cm-s-solarized .CodeMirror-cursor { border-left: 1px solid #819090; } + +/* Fat cursor */ +.cm-s-solarized.cm-s-light.cm-fat-cursor .CodeMirror-cursor { background: #77ee77; } +.cm-s-solarized.cm-s-light .cm-animate-fat-cursor { background-color: #77ee77; } +.cm-s-solarized.cm-s-dark.cm-fat-cursor .CodeMirror-cursor { background: #586e75; } +.cm-s-solarized.cm-s-dark .cm-animate-fat-cursor { background-color: #586e75; } + +/* Active line */ +.cm-s-solarized.cm-s-dark .CodeMirror-activeline-background { + background: rgba(255, 255, 255, 0.06); +} +.cm-s-solarized.cm-s-light .CodeMirror-activeline-background { + background: rgba(0, 0, 0, 0.06); +} diff --git a/docs/js/node_modules/codemirror/theme/ssms.css b/docs/js/node_modules/codemirror/theme/ssms.css new file mode 100644 index 000000000..9494c14c2 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/ssms.css @@ -0,0 +1,16 @@ +.cm-s-ssms span.cm-keyword { color: blue; } +.cm-s-ssms span.cm-comment { color: darkgreen; } +.cm-s-ssms span.cm-string { color: red; } +.cm-s-ssms span.cm-def { color: black; } +.cm-s-ssms span.cm-variable { color: black; } +.cm-s-ssms span.cm-variable-2 { color: black; } +.cm-s-ssms span.cm-atom { color: darkgray; } +.cm-s-ssms .CodeMirror-linenumber { color: teal; } +.cm-s-ssms .CodeMirror-activeline-background { background: #ffffff; } +.cm-s-ssms span.cm-string-2 { color: #FF00FF; } +.cm-s-ssms span.cm-operator, +.cm-s-ssms span.cm-bracket, +.cm-s-ssms span.cm-punctuation { color: darkgray; } +.cm-s-ssms .CodeMirror-gutters { border-right: 3px solid #ffee62; background-color: #ffffff; } +.cm-s-ssms div.CodeMirror-selected { background: #ADD6FF; } + diff --git a/docs/js/node_modules/codemirror/theme/the-matrix.css b/docs/js/node_modules/codemirror/theme/the-matrix.css new file mode 100644 index 000000000..c4c93c11e --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/the-matrix.css @@ -0,0 +1,30 @@ +.cm-s-the-matrix.CodeMirror { background: #000000; color: #00FF00; } +.cm-s-the-matrix div.CodeMirror-selected { background: #2D2D2D; } +.cm-s-the-matrix .CodeMirror-line::selection, .cm-s-the-matrix .CodeMirror-line > span::selection, .cm-s-the-matrix .CodeMirror-line > span > span::selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-the-matrix .CodeMirror-line::-moz-selection, .cm-s-the-matrix .CodeMirror-line > span::-moz-selection, .cm-s-the-matrix .CodeMirror-line > span > span::-moz-selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-the-matrix .CodeMirror-gutters { background: #060; border-right: 2px solid #00FF00; } +.cm-s-the-matrix .CodeMirror-guttermarker { color: #0f0; } +.cm-s-the-matrix .CodeMirror-guttermarker-subtle { color: white; } +.cm-s-the-matrix .CodeMirror-linenumber { color: #FFFFFF; } +.cm-s-the-matrix .CodeMirror-cursor { border-left: 1px solid #00FF00; } + +.cm-s-the-matrix span.cm-keyword { color: #008803; font-weight: bold; } +.cm-s-the-matrix span.cm-atom { color: #3FF; } +.cm-s-the-matrix span.cm-number { color: #FFB94F; } +.cm-s-the-matrix span.cm-def { color: #99C; } +.cm-s-the-matrix span.cm-variable { color: #F6C; } +.cm-s-the-matrix span.cm-variable-2 { color: #C6F; } +.cm-s-the-matrix span.cm-variable-3, .cm-s-the-matrix span.cm-type { color: #96F; } +.cm-s-the-matrix span.cm-property { color: #62FFA0; } +.cm-s-the-matrix span.cm-operator { color: #999; } +.cm-s-the-matrix span.cm-comment { color: #CCCCCC; } +.cm-s-the-matrix span.cm-string { color: #39C; } +.cm-s-the-matrix span.cm-meta { color: #C9F; } +.cm-s-the-matrix span.cm-qualifier { color: #FFF700; } +.cm-s-the-matrix span.cm-builtin { color: #30a; } +.cm-s-the-matrix span.cm-bracket { color: #cc7; } +.cm-s-the-matrix span.cm-tag { color: #FFBD40; } +.cm-s-the-matrix span.cm-attribute { color: #FFF700; } +.cm-s-the-matrix span.cm-error { color: #FF0000; } + +.cm-s-the-matrix .CodeMirror-activeline-background { background: #040; } diff --git a/docs/js/node_modules/codemirror/theme/tomorrow-night-bright.css b/docs/js/node_modules/codemirror/theme/tomorrow-night-bright.css new file mode 100644 index 000000000..b6dd4a927 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/tomorrow-night-bright.css @@ -0,0 +1,35 @@ +/* + + Name: Tomorrow Night - Bright + Author: Chris Kempson + + Port done by Gerard Braad + +*/ + +.cm-s-tomorrow-night-bright.CodeMirror { background: #000000; color: #eaeaea; } +.cm-s-tomorrow-night-bright div.CodeMirror-selected { background: #424242; } +.cm-s-tomorrow-night-bright .CodeMirror-gutters { background: #000000; border-right: 0px; } +.cm-s-tomorrow-night-bright .CodeMirror-guttermarker { color: #e78c45; } +.cm-s-tomorrow-night-bright .CodeMirror-guttermarker-subtle { color: #777; } +.cm-s-tomorrow-night-bright .CodeMirror-linenumber { color: #424242; } +.cm-s-tomorrow-night-bright .CodeMirror-cursor { border-left: 1px solid #6A6A6A; } + +.cm-s-tomorrow-night-bright span.cm-comment { color: #d27b53; } +.cm-s-tomorrow-night-bright span.cm-atom { color: #a16a94; } +.cm-s-tomorrow-night-bright span.cm-number { color: #a16a94; } + +.cm-s-tomorrow-night-bright span.cm-property, .cm-s-tomorrow-night-bright span.cm-attribute { color: #99cc99; } +.cm-s-tomorrow-night-bright span.cm-keyword { color: #d54e53; } +.cm-s-tomorrow-night-bright span.cm-string { color: #e7c547; } + +.cm-s-tomorrow-night-bright span.cm-variable { color: #b9ca4a; } +.cm-s-tomorrow-night-bright span.cm-variable-2 { color: #7aa6da; } +.cm-s-tomorrow-night-bright span.cm-def { color: #e78c45; } +.cm-s-tomorrow-night-bright span.cm-bracket { color: #eaeaea; } +.cm-s-tomorrow-night-bright span.cm-tag { color: #d54e53; } +.cm-s-tomorrow-night-bright span.cm-link { color: #a16a94; } +.cm-s-tomorrow-night-bright span.cm-error { background: #d54e53; color: #6A6A6A; } + +.cm-s-tomorrow-night-bright .CodeMirror-activeline-background { background: #2a2a2a; } +.cm-s-tomorrow-night-bright .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/docs/js/node_modules/codemirror/theme/tomorrow-night-eighties.css b/docs/js/node_modules/codemirror/theme/tomorrow-night-eighties.css new file mode 100644 index 000000000..2a9debc32 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/tomorrow-night-eighties.css @@ -0,0 +1,38 @@ +/* + + Name: Tomorrow Night - Eighties + Author: Chris Kempson + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-tomorrow-night-eighties.CodeMirror { background: #000000; color: #CCCCCC; } +.cm-s-tomorrow-night-eighties div.CodeMirror-selected { background: #2D2D2D; } +.cm-s-tomorrow-night-eighties .CodeMirror-line::selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span::selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span > span::selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-tomorrow-night-eighties .CodeMirror-line::-moz-selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span::-moz-selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span > span::-moz-selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-tomorrow-night-eighties .CodeMirror-gutters { background: #000000; border-right: 0px; } +.cm-s-tomorrow-night-eighties .CodeMirror-guttermarker { color: #f2777a; } +.cm-s-tomorrow-night-eighties .CodeMirror-guttermarker-subtle { color: #777; } +.cm-s-tomorrow-night-eighties .CodeMirror-linenumber { color: #515151; } +.cm-s-tomorrow-night-eighties .CodeMirror-cursor { border-left: 1px solid #6A6A6A; } + +.cm-s-tomorrow-night-eighties span.cm-comment { color: #d27b53; } +.cm-s-tomorrow-night-eighties span.cm-atom { color: #a16a94; } +.cm-s-tomorrow-night-eighties span.cm-number { color: #a16a94; } + +.cm-s-tomorrow-night-eighties span.cm-property, .cm-s-tomorrow-night-eighties span.cm-attribute { color: #99cc99; } +.cm-s-tomorrow-night-eighties span.cm-keyword { color: #f2777a; } +.cm-s-tomorrow-night-eighties span.cm-string { color: #ffcc66; } + +.cm-s-tomorrow-night-eighties span.cm-variable { color: #99cc99; } +.cm-s-tomorrow-night-eighties span.cm-variable-2 { color: #6699cc; } +.cm-s-tomorrow-night-eighties span.cm-def { color: #f99157; } +.cm-s-tomorrow-night-eighties span.cm-bracket { color: #CCCCCC; } +.cm-s-tomorrow-night-eighties span.cm-tag { color: #f2777a; } +.cm-s-tomorrow-night-eighties span.cm-link { color: #a16a94; } +.cm-s-tomorrow-night-eighties span.cm-error { background: #f2777a; color: #6A6A6A; } + +.cm-s-tomorrow-night-eighties .CodeMirror-activeline-background { background: #343600; } +.cm-s-tomorrow-night-eighties .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/docs/js/node_modules/codemirror/theme/ttcn.css b/docs/js/node_modules/codemirror/theme/ttcn.css new file mode 100644 index 000000000..0b14ac35d --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/ttcn.css @@ -0,0 +1,64 @@ +.cm-s-ttcn .cm-quote { color: #090; } +.cm-s-ttcn .cm-negative { color: #d44; } +.cm-s-ttcn .cm-positive { color: #292; } +.cm-s-ttcn .cm-header, .cm-strong { font-weight: bold; } +.cm-s-ttcn .cm-em { font-style: italic; } +.cm-s-ttcn .cm-link { text-decoration: underline; } +.cm-s-ttcn .cm-strikethrough { text-decoration: line-through; } +.cm-s-ttcn .cm-header { color: #00f; font-weight: bold; } + +.cm-s-ttcn .cm-atom { color: #219; } +.cm-s-ttcn .cm-attribute { color: #00c; } +.cm-s-ttcn .cm-bracket { color: #997; } +.cm-s-ttcn .cm-comment { color: #333333; } +.cm-s-ttcn .cm-def { color: #00f; } +.cm-s-ttcn .cm-em { font-style: italic; } +.cm-s-ttcn .cm-error { color: #f00; } +.cm-s-ttcn .cm-hr { color: #999; } +.cm-s-ttcn .cm-invalidchar { color: #f00; } +.cm-s-ttcn .cm-keyword { font-weight:bold; } +.cm-s-ttcn .cm-link { color: #00c; text-decoration: underline; } +.cm-s-ttcn .cm-meta { color: #555; } +.cm-s-ttcn .cm-negative { color: #d44; } +.cm-s-ttcn .cm-positive { color: #292; } +.cm-s-ttcn .cm-qualifier { color: #555; } +.cm-s-ttcn .cm-strikethrough { text-decoration: line-through; } +.cm-s-ttcn .cm-string { color: #006400; } +.cm-s-ttcn .cm-string-2 { color: #f50; } +.cm-s-ttcn .cm-strong { font-weight: bold; } +.cm-s-ttcn .cm-tag { color: #170; } +.cm-s-ttcn .cm-variable { color: #8B2252; } +.cm-s-ttcn .cm-variable-2 { color: #05a; } +.cm-s-ttcn .cm-variable-3, .cm-s-ttcn .cm-type { color: #085; } + +.cm-s-ttcn .cm-invalidchar { color: #f00; } + +/* ASN */ +.cm-s-ttcn .cm-accessTypes, +.cm-s-ttcn .cm-compareTypes { color: #27408B; } +.cm-s-ttcn .cm-cmipVerbs { color: #8B2252; } +.cm-s-ttcn .cm-modifier { color:#D2691E; } +.cm-s-ttcn .cm-status { color:#8B4545; } +.cm-s-ttcn .cm-storage { color:#A020F0; } +.cm-s-ttcn .cm-tags { color:#006400; } + +/* CFG */ +.cm-s-ttcn .cm-externalCommands { color: #8B4545; font-weight:bold; } +.cm-s-ttcn .cm-fileNCtrlMaskOptions, +.cm-s-ttcn .cm-sectionTitle { color: #2E8B57; font-weight:bold; } + +/* TTCN */ +.cm-s-ttcn .cm-booleanConsts, +.cm-s-ttcn .cm-otherConsts, +.cm-s-ttcn .cm-verdictConsts { color: #006400; } +.cm-s-ttcn .cm-configOps, +.cm-s-ttcn .cm-functionOps, +.cm-s-ttcn .cm-portOps, +.cm-s-ttcn .cm-sutOps, +.cm-s-ttcn .cm-timerOps, +.cm-s-ttcn .cm-verdictOps { color: #0000FF; } +.cm-s-ttcn .cm-preprocessor, +.cm-s-ttcn .cm-templateMatch, +.cm-s-ttcn .cm-ttcn3Macros { color: #27408B; } +.cm-s-ttcn .cm-types { color: #A52A2A; font-weight:bold; } +.cm-s-ttcn .cm-visibilityModifiers { font-weight:bold; } diff --git a/docs/js/node_modules/codemirror/theme/twilight.css b/docs/js/node_modules/codemirror/theme/twilight.css new file mode 100644 index 000000000..b2b1b2aa9 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/twilight.css @@ -0,0 +1,32 @@ +.cm-s-twilight.CodeMirror { background: #141414; color: #f7f7f7; } /**/ +.cm-s-twilight div.CodeMirror-selected { background: #323232; } /**/ +.cm-s-twilight .CodeMirror-line::selection, .cm-s-twilight .CodeMirror-line > span::selection, .cm-s-twilight .CodeMirror-line > span > span::selection { background: rgba(50, 50, 50, 0.99); } +.cm-s-twilight .CodeMirror-line::-moz-selection, .cm-s-twilight .CodeMirror-line > span::-moz-selection, .cm-s-twilight .CodeMirror-line > span > span::-moz-selection { background: rgba(50, 50, 50, 0.99); } + +.cm-s-twilight .CodeMirror-gutters { background: #222; border-right: 1px solid #aaa; } +.cm-s-twilight .CodeMirror-guttermarker { color: white; } +.cm-s-twilight .CodeMirror-guttermarker-subtle { color: #aaa; } +.cm-s-twilight .CodeMirror-linenumber { color: #aaa; } +.cm-s-twilight .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-twilight .cm-keyword { color: #f9ee98; } /**/ +.cm-s-twilight .cm-atom { color: #FC0; } +.cm-s-twilight .cm-number { color: #ca7841; } /**/ +.cm-s-twilight .cm-def { color: #8DA6CE; } +.cm-s-twilight span.cm-variable-2, .cm-s-twilight span.cm-tag { color: #607392; } /**/ +.cm-s-twilight span.cm-variable-3, .cm-s-twilight span.cm-def, .cm-s-twilight span.cm-type { color: #607392; } /**/ +.cm-s-twilight .cm-operator { color: #cda869; } /**/ +.cm-s-twilight .cm-comment { color:#777; font-style:italic; font-weight:normal; } /**/ +.cm-s-twilight .cm-string { color:#8f9d6a; font-style:italic; } /**/ +.cm-s-twilight .cm-string-2 { color:#bd6b18; } /*?*/ +.cm-s-twilight .cm-meta { background-color:#141414; color:#f7f7f7; } /*?*/ +.cm-s-twilight .cm-builtin { color: #cda869; } /*?*/ +.cm-s-twilight .cm-tag { color: #997643; } /**/ +.cm-s-twilight .cm-attribute { color: #d6bb6d; } /*?*/ +.cm-s-twilight .cm-header { color: #FF6400; } +.cm-s-twilight .cm-hr { color: #AEAEAE; } +.cm-s-twilight .cm-link { color:#ad9361; font-style:italic; text-decoration:none; } /**/ +.cm-s-twilight .cm-error { border-bottom: 1px solid red; } + +.cm-s-twilight .CodeMirror-activeline-background { background: #27282E; } +.cm-s-twilight .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/docs/js/node_modules/codemirror/theme/vibrant-ink.css b/docs/js/node_modules/codemirror/theme/vibrant-ink.css new file mode 100644 index 000000000..6358ad365 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/vibrant-ink.css @@ -0,0 +1,34 @@ +/* Taken from the popular Visual Studio Vibrant Ink Schema */ + +.cm-s-vibrant-ink.CodeMirror { background: black; color: white; } +.cm-s-vibrant-ink div.CodeMirror-selected { background: #35493c; } +.cm-s-vibrant-ink .CodeMirror-line::selection, .cm-s-vibrant-ink .CodeMirror-line > span::selection, .cm-s-vibrant-ink .CodeMirror-line > span > span::selection { background: rgba(53, 73, 60, 0.99); } +.cm-s-vibrant-ink .CodeMirror-line::-moz-selection, .cm-s-vibrant-ink .CodeMirror-line > span::-moz-selection, .cm-s-vibrant-ink .CodeMirror-line > span > span::-moz-selection { background: rgba(53, 73, 60, 0.99); } + +.cm-s-vibrant-ink .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } +.cm-s-vibrant-ink .CodeMirror-guttermarker { color: white; } +.cm-s-vibrant-ink .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-vibrant-ink .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-vibrant-ink .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-vibrant-ink .cm-keyword { color: #CC7832; } +.cm-s-vibrant-ink .cm-atom { color: #FC0; } +.cm-s-vibrant-ink .cm-number { color: #FFEE98; } +.cm-s-vibrant-ink .cm-def { color: #8DA6CE; } +.cm-s-vibrant-ink span.cm-variable-2, .cm-s-vibrant span.cm-tag { color: #FFC66D; } +.cm-s-vibrant-ink span.cm-variable-3, .cm-s-vibrant span.cm-def, .cm-s-vibrant span.cm-type { color: #FFC66D; } +.cm-s-vibrant-ink .cm-operator { color: #888; } +.cm-s-vibrant-ink .cm-comment { color: gray; font-weight: bold; } +.cm-s-vibrant-ink .cm-string { color: #A5C25C; } +.cm-s-vibrant-ink .cm-string-2 { color: red; } +.cm-s-vibrant-ink .cm-meta { color: #D8FA3C; } +.cm-s-vibrant-ink .cm-builtin { color: #8DA6CE; } +.cm-s-vibrant-ink .cm-tag { color: #8DA6CE; } +.cm-s-vibrant-ink .cm-attribute { color: #8DA6CE; } +.cm-s-vibrant-ink .cm-header { color: #FF6400; } +.cm-s-vibrant-ink .cm-hr { color: #AEAEAE; } +.cm-s-vibrant-ink .cm-link { color: #5656F3; } +.cm-s-vibrant-ink .cm-error { border-bottom: 1px solid red; } + +.cm-s-vibrant-ink .CodeMirror-activeline-background { background: #27282E; } +.cm-s-vibrant-ink .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/docs/js/node_modules/codemirror/theme/xq-dark.css b/docs/js/node_modules/codemirror/theme/xq-dark.css new file mode 100644 index 000000000..7da1a0f70 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/xq-dark.css @@ -0,0 +1,53 @@ +/* +Copyright (C) 2011 by MarkLogic Corporation +Author: Mike Brevoort + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +.cm-s-xq-dark.CodeMirror { background: #0a001f; color: #f8f8f8; } +.cm-s-xq-dark div.CodeMirror-selected { background: #27007A; } +.cm-s-xq-dark .CodeMirror-line::selection, .cm-s-xq-dark .CodeMirror-line > span::selection, .cm-s-xq-dark .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99); } +.cm-s-xq-dark .CodeMirror-line::-moz-selection, .cm-s-xq-dark .CodeMirror-line > span::-moz-selection, .cm-s-xq-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99); } +.cm-s-xq-dark .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } +.cm-s-xq-dark .CodeMirror-guttermarker { color: #FFBD40; } +.cm-s-xq-dark .CodeMirror-guttermarker-subtle { color: #f8f8f8; } +.cm-s-xq-dark .CodeMirror-linenumber { color: #f8f8f8; } +.cm-s-xq-dark .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-xq-dark span.cm-keyword { color: #FFBD40; } +.cm-s-xq-dark span.cm-atom { color: #6C8CD5; } +.cm-s-xq-dark span.cm-number { color: #164; } +.cm-s-xq-dark span.cm-def { color: #FFF; text-decoration:underline; } +.cm-s-xq-dark span.cm-variable { color: #FFF; } +.cm-s-xq-dark span.cm-variable-2 { color: #EEE; } +.cm-s-xq-dark span.cm-variable-3, .cm-s-xq-dark span.cm-type { color: #DDD; } +.cm-s-xq-dark span.cm-property {} +.cm-s-xq-dark span.cm-operator {} +.cm-s-xq-dark span.cm-comment { color: gray; } +.cm-s-xq-dark span.cm-string { color: #9FEE00; } +.cm-s-xq-dark span.cm-meta { color: yellow; } +.cm-s-xq-dark span.cm-qualifier { color: #FFF700; } +.cm-s-xq-dark span.cm-builtin { color: #30a; } +.cm-s-xq-dark span.cm-bracket { color: #cc7; } +.cm-s-xq-dark span.cm-tag { color: #FFBD40; } +.cm-s-xq-dark span.cm-attribute { color: #FFF700; } +.cm-s-xq-dark span.cm-error { color: #f00; } + +.cm-s-xq-dark .CodeMirror-activeline-background { background: #27282E; } +.cm-s-xq-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/docs/js/node_modules/codemirror/theme/xq-light.css b/docs/js/node_modules/codemirror/theme/xq-light.css new file mode 100644 index 000000000..7b182ea99 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/xq-light.css @@ -0,0 +1,43 @@ +/* +Copyright (C) 2011 by MarkLogic Corporation +Author: Mike Brevoort + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +.cm-s-xq-light span.cm-keyword { line-height: 1em; font-weight: bold; color: #5A5CAD; } +.cm-s-xq-light span.cm-atom { color: #6C8CD5; } +.cm-s-xq-light span.cm-number { color: #164; } +.cm-s-xq-light span.cm-def { text-decoration:underline; } +.cm-s-xq-light span.cm-variable { color: black; } +.cm-s-xq-light span.cm-variable-2 { color:black; } +.cm-s-xq-light span.cm-variable-3, .cm-s-xq-light span.cm-type { color: black; } +.cm-s-xq-light span.cm-property {} +.cm-s-xq-light span.cm-operator {} +.cm-s-xq-light span.cm-comment { color: #0080FF; font-style: italic; } +.cm-s-xq-light span.cm-string { color: red; } +.cm-s-xq-light span.cm-meta { color: yellow; } +.cm-s-xq-light span.cm-qualifier { color: grey; } +.cm-s-xq-light span.cm-builtin { color: #7EA656; } +.cm-s-xq-light span.cm-bracket { color: #cc7; } +.cm-s-xq-light span.cm-tag { color: #3F7F7F; } +.cm-s-xq-light span.cm-attribute { color: #7F007F; } +.cm-s-xq-light span.cm-error { color: #f00; } + +.cm-s-xq-light .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-xq-light .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; } diff --git a/docs/js/node_modules/codemirror/theme/yeti.css b/docs/js/node_modules/codemirror/theme/yeti.css new file mode 100644 index 000000000..d085f7249 --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/yeti.css @@ -0,0 +1,44 @@ +/* + + Name: yeti + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original yeti color scheme by Jesse Weed (https://github.com/jesseweed/yeti-syntax) + +*/ + + +.cm-s-yeti.CodeMirror { + background-color: #ECEAE8 !important; + color: #d1c9c0 !important; + border: none; +} + +.cm-s-yeti .CodeMirror-gutters { + color: #adaba6; + background-color: #E5E1DB; + border: none; +} +.cm-s-yeti .CodeMirror-cursor { border-left: solid thin #d1c9c0; } +.cm-s-yeti .CodeMirror-linenumber { color: #adaba6; } +.cm-s-yeti.CodeMirror-focused div.CodeMirror-selected { background: #DCD8D2; } +.cm-s-yeti .CodeMirror-line::selection, .cm-s-yeti .CodeMirror-line > span::selection, .cm-s-yeti .CodeMirror-line > span > span::selection { background: #DCD8D2; } +.cm-s-yeti .CodeMirror-line::-moz-selection, .cm-s-yeti .CodeMirror-line > span::-moz-selection, .cm-s-yeti .CodeMirror-line > span > span::-moz-selection { background: #DCD8D2; } +.cm-s-yeti span.cm-comment { color: #d4c8be; } +.cm-s-yeti span.cm-string, .cm-s-yeti span.cm-string-2 { color: #96c0d8; } +.cm-s-yeti span.cm-number { color: #a074c4; } +.cm-s-yeti span.cm-variable { color: #55b5db; } +.cm-s-yeti span.cm-variable-2 { color: #a074c4; } +.cm-s-yeti span.cm-def { color: #55b5db; } +.cm-s-yeti span.cm-operator { color: #9fb96e; } +.cm-s-yeti span.cm-keyword { color: #9fb96e; } +.cm-s-yeti span.cm-atom { color: #a074c4; } +.cm-s-yeti span.cm-meta { color: #96c0d8; } +.cm-s-yeti span.cm-tag { color: #96c0d8; } +.cm-s-yeti span.cm-attribute { color: #9fb96e; } +.cm-s-yeti span.cm-qualifier { color: #96c0d8; } +.cm-s-yeti span.cm-property { color: #a074c4; } +.cm-s-yeti span.cm-builtin { color: #a074c4; } +.cm-s-yeti span.cm-variable-3, .cm-s-yeti span.cm-type { color: #96c0d8; } +.cm-s-yeti .CodeMirror-activeline-background { background: #E7E4E0; } +.cm-s-yeti .CodeMirror-matchingbracket { text-decoration: underline; } diff --git a/docs/js/node_modules/codemirror/theme/yonce.css b/docs/js/node_modules/codemirror/theme/yonce.css new file mode 100644 index 000000000..975f0788a --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/yonce.css @@ -0,0 +1,59 @@ +/* + + Name: yoncé + Author: Thomas MacLean (http://github.com/thomasmaclean) + + Original yoncé color scheme by Mina Markham (https://github.com/minamarkham) + +*/ + +.cm-s-yonce.CodeMirror { background: #1C1C1C; color: #d4d4d4; } /**/ +.cm-s-yonce div.CodeMirror-selected { background: rgba(252, 69, 133, 0.478); } /**/ +.cm-s-yonce .CodeMirror-selectedtext, +.cm-s-yonce .CodeMirror-selected, +.cm-s-yonce .CodeMirror-line::selection, +.cm-s-yonce .CodeMirror-line > span::selection, +.cm-s-yonce .CodeMirror-line > span > span::selection, +.cm-s-yonce .CodeMirror-line::-moz-selection, +.cm-s-yonce .CodeMirror-line > span::-moz-selection, +.cm-s-yonce .CodeMirror-line > span > span::-moz-selection { background: rgba(252, 67, 132, 0.47); } + +.cm-s-yonce.CodeMirror pre { padding-left: 0px; } +.cm-s-yonce .CodeMirror-gutters {background: #1C1C1C; border-right: 0px;} +.cm-s-yonce .CodeMirror-linenumber {color: #777777; padding-right: 10px; } +.cm-s-yonce .CodeMirror-activeline .CodeMirror-linenumber.CodeMirror-gutter-elt { background: #1C1C1C; color: #fc4384; } +.cm-s-yonce .CodeMirror-linenumber { color: #777; } +.cm-s-yonce .CodeMirror-cursor { border-left: 2px solid #FC4384; } +.cm-s-yonce .cm-searching { background: rgba(243, 155, 53, .3) !important; outline: 1px solid #F39B35; } +.cm-s-yonce .cm-searching.CodeMirror-selectedtext { background: rgba(243, 155, 53, .7) !important; color: white; } + +.cm-s-yonce .cm-keyword { color: #00A7AA; } /**/ +.cm-s-yonce .cm-atom { color: #F39B35; } +.cm-s-yonce .cm-number, .cm-s-yonce span.cm-type { color: #A06FCA; } /**/ +.cm-s-yonce .cm-def { color: #98E342; } +.cm-s-yonce .cm-property, +.cm-s-yonce span.cm-variable { color: #D4D4D4; font-style: italic; } +.cm-s-yonce span.cm-variable-2 { color: #da7dae; font-style: italic; } +.cm-s-yonce span.cm-variable-3 { color: #A06FCA; } +.cm-s-yonce .cm-type.cm-def { color: #FC4384; font-style: normal; text-decoration: underline; } +.cm-s-yonce .cm-property.cm-def { color: #FC4384; font-style: normal; } +.cm-s-yonce .cm-callee { color: #FC4384; font-style: normal; } +.cm-s-yonce .cm-operator { color: #FC4384; } /**/ +.cm-s-yonce .cm-qualifier, +.cm-s-yonce .cm-tag { color: #FC4384; } +.cm-s-yonce .cm-tag.cm-bracket { color: #D4D4D4; } +.cm-s-yonce .cm-attribute { color: #A06FCA; } +.cm-s-yonce .cm-comment { color:#696d70; font-style:italic; font-weight:normal; } /**/ +.cm-s-yonce .cm-comment.cm-tag { color: #FC4384 } +.cm-s-yonce .cm-comment.cm-attribute { color: #D4D4D4; } +.cm-s-yonce .cm-string { color:#E6DB74; } /**/ +.cm-s-yonce .cm-string-2 { color:#F39B35; } /*?*/ +.cm-s-yonce .cm-meta { color: #D4D4D4; background: inherit; } +.cm-s-yonce .cm-builtin { color: #FC4384; } /*?*/ +.cm-s-yonce .cm-header { color: #da7dae; } +.cm-s-yonce .cm-hr { color: #98E342; } +.cm-s-yonce .cm-link { color:#696d70; font-style:italic; text-decoration:none; } /**/ +.cm-s-yonce .cm-error { border-bottom: 1px solid #C42412; } + +.cm-s-yonce .CodeMirror-activeline-background { background: #272727; } +.cm-s-yonce .CodeMirror-matchingbracket { outline:1px solid grey; color:#D4D4D4 !important; } diff --git a/docs/js/node_modules/codemirror/theme/zenburn.css b/docs/js/node_modules/codemirror/theme/zenburn.css new file mode 100644 index 000000000..781c40aca --- /dev/null +++ b/docs/js/node_modules/codemirror/theme/zenburn.css @@ -0,0 +1,37 @@ +/** + * " + * Using Zenburn color palette from the Emacs Zenburn Theme + * https://github.com/bbatsov/zenburn-emacs/blob/master/zenburn-theme.el + * + * Also using parts of https://github.com/xavi/coderay-lighttable-theme + * " + * From: https://github.com/wisenomad/zenburn-lighttable-theme/blob/master/zenburn.css + */ + +.cm-s-zenburn .CodeMirror-gutters { background: #3f3f3f !important; } +.cm-s-zenburn .CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { color: #999; } +.cm-s-zenburn .CodeMirror-cursor { border-left: 1px solid white; } +.cm-s-zenburn { background-color: #3f3f3f; color: #dcdccc; } +.cm-s-zenburn span.cm-builtin { color: #dcdccc; font-weight: bold; } +.cm-s-zenburn span.cm-comment { color: #7f9f7f; } +.cm-s-zenburn span.cm-keyword { color: #f0dfaf; font-weight: bold; } +.cm-s-zenburn span.cm-atom { color: #bfebbf; } +.cm-s-zenburn span.cm-def { color: #dcdccc; } +.cm-s-zenburn span.cm-variable { color: #dfaf8f; } +.cm-s-zenburn span.cm-variable-2 { color: #dcdccc; } +.cm-s-zenburn span.cm-string { color: #cc9393; } +.cm-s-zenburn span.cm-string-2 { color: #cc9393; } +.cm-s-zenburn span.cm-number { color: #dcdccc; } +.cm-s-zenburn span.cm-tag { color: #93e0e3; } +.cm-s-zenburn span.cm-property { color: #dfaf8f; } +.cm-s-zenburn span.cm-attribute { color: #dfaf8f; } +.cm-s-zenburn span.cm-qualifier { color: #7cb8bb; } +.cm-s-zenburn span.cm-meta { color: #f0dfaf; } +.cm-s-zenburn span.cm-header { color: #f0efd0; } +.cm-s-zenburn span.cm-operator { color: #f0efd0; } +.cm-s-zenburn span.CodeMirror-matchingbracket { box-sizing: border-box; background: transparent; border-bottom: 1px solid; } +.cm-s-zenburn span.CodeMirror-nonmatchingbracket { border-bottom: 1px solid; background: none; } +.cm-s-zenburn .CodeMirror-activeline { background: #000000; } +.cm-s-zenburn .CodeMirror-activeline-background { background: #000000; } +.cm-s-zenburn div.CodeMirror-selected { background: #545454; } +.cm-s-zenburn .CodeMirror-focused div.CodeMirror-selected { background: #4f4f4f; } diff --git a/docs/js/node_modules/combined-stream/License b/docs/js/node_modules/combined-stream/License new file mode 100644 index 000000000..4804b7ab4 --- /dev/null +++ b/docs/js/node_modules/combined-stream/License @@ -0,0 +1,19 @@ +Copyright (c) 2011 Debuggable Limited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/docs/js/node_modules/combined-stream/Readme.md b/docs/js/node_modules/combined-stream/Readme.md new file mode 100644 index 000000000..9e367b5bc --- /dev/null +++ b/docs/js/node_modules/combined-stream/Readme.md @@ -0,0 +1,138 @@ +# combined-stream + +A stream that emits multiple other streams one after another. + +**NB** Currently `combined-stream` works with streams version 1 only. There is ongoing effort to switch this library to streams version 2. Any help is welcome. :) Meanwhile you can explore other libraries that provide streams2 support with more or less compatibility with `combined-stream`. + +- [combined-stream2](https://www.npmjs.com/package/combined-stream2): A drop-in streams2-compatible replacement for the combined-stream module. + +- [multistream](https://www.npmjs.com/package/multistream): A stream that emits multiple other streams one after another. + +## Installation + +``` bash +npm install combined-stream +``` + +## Usage + +Here is a simple example that shows how you can use combined-stream to combine +two files into one: + +``` javascript +var CombinedStream = require('combined-stream'); +var fs = require('fs'); + +var combinedStream = CombinedStream.create(); +combinedStream.append(fs.createReadStream('file1.txt')); +combinedStream.append(fs.createReadStream('file2.txt')); + +combinedStream.pipe(fs.createWriteStream('combined.txt')); +``` + +While the example above works great, it will pause all source streams until +they are needed. If you don't want that to happen, you can set `pauseStreams` +to `false`: + +``` javascript +var CombinedStream = require('combined-stream'); +var fs = require('fs'); + +var combinedStream = CombinedStream.create({pauseStreams: false}); +combinedStream.append(fs.createReadStream('file1.txt')); +combinedStream.append(fs.createReadStream('file2.txt')); + +combinedStream.pipe(fs.createWriteStream('combined.txt')); +``` + +However, what if you don't have all the source streams yet, or you don't want +to allocate the resources (file descriptors, memory, etc.) for them right away? +Well, in that case you can simply provide a callback that supplies the stream +by calling a `next()` function: + +``` javascript +var CombinedStream = require('combined-stream'); +var fs = require('fs'); + +var combinedStream = CombinedStream.create(); +combinedStream.append(function(next) { + next(fs.createReadStream('file1.txt')); +}); +combinedStream.append(function(next) { + next(fs.createReadStream('file2.txt')); +}); + +combinedStream.pipe(fs.createWriteStream('combined.txt')); +``` + +## API + +### CombinedStream.create([options]) + +Returns a new combined stream object. Available options are: + +* `maxDataSize` +* `pauseStreams` + +The effect of those options is described below. + +### combinedStream.pauseStreams = `true` + +Whether to apply back pressure to the underlaying streams. If set to `false`, +the underlaying streams will never be paused. If set to `true`, the +underlaying streams will be paused right after being appended, as well as when +`delayedStream.pipe()` wants to throttle. + +### combinedStream.maxDataSize = `2 * 1024 * 1024` + +The maximum amount of bytes (or characters) to buffer for all source streams. +If this value is exceeded, `combinedStream` emits an `'error'` event. + +### combinedStream.dataSize = `0` + +The amount of bytes (or characters) currently buffered by `combinedStream`. + +### combinedStream.append(stream) + +Appends the given `stream` to the combinedStream object. If `pauseStreams` is +set to `true, this stream will also be paused right away. + +`streams` can also be a function that takes one parameter called `next`. `next` +is a function that must be invoked in order to provide the `next` stream, see +example above. + +Regardless of how the `stream` is appended, combined-stream always attaches an +`'error'` listener to it, so you don't have to do that manually. + +Special case: `stream` can also be a String or Buffer. + +### combinedStream.write(data) + +You should not call this, `combinedStream` takes care of piping the appended +streams into itself for you. + +### combinedStream.resume() + +Causes `combinedStream` to start drain the streams it manages. The function is +idempotent, and also emits a `'resume'` event each time which usually goes to +the stream that is currently being drained. + +### combinedStream.pause(); + +If `combinedStream.pauseStreams` is set to `false`, this does nothing. +Otherwise a `'pause'` event is emitted, this goes to the stream that is +currently being drained, so you can use it to apply back pressure. + +### combinedStream.end(); + +Sets `combinedStream.writable` to false, emits an `'end'` event, and removes +all streams from the queue. + +### combinedStream.destroy(); + +Same as `combinedStream.end()`, except it emits a `'close'` event instead of +`'end'`. + +## License + +combined-stream is licensed under the MIT license. diff --git a/docs/js/node_modules/combined-stream/lib/combined_stream.js b/docs/js/node_modules/combined-stream/lib/combined_stream.js new file mode 100644 index 000000000..125f097f3 --- /dev/null +++ b/docs/js/node_modules/combined-stream/lib/combined_stream.js @@ -0,0 +1,208 @@ +var util = require('util'); +var Stream = require('stream').Stream; +var DelayedStream = require('delayed-stream'); + +module.exports = CombinedStream; +function CombinedStream() { + this.writable = false; + this.readable = true; + this.dataSize = 0; + this.maxDataSize = 2 * 1024 * 1024; + this.pauseStreams = true; + + this._released = false; + this._streams = []; + this._currentStream = null; + this._insideLoop = false; + this._pendingNext = false; +} +util.inherits(CombinedStream, Stream); + +CombinedStream.create = function(options) { + var combinedStream = new this(); + + options = options || {}; + for (var option in options) { + combinedStream[option] = options[option]; + } + + return combinedStream; +}; + +CombinedStream.isStreamLike = function(stream) { + return (typeof stream !== 'function') + && (typeof stream !== 'string') + && (typeof stream !== 'boolean') + && (typeof stream !== 'number') + && (!Buffer.isBuffer(stream)); +}; + +CombinedStream.prototype.append = function(stream) { + var isStreamLike = CombinedStream.isStreamLike(stream); + + if (isStreamLike) { + if (!(stream instanceof DelayedStream)) { + var newStream = DelayedStream.create(stream, { + maxDataSize: Infinity, + pauseStream: this.pauseStreams, + }); + stream.on('data', this._checkDataSize.bind(this)); + stream = newStream; + } + + this._handleErrors(stream); + + if (this.pauseStreams) { + stream.pause(); + } + } + + this._streams.push(stream); + return this; +}; + +CombinedStream.prototype.pipe = function(dest, options) { + Stream.prototype.pipe.call(this, dest, options); + this.resume(); + return dest; +}; + +CombinedStream.prototype._getNext = function() { + this._currentStream = null; + + if (this._insideLoop) { + this._pendingNext = true; + return; // defer call + } + + this._insideLoop = true; + try { + do { + this._pendingNext = false; + this._realGetNext(); + } while (this._pendingNext); + } finally { + this._insideLoop = false; + } +}; + +CombinedStream.prototype._realGetNext = function() { + var stream = this._streams.shift(); + + + if (typeof stream == 'undefined') { + this.end(); + return; + } + + if (typeof stream !== 'function') { + this._pipeNext(stream); + return; + } + + var getStream = stream; + getStream(function(stream) { + var isStreamLike = CombinedStream.isStreamLike(stream); + if (isStreamLike) { + stream.on('data', this._checkDataSize.bind(this)); + this._handleErrors(stream); + } + + this._pipeNext(stream); + }.bind(this)); +}; + +CombinedStream.prototype._pipeNext = function(stream) { + this._currentStream = stream; + + var isStreamLike = CombinedStream.isStreamLike(stream); + if (isStreamLike) { + stream.on('end', this._getNext.bind(this)); + stream.pipe(this, {end: false}); + return; + } + + var value = stream; + this.write(value); + this._getNext(); +}; + +CombinedStream.prototype._handleErrors = function(stream) { + var self = this; + stream.on('error', function(err) { + self._emitError(err); + }); +}; + +CombinedStream.prototype.write = function(data) { + this.emit('data', data); +}; + +CombinedStream.prototype.pause = function() { + if (!this.pauseStreams) { + return; + } + + if(this.pauseStreams && this._currentStream && typeof(this._currentStream.pause) == 'function') this._currentStream.pause(); + this.emit('pause'); +}; + +CombinedStream.prototype.resume = function() { + if (!this._released) { + this._released = true; + this.writable = true; + this._getNext(); + } + + if(this.pauseStreams && this._currentStream && typeof(this._currentStream.resume) == 'function') this._currentStream.resume(); + this.emit('resume'); +}; + +CombinedStream.prototype.end = function() { + this._reset(); + this.emit('end'); +}; + +CombinedStream.prototype.destroy = function() { + this._reset(); + this.emit('close'); +}; + +CombinedStream.prototype._reset = function() { + this.writable = false; + this._streams = []; + this._currentStream = null; +}; + +CombinedStream.prototype._checkDataSize = function() { + this._updateDataSize(); + if (this.dataSize <= this.maxDataSize) { + return; + } + + var message = + 'DelayedStream#maxDataSize of ' + this.maxDataSize + ' bytes exceeded.'; + this._emitError(new Error(message)); +}; + +CombinedStream.prototype._updateDataSize = function() { + this.dataSize = 0; + + var self = this; + this._streams.forEach(function(stream) { + if (!stream.dataSize) { + return; + } + + self.dataSize += stream.dataSize; + }); + + if (this._currentStream && this._currentStream.dataSize) { + this.dataSize += this._currentStream.dataSize; + } +}; + +CombinedStream.prototype._emitError = function(err) { + this._reset(); + this.emit('error', err); +}; diff --git a/docs/js/node_modules/combined-stream/package.json b/docs/js/node_modules/combined-stream/package.json new file mode 100644 index 000000000..2ce5cc883 --- /dev/null +++ b/docs/js/node_modules/combined-stream/package.json @@ -0,0 +1,58 @@ +{ + "_from": "combined-stream@~1.0.6", + "_id": "combined-stream@1.0.8", + "_inBundle": false, + "_integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "_location": "/combined-stream", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": true, + "raw": "combined-stream@~1.0.6", + "name": "combined-stream", + "escapedName": "combined-stream", + "rawSpec": "~1.0.6", + "saveSpec": null, + "fetchSpec": "~1.0.6" + }, + "_requiredBy": [ + "/form-data", + "/request" + ], + "_resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "_shasum": "c3d45a8b34fd730631a110a8a2520682b31d5a7f", + "_spec": "combined-stream@~1.0.6", + "_where": "/Users/hectorip/Education/Eloquent-JavaScript-ES/node_modules/request", + "author": { + "name": "Felix Geisendörfer", + "email": "felix@debuggable.com", + "url": "http://debuggable.com/" + }, + "bugs": { + "url": "https://github.com/felixge/node-combined-stream/issues" + }, + "bundleDependencies": false, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "deprecated": false, + "description": "A stream that emits multiple other streams one after another.", + "devDependencies": { + "far": "~0.0.7" + }, + "engines": { + "node": ">= 0.8" + }, + "homepage": "https://github.com/felixge/node-combined-stream", + "license": "MIT", + "main": "./lib/combined_stream", + "name": "combined-stream", + "repository": { + "type": "git", + "url": "git://github.com/felixge/node-combined-stream.git" + }, + "scripts": { + "test": "node test/run.js" + }, + "version": "1.0.8" +} diff --git a/docs/js/node_modules/combined-stream/yarn.lock b/docs/js/node_modules/combined-stream/yarn.lock new file mode 100644 index 000000000..7edf41840 --- /dev/null +++ b/docs/js/node_modules/combined-stream/yarn.lock @@ -0,0 +1,17 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +far@~0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/far/-/far-0.0.7.tgz#01c1fd362bcd26ce9cf161af3938aa34619f79a7" + dependencies: + oop "0.0.3" + +oop@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/oop/-/oop-0.0.3.tgz#70fa405a5650891a194fdc82ca68dad6dabf4401" diff --git a/docs/js/node_modules/core-util-is/LICENSE b/docs/js/node_modules/core-util-is/LICENSE new file mode 100644 index 000000000..d8d7f9437 --- /dev/null +++ b/docs/js/node_modules/core-util-is/LICENSE @@ -0,0 +1,19 @@ +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/docs/js/node_modules/core-util-is/README.md b/docs/js/node_modules/core-util-is/README.md new file mode 100644 index 000000000..5a76b4149 --- /dev/null +++ b/docs/js/node_modules/core-util-is/README.md @@ -0,0 +1,3 @@ +# core-util-is + +The `util.is*` functions introduced in Node v0.12. diff --git a/docs/js/node_modules/core-util-is/float.patch b/docs/js/node_modules/core-util-is/float.patch new file mode 100644 index 000000000..a06d5c05f --- /dev/null +++ b/docs/js/node_modules/core-util-is/float.patch @@ -0,0 +1,604 @@ +diff --git a/lib/util.js b/lib/util.js +index a03e874..9074e8e 100644 +--- a/lib/util.js ++++ b/lib/util.js +@@ -19,430 +19,6 @@ + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. + +-var formatRegExp = /%[sdj%]/g; +-exports.format = function(f) { +- if (!isString(f)) { +- var objects = []; +- for (var i = 0; i < arguments.length; i++) { +- objects.push(inspect(arguments[i])); +- } +- return objects.join(' '); +- } +- +- var i = 1; +- var args = arguments; +- var len = args.length; +- var str = String(f).replace(formatRegExp, function(x) { +- if (x === '%%') return '%'; +- if (i >= len) return x; +- switch (x) { +- case '%s': return String(args[i++]); +- case '%d': return Number(args[i++]); +- case '%j': +- try { +- return JSON.stringify(args[i++]); +- } catch (_) { +- return '[Circular]'; +- } +- default: +- return x; +- } +- }); +- for (var x = args[i]; i < len; x = args[++i]) { +- if (isNull(x) || !isObject(x)) { +- str += ' ' + x; +- } else { +- str += ' ' + inspect(x); +- } +- } +- return str; +-}; +- +- +-// Mark that a method should not be used. +-// Returns a modified function which warns once by default. +-// If --no-deprecation is set, then it is a no-op. +-exports.deprecate = function(fn, msg) { +- // Allow for deprecating things in the process of starting up. +- if (isUndefined(global.process)) { +- return function() { +- return exports.deprecate(fn, msg).apply(this, arguments); +- }; +- } +- +- if (process.noDeprecation === true) { +- return fn; +- } +- +- var warned = false; +- function deprecated() { +- if (!warned) { +- if (process.throwDeprecation) { +- throw new Error(msg); +- } else if (process.traceDeprecation) { +- console.trace(msg); +- } else { +- console.error(msg); +- } +- warned = true; +- } +- return fn.apply(this, arguments); +- } +- +- return deprecated; +-}; +- +- +-var debugs = {}; +-var debugEnviron; +-exports.debuglog = function(set) { +- if (isUndefined(debugEnviron)) +- debugEnviron = process.env.NODE_DEBUG || ''; +- set = set.toUpperCase(); +- if (!debugs[set]) { +- if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { +- var pid = process.pid; +- debugs[set] = function() { +- var msg = exports.format.apply(exports, arguments); +- console.error('%s %d: %s', set, pid, msg); +- }; +- } else { +- debugs[set] = function() {}; +- } +- } +- return debugs[set]; +-}; +- +- +-/** +- * Echos the value of a value. Trys to print the value out +- * in the best way possible given the different types. +- * +- * @param {Object} obj The object to print out. +- * @param {Object} opts Optional options object that alters the output. +- */ +-/* legacy: obj, showHidden, depth, colors*/ +-function inspect(obj, opts) { +- // default options +- var ctx = { +- seen: [], +- stylize: stylizeNoColor +- }; +- // legacy... +- if (arguments.length >= 3) ctx.depth = arguments[2]; +- if (arguments.length >= 4) ctx.colors = arguments[3]; +- if (isBoolean(opts)) { +- // legacy... +- ctx.showHidden = opts; +- } else if (opts) { +- // got an "options" object +- exports._extend(ctx, opts); +- } +- // set default options +- if (isUndefined(ctx.showHidden)) ctx.showHidden = false; +- if (isUndefined(ctx.depth)) ctx.depth = 2; +- if (isUndefined(ctx.colors)) ctx.colors = false; +- if (isUndefined(ctx.customInspect)) ctx.customInspect = true; +- if (ctx.colors) ctx.stylize = stylizeWithColor; +- return formatValue(ctx, obj, ctx.depth); +-} +-exports.inspect = inspect; +- +- +-// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +-inspect.colors = { +- 'bold' : [1, 22], +- 'italic' : [3, 23], +- 'underline' : [4, 24], +- 'inverse' : [7, 27], +- 'white' : [37, 39], +- 'grey' : [90, 39], +- 'black' : [30, 39], +- 'blue' : [34, 39], +- 'cyan' : [36, 39], +- 'green' : [32, 39], +- 'magenta' : [35, 39], +- 'red' : [31, 39], +- 'yellow' : [33, 39] +-}; +- +-// Don't use 'blue' not visible on cmd.exe +-inspect.styles = { +- 'special': 'cyan', +- 'number': 'yellow', +- 'boolean': 'yellow', +- 'undefined': 'grey', +- 'null': 'bold', +- 'string': 'green', +- 'date': 'magenta', +- // "name": intentionally not styling +- 'regexp': 'red' +-}; +- +- +-function stylizeWithColor(str, styleType) { +- var style = inspect.styles[styleType]; +- +- if (style) { +- return '\u001b[' + inspect.colors[style][0] + 'm' + str + +- '\u001b[' + inspect.colors[style][1] + 'm'; +- } else { +- return str; +- } +-} +- +- +-function stylizeNoColor(str, styleType) { +- return str; +-} +- +- +-function arrayToHash(array) { +- var hash = {}; +- +- array.forEach(function(val, idx) { +- hash[val] = true; +- }); +- +- return hash; +-} +- +- +-function formatValue(ctx, value, recurseTimes) { +- // Provide a hook for user-specified inspect functions. +- // Check that value is an object with an inspect function on it +- if (ctx.customInspect && +- value && +- isFunction(value.inspect) && +- // Filter out the util module, it's inspect function is special +- value.inspect !== exports.inspect && +- // Also filter out any prototype objects using the circular check. +- !(value.constructor && value.constructor.prototype === value)) { +- var ret = value.inspect(recurseTimes, ctx); +- if (!isString(ret)) { +- ret = formatValue(ctx, ret, recurseTimes); +- } +- return ret; +- } +- +- // Primitive types cannot have properties +- var primitive = formatPrimitive(ctx, value); +- if (primitive) { +- return primitive; +- } +- +- // Look up the keys of the object. +- var keys = Object.keys(value); +- var visibleKeys = arrayToHash(keys); +- +- if (ctx.showHidden) { +- keys = Object.getOwnPropertyNames(value); +- } +- +- // Some type of object without properties can be shortcutted. +- if (keys.length === 0) { +- if (isFunction(value)) { +- var name = value.name ? ': ' + value.name : ''; +- return ctx.stylize('[Function' + name + ']', 'special'); +- } +- if (isRegExp(value)) { +- return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); +- } +- if (isDate(value)) { +- return ctx.stylize(Date.prototype.toString.call(value), 'date'); +- } +- if (isError(value)) { +- return formatError(value); +- } +- } +- +- var base = '', array = false, braces = ['{', '}']; +- +- // Make Array say that they are Array +- if (isArray(value)) { +- array = true; +- braces = ['[', ']']; +- } +- +- // Make functions say that they are functions +- if (isFunction(value)) { +- var n = value.name ? ': ' + value.name : ''; +- base = ' [Function' + n + ']'; +- } +- +- // Make RegExps say that they are RegExps +- if (isRegExp(value)) { +- base = ' ' + RegExp.prototype.toString.call(value); +- } +- +- // Make dates with properties first say the date +- if (isDate(value)) { +- base = ' ' + Date.prototype.toUTCString.call(value); +- } +- +- // Make error with message first say the error +- if (isError(value)) { +- base = ' ' + formatError(value); +- } +- +- if (keys.length === 0 && (!array || value.length == 0)) { +- return braces[0] + base + braces[1]; +- } +- +- if (recurseTimes < 0) { +- if (isRegExp(value)) { +- return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); +- } else { +- return ctx.stylize('[Object]', 'special'); +- } +- } +- +- ctx.seen.push(value); +- +- var output; +- if (array) { +- output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); +- } else { +- output = keys.map(function(key) { +- return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); +- }); +- } +- +- ctx.seen.pop(); +- +- return reduceToSingleString(output, base, braces); +-} +- +- +-function formatPrimitive(ctx, value) { +- if (isUndefined(value)) +- return ctx.stylize('undefined', 'undefined'); +- if (isString(value)) { +- var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') +- .replace(/'/g, "\\'") +- .replace(/\\"/g, '"') + '\''; +- return ctx.stylize(simple, 'string'); +- } +- if (isNumber(value)) { +- // Format -0 as '-0'. Strict equality won't distinguish 0 from -0, +- // so instead we use the fact that 1 / -0 < 0 whereas 1 / 0 > 0 . +- if (value === 0 && 1 / value < 0) +- return ctx.stylize('-0', 'number'); +- return ctx.stylize('' + value, 'number'); +- } +- if (isBoolean(value)) +- return ctx.stylize('' + value, 'boolean'); +- // For some reason typeof null is "object", so special case here. +- if (isNull(value)) +- return ctx.stylize('null', 'null'); +-} +- +- +-function formatError(value) { +- return '[' + Error.prototype.toString.call(value) + ']'; +-} +- +- +-function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { +- var output = []; +- for (var i = 0, l = value.length; i < l; ++i) { +- if (hasOwnProperty(value, String(i))) { +- output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, +- String(i), true)); +- } else { +- output.push(''); +- } +- } +- keys.forEach(function(key) { +- if (!key.match(/^\d+$/)) { +- output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, +- key, true)); +- } +- }); +- return output; +-} +- +- +-function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { +- var name, str, desc; +- desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; +- if (desc.get) { +- if (desc.set) { +- str = ctx.stylize('[Getter/Setter]', 'special'); +- } else { +- str = ctx.stylize('[Getter]', 'special'); +- } +- } else { +- if (desc.set) { +- str = ctx.stylize('[Setter]', 'special'); +- } +- } +- if (!hasOwnProperty(visibleKeys, key)) { +- name = '[' + key + ']'; +- } +- if (!str) { +- if (ctx.seen.indexOf(desc.value) < 0) { +- if (isNull(recurseTimes)) { +- str = formatValue(ctx, desc.value, null); +- } else { +- str = formatValue(ctx, desc.value, recurseTimes - 1); +- } +- if (str.indexOf('\n') > -1) { +- if (array) { +- str = str.split('\n').map(function(line) { +- return ' ' + line; +- }).join('\n').substr(2); +- } else { +- str = '\n' + str.split('\n').map(function(line) { +- return ' ' + line; +- }).join('\n'); +- } +- } +- } else { +- str = ctx.stylize('[Circular]', 'special'); +- } +- } +- if (isUndefined(name)) { +- if (array && key.match(/^\d+$/)) { +- return str; +- } +- name = JSON.stringify('' + key); +- if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { +- name = name.substr(1, name.length - 2); +- name = ctx.stylize(name, 'name'); +- } else { +- name = name.replace(/'/g, "\\'") +- .replace(/\\"/g, '"') +- .replace(/(^"|"$)/g, "'"); +- name = ctx.stylize(name, 'string'); +- } +- } +- +- return name + ': ' + str; +-} +- +- +-function reduceToSingleString(output, base, braces) { +- var numLinesEst = 0; +- var length = output.reduce(function(prev, cur) { +- numLinesEst++; +- if (cur.indexOf('\n') >= 0) numLinesEst++; +- return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; +- }, 0); +- +- if (length > 60) { +- return braces[0] + +- (base === '' ? '' : base + '\n ') + +- ' ' + +- output.join(',\n ') + +- ' ' + +- braces[1]; +- } +- +- return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +-} +- +- + // NOTE: These type checking functions intentionally don't use `instanceof` + // because it is fragile and can be easily faked with `Object.create()`. + function isArray(ar) { +@@ -522,166 +98,10 @@ function isPrimitive(arg) { + exports.isPrimitive = isPrimitive; + + function isBuffer(arg) { +- return arg instanceof Buffer; ++ return Buffer.isBuffer(arg); + } + exports.isBuffer = isBuffer; + + function objectToString(o) { + return Object.prototype.toString.call(o); +-} +- +- +-function pad(n) { +- return n < 10 ? '0' + n.toString(10) : n.toString(10); +-} +- +- +-var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', +- 'Oct', 'Nov', 'Dec']; +- +-// 26 Feb 16:19:34 +-function timestamp() { +- var d = new Date(); +- var time = [pad(d.getHours()), +- pad(d.getMinutes()), +- pad(d.getSeconds())].join(':'); +- return [d.getDate(), months[d.getMonth()], time].join(' '); +-} +- +- +-// log is just a thin wrapper to console.log that prepends a timestamp +-exports.log = function() { +- console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +-}; +- +- +-/** +- * Inherit the prototype methods from one constructor into another. +- * +- * The Function.prototype.inherits from lang.js rewritten as a standalone +- * function (not on Function.prototype). NOTE: If this file is to be loaded +- * during bootstrapping this function needs to be rewritten using some native +- * functions as prototype setup using normal JavaScript does not work as +- * expected during bootstrapping (see mirror.js in r114903). +- * +- * @param {function} ctor Constructor function which needs to inherit the +- * prototype. +- * @param {function} superCtor Constructor function to inherit prototype from. +- */ +-exports.inherits = function(ctor, superCtor) { +- ctor.super_ = superCtor; +- ctor.prototype = Object.create(superCtor.prototype, { +- constructor: { +- value: ctor, +- enumerable: false, +- writable: true, +- configurable: true +- } +- }); +-}; +- +-exports._extend = function(origin, add) { +- // Don't do anything if add isn't an object +- if (!add || !isObject(add)) return origin; +- +- var keys = Object.keys(add); +- var i = keys.length; +- while (i--) { +- origin[keys[i]] = add[keys[i]]; +- } +- return origin; +-}; +- +-function hasOwnProperty(obj, prop) { +- return Object.prototype.hasOwnProperty.call(obj, prop); +-} +- +- +-// Deprecated old stuff. +- +-exports.p = exports.deprecate(function() { +- for (var i = 0, len = arguments.length; i < len; ++i) { +- console.error(exports.inspect(arguments[i])); +- } +-}, 'util.p: Use console.error() instead'); +- +- +-exports.exec = exports.deprecate(function() { +- return require('child_process').exec.apply(this, arguments); +-}, 'util.exec is now called `child_process.exec`.'); +- +- +-exports.print = exports.deprecate(function() { +- for (var i = 0, len = arguments.length; i < len; ++i) { +- process.stdout.write(String(arguments[i])); +- } +-}, 'util.print: Use console.log instead'); +- +- +-exports.puts = exports.deprecate(function() { +- for (var i = 0, len = arguments.length; i < len; ++i) { +- process.stdout.write(arguments[i] + '\n'); +- } +-}, 'util.puts: Use console.log instead'); +- +- +-exports.debug = exports.deprecate(function(x) { +- process.stderr.write('DEBUG: ' + x + '\n'); +-}, 'util.debug: Use console.error instead'); +- +- +-exports.error = exports.deprecate(function(x) { +- for (var i = 0, len = arguments.length; i < len; ++i) { +- process.stderr.write(arguments[i] + '\n'); +- } +-}, 'util.error: Use console.error instead'); +- +- +-exports.pump = exports.deprecate(function(readStream, writeStream, callback) { +- var callbackCalled = false; +- +- function call(a, b, c) { +- if (callback && !callbackCalled) { +- callback(a, b, c); +- callbackCalled = true; +- } +- } +- +- readStream.addListener('data', function(chunk) { +- if (writeStream.write(chunk) === false) readStream.pause(); +- }); +- +- writeStream.addListener('drain', function() { +- readStream.resume(); +- }); +- +- readStream.addListener('end', function() { +- writeStream.end(); +- }); +- +- readStream.addListener('close', function() { +- call(); +- }); +- +- readStream.addListener('error', function(err) { +- writeStream.end(); +- call(err); +- }); +- +- writeStream.addListener('error', function(err) { +- readStream.destroy(); +- call(err); +- }); +-}, 'util.pump(): Use readableStream.pipe() instead'); +- +- +-var uv; +-exports._errnoException = function(err, syscall) { +- if (isUndefined(uv)) uv = process.binding('uv'); +- var errname = uv.errname(err); +- var e = new Error(syscall + ' ' + errname); +- e.code = errname; +- e.errno = errname; +- e.syscall = syscall; +- return e; +-}; ++} \ No newline at end of file diff --git a/docs/js/node_modules/core-util-is/lib/util.js b/docs/js/node_modules/core-util-is/lib/util.js new file mode 100644 index 000000000..ff4c851c0 --- /dev/null +++ b/docs/js/node_modules/core-util-is/lib/util.js @@ -0,0 +1,107 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. + +function isArray(arg) { + if (Array.isArray) { + return Array.isArray(arg); + } + return objectToString(arg) === '[object Array]'; +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = Buffer.isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} diff --git a/docs/js/node_modules/core-util-is/package.json b/docs/js/node_modules/core-util-is/package.json new file mode 100644 index 000000000..afcb2b61d --- /dev/null +++ b/docs/js/node_modules/core-util-is/package.json @@ -0,0 +1,62 @@ +{ + "_from": "core-util-is@1.0.2", + "_id": "core-util-is@1.0.2", + "_inBundle": false, + "_integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "_location": "/core-util-is", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": true, + "raw": "core-util-is@1.0.2", + "name": "core-util-is", + "escapedName": "core-util-is", + "rawSpec": "1.0.2", + "saveSpec": null, + "fetchSpec": "1.0.2" + }, + "_requiredBy": [ + "/verror" + ], + "_resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "_shasum": "b5fd54220aa2bc5ab57aab7140c940754503c1a7", + "_spec": "core-util-is@1.0.2", + "_where": "/Users/hectorip/Education/Eloquent-JavaScript-ES/node_modules/verror", + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "bugs": { + "url": "https://github.com/isaacs/core-util-is/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "The `util.is*` functions introduced in Node v0.12.", + "devDependencies": { + "tap": "^2.3.0" + }, + "homepage": "https://github.com/isaacs/core-util-is#readme", + "keywords": [ + "util", + "isBuffer", + "isArray", + "isNumber", + "isString", + "isRegExp", + "isThis", + "isThat", + "polyfill" + ], + "license": "MIT", + "main": "lib/util.js", + "name": "core-util-is", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/core-util-is.git" + }, + "scripts": { + "test": "tap test.js" + }, + "version": "1.0.2" +} diff --git a/docs/js/node_modules/core-util-is/test.js b/docs/js/node_modules/core-util-is/test.js new file mode 100644 index 000000000..1a490c65a --- /dev/null +++ b/docs/js/node_modules/core-util-is/test.js @@ -0,0 +1,68 @@ +var assert = require('tap'); + +var t = require('./lib/util'); + +assert.equal(t.isArray([]), true); +assert.equal(t.isArray({}), false); + +assert.equal(t.isBoolean(null), false); +assert.equal(t.isBoolean(true), true); +assert.equal(t.isBoolean(false), true); + +assert.equal(t.isNull(null), true); +assert.equal(t.isNull(undefined), false); +assert.equal(t.isNull(false), false); +assert.equal(t.isNull(), false); + +assert.equal(t.isNullOrUndefined(null), true); +assert.equal(t.isNullOrUndefined(undefined), true); +assert.equal(t.isNullOrUndefined(false), false); +assert.equal(t.isNullOrUndefined(), true); + +assert.equal(t.isNumber(null), false); +assert.equal(t.isNumber('1'), false); +assert.equal(t.isNumber(1), true); + +assert.equal(t.isString(null), false); +assert.equal(t.isString('1'), true); +assert.equal(t.isString(1), false); + +assert.equal(t.isSymbol(null), false); +assert.equal(t.isSymbol('1'), false); +assert.equal(t.isSymbol(1), false); +assert.equal(t.isSymbol(Symbol()), true); + +assert.equal(t.isUndefined(null), false); +assert.equal(t.isUndefined(undefined), true); +assert.equal(t.isUndefined(false), false); +assert.equal(t.isUndefined(), true); + +assert.equal(t.isRegExp(null), false); +assert.equal(t.isRegExp('1'), false); +assert.equal(t.isRegExp(new RegExp()), true); + +assert.equal(t.isObject({}), true); +assert.equal(t.isObject([]), true); +assert.equal(t.isObject(new RegExp()), true); +assert.equal(t.isObject(new Date()), true); + +assert.equal(t.isDate(null), false); +assert.equal(t.isDate('1'), false); +assert.equal(t.isDate(new Date()), true); + +assert.equal(t.isError(null), false); +assert.equal(t.isError({ err: true }), false); +assert.equal(t.isError(new Error()), true); + +assert.equal(t.isFunction(null), false); +assert.equal(t.isFunction({ }), false); +assert.equal(t.isFunction(function() {}), true); + +assert.equal(t.isPrimitive(null), true); +assert.equal(t.isPrimitive(''), true); +assert.equal(t.isPrimitive(0), true); +assert.equal(t.isPrimitive(new Date()), false); + +assert.equal(t.isBuffer(null), false); +assert.equal(t.isBuffer({}), false); +assert.equal(t.isBuffer(new Buffer(0)), true); diff --git a/docs/js/node_modules/cssom/LICENSE.txt b/docs/js/node_modules/cssom/LICENSE.txt new file mode 100644 index 000000000..bc57aacdd --- /dev/null +++ b/docs/js/node_modules/cssom/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) Nikita Vasilyev + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/js/node_modules/cssom/README.mdown b/docs/js/node_modules/cssom/README.mdown new file mode 100644 index 000000000..83af16baa --- /dev/null +++ b/docs/js/node_modules/cssom/README.mdown @@ -0,0 +1,67 @@ +# CSSOM + +CSSOM.js is a CSS parser written in pure JavaScript. It is also a partial implementation of [CSS Object Model](http://dev.w3.org/csswg/cssom/). + + CSSOM.parse("body {color: black}") + -> { + cssRules: [ + { + selectorText: "body", + style: { + 0: "color", + color: "black", + length: 1 + } + } + ] + } + + +## [Parser demo](http://nv.github.com/CSSOM/docs/parse.html) + +Works well in Google Chrome 6+, Safari 5+, Firefox 3.6+, Opera 10.63+. +Doesn't work in IE < 9 because of unsupported getters/setters. + +To use CSSOM.js in the browser you might want to build a one-file version that exposes a single `CSSOM` global variable: + + ➤ git clone https://github.com/NV/CSSOM.git + ➤ cd CSSOM + ➤ node build.js + build/CSSOM.js is done + +To use it with Node.js or any other CommonJS loader: + + ➤ npm install cssom + +## Don’t use it if... + +You parse CSS to mungle, minify or reformat code like this: + +```css +div { + background: gray; + background: linear-gradient(to bottom, white 0%, black 100%); +} +``` + +This pattern is often used to give browsers that don’t understand linear gradients a fallback solution (e.g. gray color in the example). +In CSSOM, `background: gray` [gets overwritten](http://nv.github.io/CSSOM/docs/parse.html#css=div%20%7B%0A%20%20%20%20%20%20background%3A%20gray%3B%0A%20%20%20%20background%3A%20linear-gradient(to%20bottom%2C%20white%200%25%2C%20black%20100%25)%3B%0A%7D). +It doesn't get preserved. + +If you do CSS mungling, minification, image inlining, and such, CSSOM.js is no good for you, considere using one of the following: + + * [postcss](https://github.com/postcss/postcss) + * [reworkcss/css](https://github.com/reworkcss/css) + * [csso](https://github.com/css/csso) + * [mensch](https://github.com/brettstimmerman/mensch) + + +## [Tests](http://nv.github.com/CSSOM/spec/) + +To run tests locally: + + ➤ git submodule init + ➤ git submodule update + + +## [Who uses CSSOM.js](https://github.com/NV/CSSOM/wiki/Who-uses-CSSOM.js) diff --git a/docs/js/node_modules/cssom/lib/CSSDocumentRule.js b/docs/js/node_modules/cssom/lib/CSSDocumentRule.js new file mode 100644 index 000000000..aec0776e2 --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSDocumentRule.js @@ -0,0 +1,39 @@ +//.CommonJS +var CSSOM = { + CSSRule: require("./CSSRule").CSSRule, + MatcherList: require("./MatcherList").MatcherList +}; +///CommonJS + + +/** + * @constructor + * @see https://developer.mozilla.org/en/CSS/@-moz-document + */ +CSSOM.CSSDocumentRule = function CSSDocumentRule() { + CSSOM.CSSRule.call(this); + this.matcher = new CSSOM.MatcherList(); + this.cssRules = []; +}; + +CSSOM.CSSDocumentRule.prototype = new CSSOM.CSSRule(); +CSSOM.CSSDocumentRule.prototype.constructor = CSSOM.CSSDocumentRule; +CSSOM.CSSDocumentRule.prototype.type = 10; +//FIXME +//CSSOM.CSSDocumentRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule; +//CSSOM.CSSDocumentRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule; + +Object.defineProperty(CSSOM.CSSDocumentRule.prototype, "cssText", { + get: function() { + var cssTexts = []; + for (var i=0, length=this.cssRules.length; i < length; i++) { + cssTexts.push(this.cssRules[i].cssText); + } + return "@-moz-document " + this.matcher.matcherText + " {" + cssTexts.join("") + "}"; + } +}); + + +//.CommonJS +exports.CSSDocumentRule = CSSOM.CSSDocumentRule; +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/CSSFontFaceRule.js b/docs/js/node_modules/cssom/lib/CSSFontFaceRule.js new file mode 100644 index 000000000..7a537fb0f --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSFontFaceRule.js @@ -0,0 +1,36 @@ +//.CommonJS +var CSSOM = { + CSSStyleDeclaration: require("./CSSStyleDeclaration").CSSStyleDeclaration, + CSSRule: require("./CSSRule").CSSRule +}; +///CommonJS + + +/** + * @constructor + * @see http://dev.w3.org/csswg/cssom/#css-font-face-rule + */ +CSSOM.CSSFontFaceRule = function CSSFontFaceRule() { + CSSOM.CSSRule.call(this); + this.style = new CSSOM.CSSStyleDeclaration(); + this.style.parentRule = this; +}; + +CSSOM.CSSFontFaceRule.prototype = new CSSOM.CSSRule(); +CSSOM.CSSFontFaceRule.prototype.constructor = CSSOM.CSSFontFaceRule; +CSSOM.CSSFontFaceRule.prototype.type = 5; +//FIXME +//CSSOM.CSSFontFaceRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule; +//CSSOM.CSSFontFaceRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule; + +// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSFontFaceRule.cpp +Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "cssText", { + get: function() { + return "@font-face {" + this.style.cssText + "}"; + } +}); + + +//.CommonJS +exports.CSSFontFaceRule = CSSOM.CSSFontFaceRule; +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/CSSHostRule.js b/docs/js/node_modules/cssom/lib/CSSHostRule.js new file mode 100644 index 000000000..365304fc9 --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSHostRule.js @@ -0,0 +1,37 @@ +//.CommonJS +var CSSOM = { + CSSRule: require("./CSSRule").CSSRule +}; +///CommonJS + + +/** + * @constructor + * @see http://www.w3.org/TR/shadow-dom/#host-at-rule + */ +CSSOM.CSSHostRule = function CSSHostRule() { + CSSOM.CSSRule.call(this); + this.cssRules = []; +}; + +CSSOM.CSSHostRule.prototype = new CSSOM.CSSRule(); +CSSOM.CSSHostRule.prototype.constructor = CSSOM.CSSHostRule; +CSSOM.CSSHostRule.prototype.type = 1001; +//FIXME +//CSSOM.CSSHostRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule; +//CSSOM.CSSHostRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule; + +Object.defineProperty(CSSOM.CSSHostRule.prototype, "cssText", { + get: function() { + var cssTexts = []; + for (var i=0, length=this.cssRules.length; i < length; i++) { + cssTexts.push(this.cssRules[i].cssText); + } + return "@host {" + cssTexts.join("") + "}"; + } +}); + + +//.CommonJS +exports.CSSHostRule = CSSOM.CSSHostRule; +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/CSSImportRule.js b/docs/js/node_modules/cssom/lib/CSSImportRule.js new file mode 100644 index 000000000..0398105a3 --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSImportRule.js @@ -0,0 +1,132 @@ +//.CommonJS +var CSSOM = { + CSSRule: require("./CSSRule").CSSRule, + CSSStyleSheet: require("./CSSStyleSheet").CSSStyleSheet, + MediaList: require("./MediaList").MediaList +}; +///CommonJS + + +/** + * @constructor + * @see http://dev.w3.org/csswg/cssom/#cssimportrule + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSImportRule + */ +CSSOM.CSSImportRule = function CSSImportRule() { + CSSOM.CSSRule.call(this); + this.href = ""; + this.media = new CSSOM.MediaList(); + this.styleSheet = new CSSOM.CSSStyleSheet(); +}; + +CSSOM.CSSImportRule.prototype = new CSSOM.CSSRule(); +CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule; +CSSOM.CSSImportRule.prototype.type = 3; + +Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", { + get: function() { + var mediaText = this.media.mediaText; + return "@import url(" + this.href + ")" + (mediaText ? " " + mediaText : "") + ";"; + }, + set: function(cssText) { + var i = 0; + + /** + * @import url(partial.css) screen, handheld; + * || | + * after-import media + * | + * url + */ + var state = ''; + + var buffer = ''; + var index; + for (var character; (character = cssText.charAt(i)); i++) { + + switch (character) { + case ' ': + case '\t': + case '\r': + case '\n': + case '\f': + if (state === 'after-import') { + state = 'url'; + } else { + buffer += character; + } + break; + + case '@': + if (!state && cssText.indexOf('@import', i) === i) { + state = 'after-import'; + i += 'import'.length; + buffer = ''; + } + break; + + case 'u': + if (state === 'url' && cssText.indexOf('url(', i) === i) { + index = cssText.indexOf(')', i + 1); + if (index === -1) { + throw i + ': ")" not found'; + } + i += 'url('.length; + var url = cssText.slice(i, index); + if (url[0] === url[url.length - 1]) { + if (url[0] === '"' || url[0] === "'") { + url = url.slice(1, -1); + } + } + this.href = url; + i = index; + state = 'media'; + } + break; + + case '"': + if (state === 'url') { + index = cssText.indexOf('"', i + 1); + if (!index) { + throw i + ": '\"' not found"; + } + this.href = cssText.slice(i + 1, index); + i = index; + state = 'media'; + } + break; + + case "'": + if (state === 'url') { + index = cssText.indexOf("'", i + 1); + if (!index) { + throw i + ': "\'" not found'; + } + this.href = cssText.slice(i + 1, index); + i = index; + state = 'media'; + } + break; + + case ';': + if (state === 'media') { + if (buffer) { + this.media.mediaText = buffer.trim(); + } + } + break; + + default: + if (state === 'media') { + buffer += character; + } + break; + } + } + } +}); + + +//.CommonJS +exports.CSSImportRule = CSSOM.CSSImportRule; +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/CSSKeyframeRule.js b/docs/js/node_modules/cssom/lib/CSSKeyframeRule.js new file mode 100644 index 000000000..c22f2f5e2 --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSKeyframeRule.js @@ -0,0 +1,37 @@ +//.CommonJS +var CSSOM = { + CSSRule: require("./CSSRule").CSSRule, + CSSStyleDeclaration: require('./CSSStyleDeclaration').CSSStyleDeclaration +}; +///CommonJS + + +/** + * @constructor + * @see http://www.w3.org/TR/css3-animations/#DOM-CSSKeyframeRule + */ +CSSOM.CSSKeyframeRule = function CSSKeyframeRule() { + CSSOM.CSSRule.call(this); + this.keyText = ''; + this.style = new CSSOM.CSSStyleDeclaration(); + this.style.parentRule = this; +}; + +CSSOM.CSSKeyframeRule.prototype = new CSSOM.CSSRule(); +CSSOM.CSSKeyframeRule.prototype.constructor = CSSOM.CSSKeyframeRule; +CSSOM.CSSKeyframeRule.prototype.type = 9; +//FIXME +//CSSOM.CSSKeyframeRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule; +//CSSOM.CSSKeyframeRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule; + +// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframeRule.cpp +Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "cssText", { + get: function() { + return this.keyText + " {" + this.style.cssText + "} "; + } +}); + + +//.CommonJS +exports.CSSKeyframeRule = CSSOM.CSSKeyframeRule; +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/CSSKeyframesRule.js b/docs/js/node_modules/cssom/lib/CSSKeyframesRule.js new file mode 100644 index 000000000..7e42717a1 --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSKeyframesRule.js @@ -0,0 +1,39 @@ +//.CommonJS +var CSSOM = { + CSSRule: require("./CSSRule").CSSRule +}; +///CommonJS + + +/** + * @constructor + * @see http://www.w3.org/TR/css3-animations/#DOM-CSSKeyframesRule + */ +CSSOM.CSSKeyframesRule = function CSSKeyframesRule() { + CSSOM.CSSRule.call(this); + this.name = ''; + this.cssRules = []; +}; + +CSSOM.CSSKeyframesRule.prototype = new CSSOM.CSSRule(); +CSSOM.CSSKeyframesRule.prototype.constructor = CSSOM.CSSKeyframesRule; +CSSOM.CSSKeyframesRule.prototype.type = 8; +//FIXME +//CSSOM.CSSKeyframesRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule; +//CSSOM.CSSKeyframesRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule; + +// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframesRule.cpp +Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "cssText", { + get: function() { + var cssTexts = []; + for (var i=0, length=this.cssRules.length; i < length; i++) { + cssTexts.push(" " + this.cssRules[i].cssText); + } + return "@" + (this._vendorPrefix || '') + "keyframes " + this.name + " { \n" + cssTexts.join("\n") + "\n}"; + } +}); + + +//.CommonJS +exports.CSSKeyframesRule = CSSOM.CSSKeyframesRule; +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/CSSMediaRule.js b/docs/js/node_modules/cssom/lib/CSSMediaRule.js new file mode 100644 index 000000000..367a35edf --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSMediaRule.js @@ -0,0 +1,41 @@ +//.CommonJS +var CSSOM = { + CSSRule: require("./CSSRule").CSSRule, + MediaList: require("./MediaList").MediaList +}; +///CommonJS + + +/** + * @constructor + * @see http://dev.w3.org/csswg/cssom/#cssmediarule + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSMediaRule + */ +CSSOM.CSSMediaRule = function CSSMediaRule() { + CSSOM.CSSRule.call(this); + this.media = new CSSOM.MediaList(); + this.cssRules = []; +}; + +CSSOM.CSSMediaRule.prototype = new CSSOM.CSSRule(); +CSSOM.CSSMediaRule.prototype.constructor = CSSOM.CSSMediaRule; +CSSOM.CSSMediaRule.prototype.type = 4; +//FIXME +//CSSOM.CSSMediaRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule; +//CSSOM.CSSMediaRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule; + +// http://opensource.apple.com/source/WebCore/WebCore-658.28/css/CSSMediaRule.cpp +Object.defineProperty(CSSOM.CSSMediaRule.prototype, "cssText", { + get: function() { + var cssTexts = []; + for (var i=0, length=this.cssRules.length; i < length; i++) { + cssTexts.push(this.cssRules[i].cssText); + } + return "@media " + this.media.mediaText + " {" + cssTexts.join("") + "}"; + } +}); + + +//.CommonJS +exports.CSSMediaRule = CSSOM.CSSMediaRule; +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/CSSOM.js b/docs/js/node_modules/cssom/lib/CSSOM.js new file mode 100644 index 000000000..95f356303 --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSOM.js @@ -0,0 +1,3 @@ +var CSSOM = {}; + + diff --git a/docs/js/node_modules/cssom/lib/CSSRule.js b/docs/js/node_modules/cssom/lib/CSSRule.js new file mode 100644 index 000000000..0b5e25b6f --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSRule.js @@ -0,0 +1,43 @@ +//.CommonJS +var CSSOM = {}; +///CommonJS + + +/** + * @constructor + * @see http://dev.w3.org/csswg/cssom/#the-cssrule-interface + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSRule + */ +CSSOM.CSSRule = function CSSRule() { + this.parentRule = null; + this.parentStyleSheet = null; +}; + +CSSOM.CSSRule.UNKNOWN_RULE = 0; // obsolete +CSSOM.CSSRule.STYLE_RULE = 1; +CSSOM.CSSRule.CHARSET_RULE = 2; // obsolete +CSSOM.CSSRule.IMPORT_RULE = 3; +CSSOM.CSSRule.MEDIA_RULE = 4; +CSSOM.CSSRule.FONT_FACE_RULE = 5; +CSSOM.CSSRule.PAGE_RULE = 6; +CSSOM.CSSRule.KEYFRAMES_RULE = 7; +CSSOM.CSSRule.KEYFRAME_RULE = 8; +CSSOM.CSSRule.MARGIN_RULE = 9; +CSSOM.CSSRule.NAMESPACE_RULE = 10; +CSSOM.CSSRule.COUNTER_STYLE_RULE = 11; +CSSOM.CSSRule.SUPPORTS_RULE = 12; +CSSOM.CSSRule.DOCUMENT_RULE = 13; +CSSOM.CSSRule.FONT_FEATURE_VALUES_RULE = 14; +CSSOM.CSSRule.VIEWPORT_RULE = 15; +CSSOM.CSSRule.REGION_STYLE_RULE = 16; + + +CSSOM.CSSRule.prototype = { + constructor: CSSOM.CSSRule + //FIXME +}; + + +//.CommonJS +exports.CSSRule = CSSOM.CSSRule; +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/CSSStyleDeclaration.js b/docs/js/node_modules/cssom/lib/CSSStyleDeclaration.js new file mode 100644 index 000000000..b43b9afa9 --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSStyleDeclaration.js @@ -0,0 +1,148 @@ +//.CommonJS +var CSSOM = {}; +///CommonJS + + +/** + * @constructor + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration + */ +CSSOM.CSSStyleDeclaration = function CSSStyleDeclaration(){ + this.length = 0; + this.parentRule = null; + + // NON-STANDARD + this._importants = {}; +}; + + +CSSOM.CSSStyleDeclaration.prototype = { + + constructor: CSSOM.CSSStyleDeclaration, + + /** + * + * @param {string} name + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-getPropertyValue + * @return {string} the value of the property if it has been explicitly set for this declaration block. + * Returns the empty string if the property has not been set. + */ + getPropertyValue: function(name) { + return this[name] || ""; + }, + + /** + * + * @param {string} name + * @param {string} value + * @param {string} [priority=null] "important" or null + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty + */ + setProperty: function(name, value, priority) { + if (this[name]) { + // Property already exist. Overwrite it. + var index = Array.prototype.indexOf.call(this, name); + if (index < 0) { + this[this.length] = name; + this.length++; + } + } else { + // New property. + this[this.length] = name; + this.length++; + } + this[name] = value + ""; + this._importants[name] = priority; + }, + + /** + * + * @param {string} name + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-removeProperty + * @return {string} the value of the property if it has been explicitly set for this declaration block. + * Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property. + */ + removeProperty: function(name) { + if (!(name in this)) { + return ""; + } + var index = Array.prototype.indexOf.call(this, name); + if (index < 0) { + return ""; + } + var prevValue = this[name]; + this[name] = ""; + + // That's what WebKit and Opera do + Array.prototype.splice.call(this, index, 1); + + // That's what Firefox does + //this[index] = "" + + return prevValue; + }, + + getPropertyCSSValue: function() { + //FIXME + }, + + /** + * + * @param {String} name + */ + getPropertyPriority: function(name) { + return this._importants[name] || ""; + }, + + + /** + * element.style.overflow = "auto" + * element.style.getPropertyShorthand("overflow-x") + * -> "overflow" + */ + getPropertyShorthand: function() { + //FIXME + }, + + isPropertyImplicit: function() { + //FIXME + }, + + // Doesn't work in IE < 9 + get cssText(){ + var properties = []; + for (var i=0, length=this.length; i < length; ++i) { + var name = this[i]; + var value = this.getPropertyValue(name); + var priority = this.getPropertyPriority(name); + if (priority) { + priority = " !" + priority; + } + properties[i] = name + ": " + value + priority + ";"; + } + return properties.join(" "); + }, + + set cssText(text){ + var i, name; + for (i = this.length; i--;) { + name = this[i]; + this[name] = ""; + } + Array.prototype.splice.call(this, 0, this.length); + this._importants = {}; + + var dummyRule = CSSOM.parse('#bogus{' + text + '}').cssRules[0].style; + var length = dummyRule.length; + for (i = 0; i < length; ++i) { + name = dummyRule[i]; + this.setProperty(dummyRule[i], dummyRule.getPropertyValue(name), dummyRule.getPropertyPriority(name)); + } + } +}; + + +//.CommonJS +exports.CSSStyleDeclaration = CSSOM.CSSStyleDeclaration; +CSSOM.parse = require('./parse').parse; // Cannot be included sooner due to the mutual dependency between parse.js and CSSStyleDeclaration.js +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/CSSStyleRule.js b/docs/js/node_modules/cssom/lib/CSSStyleRule.js new file mode 100644 index 000000000..630b3f85c --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSStyleRule.js @@ -0,0 +1,190 @@ +//.CommonJS +var CSSOM = { + CSSStyleDeclaration: require("./CSSStyleDeclaration").CSSStyleDeclaration, + CSSRule: require("./CSSRule").CSSRule +}; +///CommonJS + + +/** + * @constructor + * @see http://dev.w3.org/csswg/cssom/#cssstylerule + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleRule + */ +CSSOM.CSSStyleRule = function CSSStyleRule() { + CSSOM.CSSRule.call(this); + this.selectorText = ""; + this.style = new CSSOM.CSSStyleDeclaration(); + this.style.parentRule = this; +}; + +CSSOM.CSSStyleRule.prototype = new CSSOM.CSSRule(); +CSSOM.CSSStyleRule.prototype.constructor = CSSOM.CSSStyleRule; +CSSOM.CSSStyleRule.prototype.type = 1; + +Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", { + get: function() { + var text; + if (this.selectorText) { + text = this.selectorText + " {" + this.style.cssText + "}"; + } else { + text = ""; + } + return text; + }, + set: function(cssText) { + var rule = CSSOM.CSSStyleRule.parse(cssText); + this.style = rule.style; + this.selectorText = rule.selectorText; + } +}); + + +/** + * NON-STANDARD + * lightweight version of parse.js. + * @param {string} ruleText + * @return CSSStyleRule + */ +CSSOM.CSSStyleRule.parse = function(ruleText) { + var i = 0; + var state = "selector"; + var index; + var j = i; + var buffer = ""; + + var SIGNIFICANT_WHITESPACE = { + "selector": true, + "value": true + }; + + var styleRule = new CSSOM.CSSStyleRule(); + var name, priority=""; + + for (var character; (character = ruleText.charAt(i)); i++) { + + switch (character) { + + case " ": + case "\t": + case "\r": + case "\n": + case "\f": + if (SIGNIFICANT_WHITESPACE[state]) { + // Squash 2 or more white-spaces in the row into 1 + switch (ruleText.charAt(i - 1)) { + case " ": + case "\t": + case "\r": + case "\n": + case "\f": + break; + default: + buffer += " "; + break; + } + } + break; + + // String + case '"': + j = i + 1; + index = ruleText.indexOf('"', j) + 1; + if (!index) { + throw '" is missing'; + } + buffer += ruleText.slice(i, index); + i = index - 1; + break; + + case "'": + j = i + 1; + index = ruleText.indexOf("'", j) + 1; + if (!index) { + throw "' is missing"; + } + buffer += ruleText.slice(i, index); + i = index - 1; + break; + + // Comment + case "/": + if (ruleText.charAt(i + 1) === "*") { + i += 2; + index = ruleText.indexOf("*/", i); + if (index === -1) { + throw new SyntaxError("Missing */"); + } else { + i = index + 1; + } + } else { + buffer += character; + } + break; + + case "{": + if (state === "selector") { + styleRule.selectorText = buffer.trim(); + buffer = ""; + state = "name"; + } + break; + + case ":": + if (state === "name") { + name = buffer.trim(); + buffer = ""; + state = "value"; + } else { + buffer += character; + } + break; + + case "!": + if (state === "value" && ruleText.indexOf("!important", i) === i) { + priority = "important"; + i += "important".length; + } else { + buffer += character; + } + break; + + case ";": + if (state === "value") { + styleRule.style.setProperty(name, buffer.trim(), priority); + priority = ""; + buffer = ""; + state = "name"; + } else { + buffer += character; + } + break; + + case "}": + if (state === "value") { + styleRule.style.setProperty(name, buffer.trim(), priority); + priority = ""; + buffer = ""; + } else if (state === "name") { + break; + } else { + buffer += character; + } + state = "selector"; + break; + + default: + buffer += character; + break; + + } + } + + return styleRule; + +}; + + +//.CommonJS +exports.CSSStyleRule = CSSOM.CSSStyleRule; +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/CSSStyleSheet.js b/docs/js/node_modules/cssom/lib/CSSStyleSheet.js new file mode 100644 index 000000000..f0e0dfc59 --- /dev/null +++ b/docs/js/node_modules/cssom/lib/CSSStyleSheet.js @@ -0,0 +1,88 @@ +//.CommonJS +var CSSOM = { + StyleSheet: require("./StyleSheet").StyleSheet, + CSSStyleRule: require("./CSSStyleRule").CSSStyleRule +}; +///CommonJS + + +/** + * @constructor + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet + */ +CSSOM.CSSStyleSheet = function CSSStyleSheet() { + CSSOM.StyleSheet.call(this); + this.cssRules = []; +}; + + +CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet(); +CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet; + + +/** + * Used to insert a new rule into the style sheet. The new rule now becomes part of the cascade. + * + * sheet = new Sheet("body {margin: 0}") + * sheet.toString() + * -> "body{margin:0;}" + * sheet.insertRule("img {border: none}", 0) + * -> 0 + * sheet.toString() + * -> "img{border:none;}body{margin:0;}" + * + * @param {string} rule + * @param {number} index + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-insertRule + * @return {number} The index within the style sheet's rule collection of the newly inserted rule. + */ +CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) { + if (index < 0 || index > this.cssRules.length) { + throw new RangeError("INDEX_SIZE_ERR"); + } + var cssRule = CSSOM.parse(rule).cssRules[0]; + cssRule.parentStyleSheet = this; + this.cssRules.splice(index, 0, cssRule); + return index; +}; + + +/** + * Used to delete a rule from the style sheet. + * + * sheet = new Sheet("img{border:none} body{margin:0}") + * sheet.toString() + * -> "img{border:none;}body{margin:0;}" + * sheet.deleteRule(0) + * sheet.toString() + * -> "body{margin:0;}" + * + * @param {number} index within the style sheet's rule list of the rule to remove. + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-deleteRule + */ +CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) { + if (index < 0 || index >= this.cssRules.length) { + throw new RangeError("INDEX_SIZE_ERR"); + } + this.cssRules.splice(index, 1); +}; + + +/** + * NON-STANDARD + * @return {string} serialize stylesheet + */ +CSSOM.CSSStyleSheet.prototype.toString = function() { + var result = ""; + var rules = this.cssRules; + for (var i=0; i 1000 ? '1000px' : 'auto'); + * } + */ +CSSOM.CSSValueExpression.prototype.parse = function() { + var token = this._token, + idx = this._idx; + + var character = '', + expression = '', + error = '', + info, + paren = []; + + + for (; ; ++idx) { + character = token.charAt(idx); + + // end of token + if (character === '') { + error = 'css expression error: unfinished expression!'; + break; + } + + switch(character) { + case '(': + paren.push(character); + expression += character; + break; + + case ')': + paren.pop(character); + expression += character; + break; + + case '/': + if ((info = this._parseJSComment(token, idx))) { // comment? + if (info.error) { + error = 'css expression error: unfinished comment in expression!'; + } else { + idx = info.idx; + // ignore the comment + } + } else if ((info = this._parseJSRexExp(token, idx))) { // regexp + idx = info.idx; + expression += info.text; + } else { // other + expression += character; + } + break; + + case "'": + case '"': + info = this._parseJSString(token, idx, character); + if (info) { // string + idx = info.idx; + expression += info.text; + } else { + expression += character; + } + break; + + default: + expression += character; + break; + } + + if (error) { + break; + } + + // end of expression + if (paren.length === 0) { + break; + } + } + + var ret; + if (error) { + ret = { + error: error + }; + } else { + ret = { + idx: idx, + expression: expression + }; + } + + return ret; +}; + + +/** + * + * @return {Object|false} + * - idx: + * - text: + * or + * - error: + * or + * false + * + */ +CSSOM.CSSValueExpression.prototype._parseJSComment = function(token, idx) { + var nextChar = token.charAt(idx + 1), + text; + + if (nextChar === '/' || nextChar === '*') { + var startIdx = idx, + endIdx, + commentEndChar; + + if (nextChar === '/') { // line comment + commentEndChar = '\n'; + } else if (nextChar === '*') { // block comment + commentEndChar = '*/'; + } + + endIdx = token.indexOf(commentEndChar, startIdx + 1 + 1); + if (endIdx !== -1) { + endIdx = endIdx + commentEndChar.length - 1; + text = token.substring(idx, endIdx + 1); + return { + idx: endIdx, + text: text + }; + } else { + var error = 'css expression error: unfinished comment in expression!'; + return { + error: error + }; + } + } else { + return false; + } +}; + + +/** + * + * @return {Object|false} + * - idx: + * - text: + * or + * false + * + */ +CSSOM.CSSValueExpression.prototype._parseJSString = function(token, idx, sep) { + var endIdx = this._findMatchedIdx(token, idx, sep), + text; + + if (endIdx === -1) { + return false; + } else { + text = token.substring(idx, endIdx + sep.length); + + return { + idx: endIdx, + text: text + }; + } +}; + + +/** + * parse regexp in css expression + * + * @return {Object|false} + * - idx: + * - regExp: + * or + * false + */ + +/* + +all legal RegExp + +/a/ +(/a/) +[/a/] +[12, /a/] + +!/a/ + ++/a/ +-/a/ +* /a/ +/ /a/ +%/a/ + +===/a/ +!==/a/ +==/a/ +!=/a/ +>/a/ +>=/a/ +>/a/ +>>>/a/ + +&&/a/ +||/a/ +?/a/ +=/a/ +,/a/ + + delete /a/ + in /a/ +instanceof /a/ + new /a/ + typeof /a/ + void /a/ + +*/ +CSSOM.CSSValueExpression.prototype._parseJSRexExp = function(token, idx) { + var before = token.substring(0, idx).replace(/\s+$/, ""), + legalRegx = [ + /^$/, + /\($/, + /\[$/, + /\!$/, + /\+$/, + /\-$/, + /\*$/, + /\/\s+/, + /\%$/, + /\=$/, + /\>$/, + /<$/, + /\&$/, + /\|$/, + /\^$/, + /\~$/, + /\?$/, + /\,$/, + /delete$/, + /in$/, + /instanceof$/, + /new$/, + /typeof$/, + /void$/ + ]; + + var isLegal = legalRegx.some(function(reg) { + return reg.test(before); + }); + + if (!isLegal) { + return false; + } else { + var sep = '/'; + + // same logic as string + return this._parseJSString(token, idx, sep); + } +}; + + +/** + * + * find next sep(same line) index in `token` + * + * @return {Number} + * + */ +CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep) { + var startIdx = idx, + endIdx; + + var NOT_FOUND = -1; + + while(true) { + endIdx = token.indexOf(sep, startIdx + 1); + + if (endIdx === -1) { // not found + endIdx = NOT_FOUND; + break; + } else { + var text = token.substring(idx + 1, endIdx), + matched = text.match(/\\+$/); + if (!matched || matched[0] % 2 === 0) { // not escaped + break; + } else { + startIdx = endIdx; + } + } + } + + // boundary must be in the same line(js sting or regexp) + var nextNewLineIdx = token.indexOf('\n', idx + 1); + if (nextNewLineIdx < endIdx) { + endIdx = NOT_FOUND; + } + + + return endIdx; +}; + + + + +//.CommonJS +exports.CSSValueExpression = CSSOM.CSSValueExpression; +///CommonJS diff --git a/docs/js/node_modules/cssom/lib/MatcherList.js b/docs/js/node_modules/cssom/lib/MatcherList.js new file mode 100644 index 000000000..a79158518 --- /dev/null +++ b/docs/js/node_modules/cssom/lib/MatcherList.js @@ -0,0 +1,62 @@ +//.CommonJS +var CSSOM = {}; +///CommonJS + + +/** + * @constructor + * @see https://developer.mozilla.org/en/CSS/@-moz-document + */ +CSSOM.MatcherList = function MatcherList(){ + this.length = 0; +}; + +CSSOM.MatcherList.prototype = { + + constructor: CSSOM.MatcherList, + + /** + * @return {string} + */ + get matcherText() { + return Array.prototype.join.call(this, ", "); + }, + + /** + * @param {string} value + */ + set matcherText(value) { + // just a temporary solution, actually it may be wrong by just split the value with ',', because a url can include ','. + var values = value.split(","); + var length = this.length = values.length; + for (var i=0; i 0; + + while (ancestorRules.length > 0) { + parentRule = ancestorRules.pop(); + + if ( + parentRule.constructor.name === "CSSMediaRule" + || parentRule.constructor.name === "CSSSupportsRule" + ) { + prevScope = currentScope; + currentScope = parentRule; + currentScope.cssRules.push(prevScope); + break; + } + + if (ancestorRules.length === 0) { + hasAncestors = false; + } + } + + if (!hasAncestors) { + currentScope.__ends = i + 1; + styleSheet.cssRules.push(currentScope); + currentScope = styleSheet; + parentRule = null; + } + + buffer = ""; + state = "before-selector"; + break; + } + break; + + default: + switch (state) { + case "before-selector": + state = "selector"; + styleRule = new CSSOM.CSSStyleRule(); + styleRule.__starts = i; + break; + case "before-name": + state = "name"; + break; + case "before-value": + state = "value"; + break; + case "importRule-begin": + state = "importRule"; + break; + } + buffer += character; + break; + } + } + + return styleSheet; +}; + + +//.CommonJS +exports.parse = CSSOM.parse; +// The following modules cannot be included sooner due to the mutual dependency with parse.js +CSSOM.CSSStyleSheet = require("./CSSStyleSheet").CSSStyleSheet; +CSSOM.CSSStyleRule = require("./CSSStyleRule").CSSStyleRule; +CSSOM.CSSImportRule = require("./CSSImportRule").CSSImportRule; +CSSOM.CSSMediaRule = require("./CSSMediaRule").CSSMediaRule; +CSSOM.CSSSupportsRule = require("./CSSSupportsRule").CSSSupportsRule; +CSSOM.CSSFontFaceRule = require("./CSSFontFaceRule").CSSFontFaceRule; +CSSOM.CSSHostRule = require("./CSSHostRule").CSSHostRule; +CSSOM.CSSStyleDeclaration = require('./CSSStyleDeclaration').CSSStyleDeclaration; +CSSOM.CSSKeyframeRule = require('./CSSKeyframeRule').CSSKeyframeRule; +CSSOM.CSSKeyframesRule = require('./CSSKeyframesRule').CSSKeyframesRule; +CSSOM.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression; +CSSOM.CSSDocumentRule = require('./CSSDocumentRule').CSSDocumentRule; +///CommonJS diff --git a/docs/js/node_modules/cssom/package.json b/docs/js/node_modules/cssom/package.json new file mode 100644 index 000000000..fd22917ac --- /dev/null +++ b/docs/js/node_modules/cssom/package.json @@ -0,0 +1,54 @@ +{ + "_from": "cssom@>= 0.3.2 < 0.4.0", + "_id": "cssom@0.3.8", + "_inBundle": false, + "_integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "_location": "/cssom", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": true, + "raw": "cssom@>= 0.3.2 < 0.4.0", + "name": "cssom", + "escapedName": "cssom", + "rawSpec": ">= 0.3.2 < 0.4.0", + "saveSpec": null, + "fetchSpec": ">= 0.3.2 < 0.4.0" + }, + "_requiredBy": [ + "/cssstyle", + "/jsdom" + ], + "_resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "_shasum": "9f1276f5b2b463f2114d3f2c75250af8c1a36f4a", + "_spec": "cssom@>= 0.3.2 < 0.4.0", + "_where": "/Users/hectorip/Education/Eloquent-JavaScript-ES/node_modules/jsdom", + "author": { + "name": "Nikita Vasilyev", + "email": "me@elv1s.ru" + }, + "bugs": { + "url": "https://github.com/NV/CSSOM/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "CSS Object Model implementation and CSS parser", + "files": [ + "lib/" + ], + "homepage": "https://github.com/NV/CSSOM#readme", + "keywords": [ + "CSS", + "CSSOM", + "parser", + "styleSheet" + ], + "license": "MIT", + "main": "./lib/index.js", + "name": "cssom", + "repository": { + "type": "git", + "url": "git+https://github.com/NV/CSSOM.git" + }, + "version": "0.3.8" +} diff --git a/docs/js/node_modules/cssstyle/.eslintignore b/docs/js/node_modules/cssstyle/.eslintignore new file mode 100644 index 000000000..f9e0b7329 --- /dev/null +++ b/docs/js/node_modules/cssstyle/.eslintignore @@ -0,0 +1,3 @@ +node_modules +lib/implementedProperties.js +lib/properties.js diff --git a/docs/js/node_modules/cssstyle/.eslintrc.js b/docs/js/node_modules/cssstyle/.eslintrc.js new file mode 100644 index 000000000..770d7996f --- /dev/null +++ b/docs/js/node_modules/cssstyle/.eslintrc.js @@ -0,0 +1,50 @@ +'use strict'; + +module.exports = { + root: true, + extends: ['eslint:recommended', 'prettier'], + parserOptions: { + ecmaVersion: 2018, + }, + env: { + es6: true, + }, + globals: { + exports: true, + module: true, + require: true, + window: true, + }, + plugins: ['prettier'], + rules: { + 'prettier/prettier': [ + 'warn', + { + printWidth: 100, + singleQuote: true, + trailingComma: 'es5', + }, + ], + strict: ['warn', 'global'], + }, + overrides: [ + { + files: ['lib/implementedProperties.js', 'lib/properties.js'], + rules: { + 'prettier/prettier': 'off', + }, + }, + { + files: 'scripts/**/*', + rules: { + 'no-console': 'off', + }, + }, + { + files: ['scripts/**/*', 'tests/**/*'], + env: { + node: true, + }, + }, + ], +}; diff --git a/docs/js/node_modules/cssstyle/.travis.yml b/docs/js/node_modules/cssstyle/.travis.yml new file mode 100644 index 000000000..cf9a2232e --- /dev/null +++ b/docs/js/node_modules/cssstyle/.travis.yml @@ -0,0 +1,15 @@ +sudo: false +language: node_js +cache: + directories: + - node_modules +notifications: + email: true +node_js: + - 6 + - 8 + - 10 + - 11 + +script: + - npm run test-ci diff --git a/docs/js/node_modules/cssstyle/MIT-LICENSE.txt b/docs/js/node_modules/cssstyle/MIT-LICENSE.txt new file mode 100644 index 000000000..060a7e630 --- /dev/null +++ b/docs/js/node_modules/cssstyle/MIT-LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) Chad Walker + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/js/node_modules/cssstyle/README.md b/docs/js/node_modules/cssstyle/README.md new file mode 100644 index 000000000..66b14ae9b --- /dev/null +++ b/docs/js/node_modules/cssstyle/README.md @@ -0,0 +1,27 @@ +# CSSStyleDeclaration + +[![NpmVersion](https://img.shields.io/npm/v/cssstyle.svg)](https://www.npmjs.com/package/cssstyle) [![Build Status](https://travis-ci.org/jsakas/CSSStyleDeclaration.svg?branch=master)](https://travis-ci.org/jsakas/CSSStyleDeclaration) + +CSSStyleDeclaration is a work-a-like to the CSSStyleDeclaration class in Nikita Vasilyev's [CSSOM](https://github.com/NV/CSSOM). I made it so that when using [jQuery in node](https://github.com/tmtk75/node-jquery) setting css attributes via $.fn.css() would work. node-jquery uses [jsdom](https://github.com/tmpvar/jsdom) to create a DOM to use in node. jsdom uses CSSOM for styling, and CSSOM's implementation of the [CSSStyleDeclaration](http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration) doesn't support [CSS2Properties](http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSS2Properties), which is how jQuery's [$.fn.css()](http://api.jquery.com/css/) operates. + +### Why not just issue a pull request? + +Well, NV wants to keep CSSOM fast (which I can appreciate) and CSS2Properties aren't required by the standard (though every browser has the interface). So I figured the path of least resistance would be to just modify this one class, publish it as a node module (that requires CSSOM) and then make a pull request of jsdom to use it. + +### How do I test this code? + +`npm test` should do the trick, assuming you have the dev dependencies installed: + +``` +$ npm test + +tests +✔ Verify Has Properties +✔ Verify Has Functions +✔ Verify Has Special Properties +✔ Test From Style String +✔ Test From Properties +✔ Test Shorthand Properties +✔ Test width and height Properties and null and empty strings +✔ Test Implicit Properties +``` diff --git a/docs/js/node_modules/cssstyle/lib/CSSStyleDeclaration.js b/docs/js/node_modules/cssstyle/lib/CSSStyleDeclaration.js new file mode 100644 index 000000000..0bb9ecede --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/CSSStyleDeclaration.js @@ -0,0 +1,255 @@ +/********************************************************************* + * This is a fork from the CSS Style Declaration part of + * https://github.com/NV/CSSOM + ********************************************************************/ +'use strict'; +var CSSOM = require('cssom'); +var allProperties = require('./allProperties'); +var allExtraProperties = require('./allExtraProperties'); +var implementedProperties = require('./implementedProperties'); +var { dashedToCamelCase } = require('./parsers'); +var getBasicPropertyDescriptor = require('./utils/getBasicPropertyDescriptor'); + +/** + * @constructor + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration + */ +var CSSStyleDeclaration = function CSSStyleDeclaration(onChangeCallback) { + this._values = {}; + this._importants = {}; + this._length = 0; + this._onChange = + onChangeCallback || + function() { + return; + }; +}; +CSSStyleDeclaration.prototype = { + constructor: CSSStyleDeclaration, + + /** + * + * @param {string} name + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-getPropertyValue + * @return {string} the value of the property if it has been explicitly set for this declaration block. + * Returns the empty string if the property has not been set. + */ + getPropertyValue: function(name) { + if (!this._values.hasOwnProperty(name)) { + return ''; + } + return this._values[name].toString(); + }, + + /** + * + * @param {string} name + * @param {string} value + * @param {string} [priority=null] "important" or null + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty + */ + setProperty: function(name, value, priority) { + if (value === undefined) { + return; + } + if (value === null || value === '') { + this.removeProperty(name); + return; + } + var lowercaseName = name.toLowerCase(); + if (!allProperties.has(lowercaseName) && !allExtraProperties.has(lowercaseName)) { + return; + } + + this[lowercaseName] = value; + this._importants[lowercaseName] = priority; + }, + _setProperty: function(name, value, priority) { + if (value === undefined) { + return; + } + if (value === null || value === '') { + this.removeProperty(name); + return; + } + if (this._values[name]) { + // Property already exist. Overwrite it. + var index = Array.prototype.indexOf.call(this, name); + if (index < 0) { + this[this._length] = name; + this._length++; + } + } else { + // New property. + this[this._length] = name; + this._length++; + } + this._values[name] = value; + this._importants[name] = priority; + this._onChange(this.cssText); + }, + + /** + * + * @param {string} name + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-removeProperty + * @return {string} the value of the property if it has been explicitly set for this declaration block. + * Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property. + */ + removeProperty: function(name) { + if (!this._values.hasOwnProperty(name)) { + return ''; + } + + var prevValue = this._values[name]; + delete this._values[name]; + delete this._importants[name]; + + var index = Array.prototype.indexOf.call(this, name); + if (index < 0) { + return prevValue; + } + + // That's what WebKit and Opera do + Array.prototype.splice.call(this, index, 1); + + // That's what Firefox does + //this[index] = "" + + this._onChange(this.cssText); + return prevValue; + }, + + /** + * + * @param {String} name + */ + getPropertyPriority: function(name) { + return this._importants[name] || ''; + }, + + getPropertyCSSValue: function() { + //FIXME + return; + }, + + /** + * element.style.overflow = "auto" + * element.style.getPropertyShorthand("overflow-x") + * -> "overflow" + */ + getPropertyShorthand: function() { + //FIXME + return; + }, + + isPropertyImplicit: function() { + //FIXME + return; + }, + + /** + * http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-item + */ + item: function(index) { + index = parseInt(index, 10); + if (index < 0 || index >= this._length) { + return ''; + } + return this[index]; + }, +}; + +Object.defineProperties(CSSStyleDeclaration.prototype, { + cssText: { + get: function() { + var properties = []; + var i; + var name; + var value; + var priority; + for (i = 0; i < this._length; i++) { + name = this[i]; + value = this.getPropertyValue(name); + priority = this.getPropertyPriority(name); + if (priority !== '') { + priority = ' !' + priority; + } + properties.push([name, ': ', value, priority, ';'].join('')); + } + return properties.join(' '); + }, + set: function(value) { + var i; + this._values = {}; + Array.prototype.splice.call(this, 0, this._length); + this._importants = {}; + var dummyRule; + try { + dummyRule = CSSOM.parse('#bogus{' + value + '}').cssRules[0].style; + } catch (err) { + // malformed css, just return + return; + } + var rule_length = dummyRule.length; + var name; + for (i = 0; i < rule_length; ++i) { + name = dummyRule[i]; + this.setProperty( + dummyRule[i], + dummyRule.getPropertyValue(name), + dummyRule.getPropertyPriority(name) + ); + } + this._onChange(this.cssText); + }, + enumerable: true, + configurable: true, + }, + parentRule: { + get: function() { + return null; + }, + enumerable: true, + configurable: true, + }, + length: { + get: function() { + return this._length; + }, + /** + * This deletes indices if the new length is less then the current + * length. If the new length is more, it does nothing, the new indices + * will be undefined until set. + **/ + set: function(value) { + var i; + for (i = value; i < this._length; i++) { + delete this[i]; + } + this._length = value; + }, + enumerable: true, + configurable: true, + }, +}); + +require('./properties')(CSSStyleDeclaration.prototype); + +allProperties.forEach(function(property) { + if (!implementedProperties.has(property)) { + var declaration = getBasicPropertyDescriptor(property); + Object.defineProperty(CSSStyleDeclaration.prototype, property, declaration); + Object.defineProperty(CSSStyleDeclaration.prototype, dashedToCamelCase(property), declaration); + } +}); + +allExtraProperties.forEach(function(property) { + if (!implementedProperties.has(property)) { + var declaration = getBasicPropertyDescriptor(property); + Object.defineProperty(CSSStyleDeclaration.prototype, property, declaration); + Object.defineProperty(CSSStyleDeclaration.prototype, dashedToCamelCase(property), declaration); + } +}); + +exports.CSSStyleDeclaration = CSSStyleDeclaration; diff --git a/docs/js/node_modules/cssstyle/lib/allExtraProperties.js b/docs/js/node_modules/cssstyle/lib/allExtraProperties.js new file mode 100644 index 000000000..0d66ca8f4 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/allExtraProperties.js @@ -0,0 +1,248 @@ +'use strict'; + +/** + * This file contains all implemented properties that are not a part of any + * current specifications or drafts, but are handled by browsers nevertheless. + */ + +var allExtraProperties = new Set(); +module.exports = allExtraProperties; +allExtraProperties.add('background-position-x'); +allExtraProperties.add('background-position-y'); +allExtraProperties.add('background-repeat-x'); +allExtraProperties.add('background-repeat-y'); +allExtraProperties.add('color-interpolation'); +allExtraProperties.add('color-profile'); +allExtraProperties.add('color-rendering'); +allExtraProperties.add('css-float'); +allExtraProperties.add('enable-background'); +allExtraProperties.add('fill'); +allExtraProperties.add('fill-opacity'); +allExtraProperties.add('fill-rule'); +allExtraProperties.add('glyph-orientation-horizontal'); +allExtraProperties.add('image-rendering'); +allExtraProperties.add('kerning'); +allExtraProperties.add('marker'); +allExtraProperties.add('marker-end'); +allExtraProperties.add('marker-mid'); +allExtraProperties.add('marker-offset'); +allExtraProperties.add('marker-start'); +allExtraProperties.add('marks'); +allExtraProperties.add('pointer-events'); +allExtraProperties.add('shape-rendering'); +allExtraProperties.add('size'); +allExtraProperties.add('src'); +allExtraProperties.add('stop-color'); +allExtraProperties.add('stop-opacity'); +allExtraProperties.add('stroke'); +allExtraProperties.add('stroke-dasharray'); +allExtraProperties.add('stroke-dashoffset'); +allExtraProperties.add('stroke-linecap'); +allExtraProperties.add('stroke-linejoin'); +allExtraProperties.add('stroke-miterlimit'); +allExtraProperties.add('stroke-opacity'); +allExtraProperties.add('stroke-width'); +allExtraProperties.add('text-anchor'); +allExtraProperties.add('text-line-through'); +allExtraProperties.add('text-line-through-color'); +allExtraProperties.add('text-line-through-mode'); +allExtraProperties.add('text-line-through-style'); +allExtraProperties.add('text-line-through-width'); +allExtraProperties.add('text-overline'); +allExtraProperties.add('text-overline-color'); +allExtraProperties.add('text-overline-mode'); +allExtraProperties.add('text-overline-style'); +allExtraProperties.add('text-overline-width'); +allExtraProperties.add('text-rendering'); +allExtraProperties.add('text-underline'); +allExtraProperties.add('text-underline-color'); +allExtraProperties.add('text-underline-mode'); +allExtraProperties.add('text-underline-style'); +allExtraProperties.add('text-underline-width'); +allExtraProperties.add('unicode-range'); +allExtraProperties.add('vector-effect'); +allExtraProperties.add('webkit-animation'); +allExtraProperties.add('webkit-animation-delay'); +allExtraProperties.add('webkit-animation-direction'); +allExtraProperties.add('webkit-animation-duration'); +allExtraProperties.add('webkit-animation-fill-mode'); +allExtraProperties.add('webkit-animation-iteration-count'); +allExtraProperties.add('webkit-animation-name'); +allExtraProperties.add('webkit-animation-play-state'); +allExtraProperties.add('webkit-animation-timing-function'); +allExtraProperties.add('webkit-appearance'); +allExtraProperties.add('webkit-aspect-ratio'); +allExtraProperties.add('webkit-backface-visibility'); +allExtraProperties.add('webkit-background-clip'); +allExtraProperties.add('webkit-background-composite'); +allExtraProperties.add('webkit-background-origin'); +allExtraProperties.add('webkit-background-size'); +allExtraProperties.add('webkit-border-after'); +allExtraProperties.add('webkit-border-after-color'); +allExtraProperties.add('webkit-border-after-style'); +allExtraProperties.add('webkit-border-after-width'); +allExtraProperties.add('webkit-border-before'); +allExtraProperties.add('webkit-border-before-color'); +allExtraProperties.add('webkit-border-before-style'); +allExtraProperties.add('webkit-border-before-width'); +allExtraProperties.add('webkit-border-end'); +allExtraProperties.add('webkit-border-end-color'); +allExtraProperties.add('webkit-border-end-style'); +allExtraProperties.add('webkit-border-end-width'); +allExtraProperties.add('webkit-border-fit'); +allExtraProperties.add('webkit-border-horizontal-spacing'); +allExtraProperties.add('webkit-border-image'); +allExtraProperties.add('webkit-border-radius'); +allExtraProperties.add('webkit-border-start'); +allExtraProperties.add('webkit-border-start-color'); +allExtraProperties.add('webkit-border-start-style'); +allExtraProperties.add('webkit-border-start-width'); +allExtraProperties.add('webkit-border-vertical-spacing'); +allExtraProperties.add('webkit-box-align'); +allExtraProperties.add('webkit-box-direction'); +allExtraProperties.add('webkit-box-flex'); +allExtraProperties.add('webkit-box-flex-group'); +allExtraProperties.add('webkit-box-lines'); +allExtraProperties.add('webkit-box-ordinal-group'); +allExtraProperties.add('webkit-box-orient'); +allExtraProperties.add('webkit-box-pack'); +allExtraProperties.add('webkit-box-reflect'); +allExtraProperties.add('webkit-box-shadow'); +allExtraProperties.add('webkit-color-correction'); +allExtraProperties.add('webkit-column-axis'); +allExtraProperties.add('webkit-column-break-after'); +allExtraProperties.add('webkit-column-break-before'); +allExtraProperties.add('webkit-column-break-inside'); +allExtraProperties.add('webkit-column-count'); +allExtraProperties.add('webkit-column-gap'); +allExtraProperties.add('webkit-column-rule'); +allExtraProperties.add('webkit-column-rule-color'); +allExtraProperties.add('webkit-column-rule-style'); +allExtraProperties.add('webkit-column-rule-width'); +allExtraProperties.add('webkit-columns'); +allExtraProperties.add('webkit-column-span'); +allExtraProperties.add('webkit-column-width'); +allExtraProperties.add('webkit-filter'); +allExtraProperties.add('webkit-flex-align'); +allExtraProperties.add('webkit-flex-direction'); +allExtraProperties.add('webkit-flex-flow'); +allExtraProperties.add('webkit-flex-item-align'); +allExtraProperties.add('webkit-flex-line-pack'); +allExtraProperties.add('webkit-flex-order'); +allExtraProperties.add('webkit-flex-pack'); +allExtraProperties.add('webkit-flex-wrap'); +allExtraProperties.add('webkit-flow-from'); +allExtraProperties.add('webkit-flow-into'); +allExtraProperties.add('webkit-font-feature-settings'); +allExtraProperties.add('webkit-font-kerning'); +allExtraProperties.add('webkit-font-size-delta'); +allExtraProperties.add('webkit-font-smoothing'); +allExtraProperties.add('webkit-font-variant-ligatures'); +allExtraProperties.add('webkit-highlight'); +allExtraProperties.add('webkit-hyphenate-character'); +allExtraProperties.add('webkit-hyphenate-limit-after'); +allExtraProperties.add('webkit-hyphenate-limit-before'); +allExtraProperties.add('webkit-hyphenate-limit-lines'); +allExtraProperties.add('webkit-hyphens'); +allExtraProperties.add('webkit-line-align'); +allExtraProperties.add('webkit-line-box-contain'); +allExtraProperties.add('webkit-line-break'); +allExtraProperties.add('webkit-line-clamp'); +allExtraProperties.add('webkit-line-grid'); +allExtraProperties.add('webkit-line-snap'); +allExtraProperties.add('webkit-locale'); +allExtraProperties.add('webkit-logical-height'); +allExtraProperties.add('webkit-logical-width'); +allExtraProperties.add('webkit-margin-after'); +allExtraProperties.add('webkit-margin-after-collapse'); +allExtraProperties.add('webkit-margin-before'); +allExtraProperties.add('webkit-margin-before-collapse'); +allExtraProperties.add('webkit-margin-bottom-collapse'); +allExtraProperties.add('webkit-margin-collapse'); +allExtraProperties.add('webkit-margin-end'); +allExtraProperties.add('webkit-margin-start'); +allExtraProperties.add('webkit-margin-top-collapse'); +allExtraProperties.add('webkit-marquee'); +allExtraProperties.add('webkit-marquee-direction'); +allExtraProperties.add('webkit-marquee-increment'); +allExtraProperties.add('webkit-marquee-repetition'); +allExtraProperties.add('webkit-marquee-speed'); +allExtraProperties.add('webkit-marquee-style'); +allExtraProperties.add('webkit-mask'); +allExtraProperties.add('webkit-mask-attachment'); +allExtraProperties.add('webkit-mask-box-image'); +allExtraProperties.add('webkit-mask-box-image-outset'); +allExtraProperties.add('webkit-mask-box-image-repeat'); +allExtraProperties.add('webkit-mask-box-image-slice'); +allExtraProperties.add('webkit-mask-box-image-source'); +allExtraProperties.add('webkit-mask-box-image-width'); +allExtraProperties.add('webkit-mask-clip'); +allExtraProperties.add('webkit-mask-composite'); +allExtraProperties.add('webkit-mask-image'); +allExtraProperties.add('webkit-mask-origin'); +allExtraProperties.add('webkit-mask-position'); +allExtraProperties.add('webkit-mask-position-x'); +allExtraProperties.add('webkit-mask-position-y'); +allExtraProperties.add('webkit-mask-repeat'); +allExtraProperties.add('webkit-mask-repeat-x'); +allExtraProperties.add('webkit-mask-repeat-y'); +allExtraProperties.add('webkit-mask-size'); +allExtraProperties.add('webkit-match-nearest-mail-blockquote-color'); +allExtraProperties.add('webkit-max-logical-height'); +allExtraProperties.add('webkit-max-logical-width'); +allExtraProperties.add('webkit-min-logical-height'); +allExtraProperties.add('webkit-min-logical-width'); +allExtraProperties.add('webkit-nbsp-mode'); +allExtraProperties.add('webkit-overflow-scrolling'); +allExtraProperties.add('webkit-padding-after'); +allExtraProperties.add('webkit-padding-before'); +allExtraProperties.add('webkit-padding-end'); +allExtraProperties.add('webkit-padding-start'); +allExtraProperties.add('webkit-perspective'); +allExtraProperties.add('webkit-perspective-origin'); +allExtraProperties.add('webkit-perspective-origin-x'); +allExtraProperties.add('webkit-perspective-origin-y'); +allExtraProperties.add('webkit-print-color-adjust'); +allExtraProperties.add('webkit-region-break-after'); +allExtraProperties.add('webkit-region-break-before'); +allExtraProperties.add('webkit-region-break-inside'); +allExtraProperties.add('webkit-region-overflow'); +allExtraProperties.add('webkit-rtl-ordering'); +allExtraProperties.add('webkit-svg-shadow'); +allExtraProperties.add('webkit-tap-highlight-color'); +allExtraProperties.add('webkit-text-combine'); +allExtraProperties.add('webkit-text-decorations-in-effect'); +allExtraProperties.add('webkit-text-emphasis'); +allExtraProperties.add('webkit-text-emphasis-color'); +allExtraProperties.add('webkit-text-emphasis-position'); +allExtraProperties.add('webkit-text-emphasis-style'); +allExtraProperties.add('webkit-text-fill-color'); +allExtraProperties.add('webkit-text-orientation'); +allExtraProperties.add('webkit-text-security'); +allExtraProperties.add('webkit-text-size-adjust'); +allExtraProperties.add('webkit-text-stroke'); +allExtraProperties.add('webkit-text-stroke-color'); +allExtraProperties.add('webkit-text-stroke-width'); +allExtraProperties.add('webkit-transform'); +allExtraProperties.add('webkit-transform-origin'); +allExtraProperties.add('webkit-transform-origin-x'); +allExtraProperties.add('webkit-transform-origin-y'); +allExtraProperties.add('webkit-transform-origin-z'); +allExtraProperties.add('webkit-transform-style'); +allExtraProperties.add('webkit-transition'); +allExtraProperties.add('webkit-transition-delay'); +allExtraProperties.add('webkit-transition-duration'); +allExtraProperties.add('webkit-transition-property'); +allExtraProperties.add('webkit-transition-timing-function'); +allExtraProperties.add('webkit-user-drag'); +allExtraProperties.add('webkit-user-modify'); +allExtraProperties.add('webkit-user-select'); +allExtraProperties.add('webkit-wrap'); +allExtraProperties.add('webkit-wrap-flow'); +allExtraProperties.add('webkit-wrap-margin'); +allExtraProperties.add('webkit-wrap-padding'); +allExtraProperties.add('webkit-wrap-shape-inside'); +allExtraProperties.add('webkit-wrap-shape-outside'); +allExtraProperties.add('webkit-wrap-through'); +allExtraProperties.add('webkit-writing-mode'); +allExtraProperties.add('zoom'); diff --git a/docs/js/node_modules/cssstyle/lib/allProperties.js b/docs/js/node_modules/cssstyle/lib/allProperties.js new file mode 100644 index 000000000..fc801df2d --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/allProperties.js @@ -0,0 +1,457 @@ +'use strict'; + +// autogenerated - 2/3/2019 + +/* + * + * https://www.w3.org/Style/CSS/all-properties.en.html + */ + +var allProperties = new Set(); +module.exports = allProperties; +allProperties.add('align-content'); +allProperties.add('align-items'); +allProperties.add('align-self'); +allProperties.add('alignment-baseline'); +allProperties.add('all'); +allProperties.add('animation'); +allProperties.add('animation-delay'); +allProperties.add('animation-direction'); +allProperties.add('animation-duration'); +allProperties.add('animation-fill-mode'); +allProperties.add('animation-iteration-count'); +allProperties.add('animation-name'); +allProperties.add('animation-play-state'); +allProperties.add('animation-timing-function'); +allProperties.add('appearance'); +allProperties.add('azimuth'); +allProperties.add('background'); +allProperties.add('background-attachment'); +allProperties.add('background-blend-mode'); +allProperties.add('background-clip'); +allProperties.add('background-color'); +allProperties.add('background-image'); +allProperties.add('background-origin'); +allProperties.add('background-position'); +allProperties.add('background-repeat'); +allProperties.add('background-size'); +allProperties.add('baseline-shift'); +allProperties.add('block-overflow'); +allProperties.add('block-size'); +allProperties.add('bookmark-label'); +allProperties.add('bookmark-level'); +allProperties.add('bookmark-state'); +allProperties.add('border'); +allProperties.add('border-block'); +allProperties.add('border-block-color'); +allProperties.add('border-block-end'); +allProperties.add('border-block-end-color'); +allProperties.add('border-block-end-style'); +allProperties.add('border-block-end-width'); +allProperties.add('border-block-start'); +allProperties.add('border-block-start-color'); +allProperties.add('border-block-start-style'); +allProperties.add('border-block-start-width'); +allProperties.add('border-block-style'); +allProperties.add('border-block-width'); +allProperties.add('border-bottom'); +allProperties.add('border-bottom-color'); +allProperties.add('border-bottom-left-radius'); +allProperties.add('border-bottom-right-radius'); +allProperties.add('border-bottom-style'); +allProperties.add('border-bottom-width'); +allProperties.add('border-boundary'); +allProperties.add('border-collapse'); +allProperties.add('border-color'); +allProperties.add('border-end-end-radius'); +allProperties.add('border-end-start-radius'); +allProperties.add('border-image'); +allProperties.add('border-image-outset'); +allProperties.add('border-image-repeat'); +allProperties.add('border-image-slice'); +allProperties.add('border-image-source'); +allProperties.add('border-image-width'); +allProperties.add('border-inline'); +allProperties.add('border-inline-color'); +allProperties.add('border-inline-end'); +allProperties.add('border-inline-end-color'); +allProperties.add('border-inline-end-style'); +allProperties.add('border-inline-end-width'); +allProperties.add('border-inline-start'); +allProperties.add('border-inline-start-color'); +allProperties.add('border-inline-start-style'); +allProperties.add('border-inline-start-width'); +allProperties.add('border-inline-style'); +allProperties.add('border-inline-width'); +allProperties.add('border-left'); +allProperties.add('border-left-color'); +allProperties.add('border-left-style'); +allProperties.add('border-left-width'); +allProperties.add('border-radius'); +allProperties.add('border-right'); +allProperties.add('border-right-color'); +allProperties.add('border-right-style'); +allProperties.add('border-right-width'); +allProperties.add('border-spacing'); +allProperties.add('border-start-end-radius'); +allProperties.add('border-start-start-radius'); +allProperties.add('border-style'); +allProperties.add('border-top'); +allProperties.add('border-top-color'); +allProperties.add('border-top-left-radius'); +allProperties.add('border-top-right-radius'); +allProperties.add('border-top-style'); +allProperties.add('border-top-width'); +allProperties.add('border-width'); +allProperties.add('bottom'); +allProperties.add('box-decoration-break'); +allProperties.add('box-shadow'); +allProperties.add('box-sizing'); +allProperties.add('box-snap'); +allProperties.add('break-after'); +allProperties.add('break-before'); +allProperties.add('break-inside'); +allProperties.add('caption-side'); +allProperties.add('caret'); +allProperties.add('caret-color'); +allProperties.add('caret-shape'); +allProperties.add('chains'); +allProperties.add('clear'); +allProperties.add('clip'); +allProperties.add('clip-path'); +allProperties.add('clip-rule'); +allProperties.add('color'); +allProperties.add('color-interpolation-filters'); +allProperties.add('column-count'); +allProperties.add('column-fill'); +allProperties.add('column-gap'); +allProperties.add('column-rule'); +allProperties.add('column-rule-color'); +allProperties.add('column-rule-style'); +allProperties.add('column-rule-width'); +allProperties.add('column-span'); +allProperties.add('column-width'); +allProperties.add('columns'); +allProperties.add('contain'); +allProperties.add('content'); +allProperties.add('continue'); +allProperties.add('counter-increment'); +allProperties.add('counter-reset'); +allProperties.add('counter-set'); +allProperties.add('cue'); +allProperties.add('cue-after'); +allProperties.add('cue-before'); +allProperties.add('cursor'); +allProperties.add('direction'); +allProperties.add('display'); +allProperties.add('dominant-baseline'); +allProperties.add('elevation'); +allProperties.add('empty-cells'); +allProperties.add('filter'); +allProperties.add('flex'); +allProperties.add('flex-basis'); +allProperties.add('flex-direction'); +allProperties.add('flex-flow'); +allProperties.add('flex-grow'); +allProperties.add('flex-shrink'); +allProperties.add('flex-wrap'); +allProperties.add('float'); +allProperties.add('flood-color'); +allProperties.add('flood-opacity'); +allProperties.add('flow'); +allProperties.add('flow-from'); +allProperties.add('flow-into'); +allProperties.add('font'); +allProperties.add('font-family'); +allProperties.add('font-feature-settings'); +allProperties.add('font-kerning'); +allProperties.add('font-language-override'); +allProperties.add('font-max-size'); +allProperties.add('font-min-size'); +allProperties.add('font-optical-sizing'); +allProperties.add('font-palette'); +allProperties.add('font-size'); +allProperties.add('font-size-adjust'); +allProperties.add('font-stretch'); +allProperties.add('font-style'); +allProperties.add('font-synthesis'); +allProperties.add('font-synthesis-small-caps'); +allProperties.add('font-synthesis-style'); +allProperties.add('font-synthesis-weight'); +allProperties.add('font-variant'); +allProperties.add('font-variant-alternates'); +allProperties.add('font-variant-caps'); +allProperties.add('font-variant-east-asian'); +allProperties.add('font-variant-emoji'); +allProperties.add('font-variant-ligatures'); +allProperties.add('font-variant-numeric'); +allProperties.add('font-variant-position'); +allProperties.add('font-variation-settings'); +allProperties.add('font-weight'); +allProperties.add('footnote-display'); +allProperties.add('footnote-policy'); +allProperties.add('gap'); +allProperties.add('glyph-orientation-vertical'); +allProperties.add('grid'); +allProperties.add('grid-area'); +allProperties.add('grid-auto-columns'); +allProperties.add('grid-auto-flow'); +allProperties.add('grid-auto-rows'); +allProperties.add('grid-column'); +allProperties.add('grid-column-end'); +allProperties.add('grid-column-start'); +allProperties.add('grid-row'); +allProperties.add('grid-row-end'); +allProperties.add('grid-row-start'); +allProperties.add('grid-template'); +allProperties.add('grid-template-areas'); +allProperties.add('grid-template-columns'); +allProperties.add('grid-template-rows'); +allProperties.add('hanging-punctuation'); +allProperties.add('height'); +allProperties.add('hyphenate-character'); +allProperties.add('hyphenate-limit-chars'); +allProperties.add('hyphenate-limit-last'); +allProperties.add('hyphenate-limit-lines'); +allProperties.add('hyphenate-limit-zone'); +allProperties.add('hyphens'); +allProperties.add('image-orientation'); +allProperties.add('image-resolution'); +allProperties.add('initial-letters'); +allProperties.add('initial-letters-align'); +allProperties.add('initial-letters-wrap'); +allProperties.add('inline-size'); +allProperties.add('inline-sizing'); +allProperties.add('inset'); +allProperties.add('inset-block'); +allProperties.add('inset-block-end'); +allProperties.add('inset-block-start'); +allProperties.add('inset-inline'); +allProperties.add('inset-inline-end'); +allProperties.add('inset-inline-start'); +allProperties.add('isolation'); +allProperties.add('justify-content'); +allProperties.add('justify-items'); +allProperties.add('justify-self'); +allProperties.add('left'); +allProperties.add('letter-spacing'); +allProperties.add('lighting-color'); +allProperties.add('line-break'); +allProperties.add('line-clamp'); +allProperties.add('line-grid'); +allProperties.add('line-height'); +allProperties.add('line-padding'); +allProperties.add('line-snap'); +allProperties.add('list-style'); +allProperties.add('list-style-image'); +allProperties.add('list-style-position'); +allProperties.add('list-style-type'); +allProperties.add('margin'); +allProperties.add('margin-block'); +allProperties.add('margin-block-end'); +allProperties.add('margin-block-start'); +allProperties.add('margin-bottom'); +allProperties.add('margin-inline'); +allProperties.add('margin-inline-end'); +allProperties.add('margin-inline-start'); +allProperties.add('margin-left'); +allProperties.add('margin-right'); +allProperties.add('margin-top'); +allProperties.add('margin-trim'); +allProperties.add('marker-side'); +allProperties.add('mask'); +allProperties.add('mask-border'); +allProperties.add('mask-border-mode'); +allProperties.add('mask-border-outset'); +allProperties.add('mask-border-repeat'); +allProperties.add('mask-border-slice'); +allProperties.add('mask-border-source'); +allProperties.add('mask-border-width'); +allProperties.add('mask-clip'); +allProperties.add('mask-composite'); +allProperties.add('mask-image'); +allProperties.add('mask-mode'); +allProperties.add('mask-origin'); +allProperties.add('mask-position'); +allProperties.add('mask-repeat'); +allProperties.add('mask-size'); +allProperties.add('mask-type'); +allProperties.add('max-block-size'); +allProperties.add('max-height'); +allProperties.add('max-inline-size'); +allProperties.add('max-lines'); +allProperties.add('max-width'); +allProperties.add('min-block-size'); +allProperties.add('min-height'); +allProperties.add('min-inline-size'); +allProperties.add('min-width'); +allProperties.add('mix-blend-mode'); +allProperties.add('nav-down'); +allProperties.add('nav-left'); +allProperties.add('nav-right'); +allProperties.add('nav-up'); +allProperties.add('object-fit'); +allProperties.add('object-position'); +allProperties.add('offset'); +allProperties.add('offset-after'); +allProperties.add('offset-anchor'); +allProperties.add('offset-before'); +allProperties.add('offset-distance'); +allProperties.add('offset-end'); +allProperties.add('offset-path'); +allProperties.add('offset-position'); +allProperties.add('offset-rotate'); +allProperties.add('offset-start'); +allProperties.add('opacity'); +allProperties.add('order'); +allProperties.add('orphans'); +allProperties.add('outline'); +allProperties.add('outline-color'); +allProperties.add('outline-offset'); +allProperties.add('outline-style'); +allProperties.add('outline-width'); +allProperties.add('overflow'); +allProperties.add('overflow-block'); +allProperties.add('overflow-inline'); +allProperties.add('overflow-wrap'); +allProperties.add('overflow-x'); +allProperties.add('overflow-y'); +allProperties.add('padding'); +allProperties.add('padding-block'); +allProperties.add('padding-block-end'); +allProperties.add('padding-block-start'); +allProperties.add('padding-bottom'); +allProperties.add('padding-inline'); +allProperties.add('padding-inline-end'); +allProperties.add('padding-inline-start'); +allProperties.add('padding-left'); +allProperties.add('padding-right'); +allProperties.add('padding-top'); +allProperties.add('page'); +allProperties.add('page-break-after'); +allProperties.add('page-break-before'); +allProperties.add('page-break-inside'); +allProperties.add('pause'); +allProperties.add('pause-after'); +allProperties.add('pause-before'); +allProperties.add('pitch'); +allProperties.add('pitch-range'); +allProperties.add('place-content'); +allProperties.add('place-items'); +allProperties.add('place-self'); +allProperties.add('play-during'); +allProperties.add('position'); +allProperties.add('presentation-level'); +allProperties.add('quotes'); +allProperties.add('region-fragment'); +allProperties.add('resize'); +allProperties.add('rest'); +allProperties.add('rest-after'); +allProperties.add('rest-before'); +allProperties.add('richness'); +allProperties.add('right'); +allProperties.add('row-gap'); +allProperties.add('ruby-align'); +allProperties.add('ruby-merge'); +allProperties.add('ruby-position'); +allProperties.add('running'); +allProperties.add('scroll-behavior'); +allProperties.add('scroll-margin'); +allProperties.add('scroll-margin-block'); +allProperties.add('scroll-margin-block-end'); +allProperties.add('scroll-margin-block-start'); +allProperties.add('scroll-margin-bottom'); +allProperties.add('scroll-margin-inline'); +allProperties.add('scroll-margin-inline-end'); +allProperties.add('scroll-margin-inline-start'); +allProperties.add('scroll-margin-left'); +allProperties.add('scroll-margin-right'); +allProperties.add('scroll-margin-top'); +allProperties.add('scroll-padding'); +allProperties.add('scroll-padding-block'); +allProperties.add('scroll-padding-block-end'); +allProperties.add('scroll-padding-block-start'); +allProperties.add('scroll-padding-bottom'); +allProperties.add('scroll-padding-inline'); +allProperties.add('scroll-padding-inline-end'); +allProperties.add('scroll-padding-inline-start'); +allProperties.add('scroll-padding-left'); +allProperties.add('scroll-padding-right'); +allProperties.add('scroll-padding-top'); +allProperties.add('scroll-snap-align'); +allProperties.add('scroll-snap-stop'); +allProperties.add('scroll-snap-type'); +allProperties.add('shape-image-threshold'); +allProperties.add('shape-inside'); +allProperties.add('shape-margin'); +allProperties.add('shape-outside'); +allProperties.add('speak'); +allProperties.add('speak-as'); +allProperties.add('speak-header'); +allProperties.add('speak-numeral'); +allProperties.add('speak-punctuation'); +allProperties.add('speech-rate'); +allProperties.add('stress'); +allProperties.add('string-set'); +allProperties.add('tab-size'); +allProperties.add('table-layout'); +allProperties.add('text-align'); +allProperties.add('text-align-all'); +allProperties.add('text-align-last'); +allProperties.add('text-combine-upright'); +allProperties.add('text-decoration'); +allProperties.add('text-decoration-color'); +allProperties.add('text-decoration-line'); +allProperties.add('text-decoration-style'); +allProperties.add('text-emphasis'); +allProperties.add('text-emphasis-color'); +allProperties.add('text-emphasis-position'); +allProperties.add('text-emphasis-style'); +allProperties.add('text-group-align'); +allProperties.add('text-indent'); +allProperties.add('text-justify'); +allProperties.add('text-orientation'); +allProperties.add('text-overflow'); +allProperties.add('text-shadow'); +allProperties.add('text-space-collapse'); +allProperties.add('text-space-trim'); +allProperties.add('text-spacing'); +allProperties.add('text-transform'); +allProperties.add('text-underline-position'); +allProperties.add('text-wrap'); +allProperties.add('top'); +allProperties.add('transform'); +allProperties.add('transform-box'); +allProperties.add('transform-origin'); +allProperties.add('transition'); +allProperties.add('transition-delay'); +allProperties.add('transition-duration'); +allProperties.add('transition-property'); +allProperties.add('transition-timing-function'); +allProperties.add('unicode-bidi'); +allProperties.add('user-select'); +allProperties.add('vertical-align'); +allProperties.add('visibility'); +allProperties.add('voice-balance'); +allProperties.add('voice-duration'); +allProperties.add('voice-family'); +allProperties.add('voice-pitch'); +allProperties.add('voice-range'); +allProperties.add('voice-rate'); +allProperties.add('voice-stress'); +allProperties.add('voice-volume'); +allProperties.add('volume'); +allProperties.add('white-space'); +allProperties.add('widows'); +allProperties.add('width'); +allProperties.add('will-change'); +allProperties.add('word-break'); +allProperties.add('word-spacing'); +allProperties.add('word-wrap'); +allProperties.add('wrap-after'); +allProperties.add('wrap-before'); +allProperties.add('wrap-flow'); +allProperties.add('wrap-inside'); +allProperties.add('wrap-through'); +allProperties.add('writing-mode'); +allProperties.add('z-index'); diff --git a/docs/js/node_modules/cssstyle/lib/constants.js b/docs/js/node_modules/cssstyle/lib/constants.js new file mode 100644 index 000000000..1b588695e --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/constants.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports.POSITION_AT_SHORTHAND = { + first: 0, + second: 1, +}; diff --git a/docs/js/node_modules/cssstyle/lib/implementedProperties.js b/docs/js/node_modules/cssstyle/lib/implementedProperties.js new file mode 100644 index 000000000..fd8cbca0a --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/implementedProperties.js @@ -0,0 +1,90 @@ +'use strict'; + +// autogenerated - 7/15/2019 + +/* + * + * https://www.w3.org/Style/CSS/all-properties.en.html + */ + +var implementedProperties = new Set(); +implementedProperties.add("azimuth"); +implementedProperties.add("background"); +implementedProperties.add("background-attachment"); +implementedProperties.add("background-color"); +implementedProperties.add("background-image"); +implementedProperties.add("background-position"); +implementedProperties.add("background-repeat"); +implementedProperties.add("border"); +implementedProperties.add("border-bottom"); +implementedProperties.add("border-bottom-color"); +implementedProperties.add("border-bottom-style"); +implementedProperties.add("border-bottom-width"); +implementedProperties.add("border-collapse"); +implementedProperties.add("border-color"); +implementedProperties.add("border-left"); +implementedProperties.add("border-left-color"); +implementedProperties.add("border-left-style"); +implementedProperties.add("border-left-width"); +implementedProperties.add("border-right"); +implementedProperties.add("border-right-color"); +implementedProperties.add("border-right-style"); +implementedProperties.add("border-right-width"); +implementedProperties.add("border-spacing"); +implementedProperties.add("border-style"); +implementedProperties.add("border-top"); +implementedProperties.add("border-top-color"); +implementedProperties.add("border-top-style"); +implementedProperties.add("border-top-width"); +implementedProperties.add("border-width"); +implementedProperties.add("bottom"); +implementedProperties.add("clear"); +implementedProperties.add("clip"); +implementedProperties.add("color"); +implementedProperties.add("css-float"); +implementedProperties.add("flex"); +implementedProperties.add("flex-basis"); +implementedProperties.add("flex-grow"); +implementedProperties.add("flex-shrink"); +implementedProperties.add("float"); +implementedProperties.add("flood-color"); +implementedProperties.add("font"); +implementedProperties.add("font-family"); +implementedProperties.add("font-size"); +implementedProperties.add("font-style"); +implementedProperties.add("font-variant"); +implementedProperties.add("font-weight"); +implementedProperties.add("height"); +implementedProperties.add("left"); +implementedProperties.add("lighting-color"); +implementedProperties.add("line-height"); +implementedProperties.add("margin"); +implementedProperties.add("margin-bottom"); +implementedProperties.add("margin-left"); +implementedProperties.add("margin-right"); +implementedProperties.add("margin-top"); +implementedProperties.add("opacity"); +implementedProperties.add("outline-color"); +implementedProperties.add("padding"); +implementedProperties.add("padding-bottom"); +implementedProperties.add("padding-left"); +implementedProperties.add("padding-right"); +implementedProperties.add("padding-top"); +implementedProperties.add("right"); +implementedProperties.add("stop-color"); +implementedProperties.add("text-line-through-color"); +implementedProperties.add("text-overline-color"); +implementedProperties.add("text-underline-color"); +implementedProperties.add("top"); +implementedProperties.add("webkit-border-after-color"); +implementedProperties.add("webkit-border-before-color"); +implementedProperties.add("webkit-border-end-color"); +implementedProperties.add("webkit-border-start-color"); +implementedProperties.add("webkit-column-rule-color"); +implementedProperties.add("webkit-match-nearest-mail-blockquote-color"); +implementedProperties.add("webkit-tap-highlight-color"); +implementedProperties.add("webkit-text-emphasis-color"); +implementedProperties.add("webkit-text-fill-color"); +implementedProperties.add("webkit-text-stroke-color"); +implementedProperties.add("width"); +module.exports = implementedProperties; diff --git a/docs/js/node_modules/cssstyle/lib/named_colors.json b/docs/js/node_modules/cssstyle/lib/named_colors.json new file mode 100644 index 000000000..63667a5a5 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/named_colors.json @@ -0,0 +1,152 @@ +[ + "aliceblue", + "antiquewhite", + "aqua", + "aquamarine", + "azure", + "beige", + "bisque", + "black", + "blanchedalmond", + "blue", + "blueviolet", + "brown", + "burlywood", + "cadetblue", + "chartreuse", + "chocolate", + "coral", + "cornflowerblue", + "cornsilk", + "crimson", + "cyan", + "darkblue", + "darkcyan", + "darkgoldenrod", + "darkgray", + "darkgreen", + "darkgrey", + "darkkhaki", + "darkmagenta", + "darkolivegreen", + "darkorange", + "darkorchid", + "darkred", + "darksalmon", + "darkseagreen", + "darkslateblue", + "darkslategray", + "darkslategrey", + "darkturquoise", + "darkviolet", + "deeppink", + "deepskyblue", + "dimgray", + "dimgrey", + "dodgerblue", + "firebrick", + "floralwhite", + "forestgreen", + "fuchsia", + "gainsboro", + "ghostwhite", + "gold", + "goldenrod", + "gray", + "green", + "greenyellow", + "grey", + "honeydew", + "hotpink", + "indianred", + "indigo", + "ivory", + "khaki", + "lavender", + "lavenderblush", + "lawngreen", + "lemonchiffon", + "lightblue", + "lightcoral", + "lightcyan", + "lightgoldenrodyellow", + "lightgray", + "lightgreen", + "lightgrey", + "lightpink", + "lightsalmon", + "lightseagreen", + "lightskyblue", + "lightslategray", + "lightslategrey", + "lightsteelblue", + "lightyellow", + "lime", + "limegreen", + "linen", + "magenta", + "maroon", + "mediumaquamarine", + "mediumblue", + "mediumorchid", + "mediumpurple", + "mediumseagreen", + "mediumslateblue", + "mediumspringgreen", + "mediumturquoise", + "mediumvioletred", + "midnightblue", + "mintcream", + "mistyrose", + "moccasin", + "navajowhite", + "navy", + "oldlace", + "olive", + "olivedrab", + "orange", + "orangered", + "orchid", + "palegoldenrod", + "palegreen", + "paleturquoise", + "palevioletred", + "papayawhip", + "peachpuff", + "peru", + "pink", + "plum", + "powderblue", + "purple", + "rebeccapurple", + "red", + "rosybrown", + "royalblue", + "saddlebrown", + "salmon", + "sandybrown", + "seagreen", + "seashell", + "sienna", + "silver", + "skyblue", + "slateblue", + "slategray", + "slategrey", + "snow", + "springgreen", + "steelblue", + "tan", + "teal", + "thistle", + "tomato", + "turquoise", + "violet", + "wheat", + "white", + "whitesmoke", + "yellow", + "yellowgreen", + "transparent", + "currentcolor" +] diff --git a/docs/js/node_modules/cssstyle/lib/parsers.js b/docs/js/node_modules/cssstyle/lib/parsers.js new file mode 100644 index 000000000..24021ba0f --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/parsers.js @@ -0,0 +1,697 @@ +/********************************************************************* + * These are commonly used parsers for CSS Values they take a string * + * to parse and return a string after it's been converted, if needed * + ********************************************************************/ +'use strict'; + +const namedColors = require('./named_colors.json'); + +exports.TYPES = { + INTEGER: 1, + NUMBER: 2, + LENGTH: 3, + PERCENT: 4, + URL: 5, + COLOR: 6, + STRING: 7, + ANGLE: 8, + KEYWORD: 9, + NULL_OR_EMPTY_STR: 10, +}; + +// rough regular expressions +var integerRegEx = /^[-+]?[0-9]+$/; +var numberRegEx = /^[-+]?[0-9]*\.[0-9]+$/; +var lengthRegEx = /^(0|[-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px|ex|rem|vh|vw))$/; +var percentRegEx = /^[-+]?[0-9]*\.?[0-9]+%$/; +var urlRegEx = /^url\(\s*([^)]*)\s*\)$/; +var stringRegEx = /^("[^"]*"|'[^']*')$/; +var colorRegEx1 = /^#[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])?$/; +var colorRegEx2 = /^rgb\(([^)]*)\)$/; +var colorRegEx3 = /^rgba\(([^)]*)\)$/; +var colorRegEx4 = /^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/; +var angleRegEx = /^([-+]?[0-9]*\.?[0-9]+)(deg|grad|rad)$/; + +// This will return one of the above types based on the passed in string +exports.valueType = function valueType(val) { + if (val === '' || val === null) { + return exports.TYPES.NULL_OR_EMPTY_STR; + } + if (typeof val === 'number') { + val = val.toString(); + } + + if (typeof val !== 'string') { + return undefined; + } + + if (integerRegEx.test(val)) { + return exports.TYPES.INTEGER; + } + if (numberRegEx.test(val)) { + return exports.TYPES.NUMBER; + } + if (lengthRegEx.test(val)) { + return exports.TYPES.LENGTH; + } + if (percentRegEx.test(val)) { + return exports.TYPES.PERCENT; + } + if (urlRegEx.test(val)) { + return exports.TYPES.URL; + } + if (stringRegEx.test(val)) { + return exports.TYPES.STRING; + } + if (angleRegEx.test(val)) { + return exports.TYPES.ANGLE; + } + if (colorRegEx1.test(val)) { + return exports.TYPES.COLOR; + } + var res = colorRegEx2.exec(val); + var parts; + if (res !== null) { + parts = res[1].split(/\s*,\s*/); + if (parts.length !== 3) { + return undefined; + } + if ( + parts.every(percentRegEx.test.bind(percentRegEx)) || + parts.every(integerRegEx.test.bind(integerRegEx)) + ) { + return exports.TYPES.COLOR; + } + return undefined; + } + res = colorRegEx3.exec(val); + if (res !== null) { + parts = res[1].split(/\s*,\s*/); + if (parts.length !== 4) { + return undefined; + } + if ( + parts.slice(0, 3).every(percentRegEx.test.bind(percentRegEx)) || + parts.every(integerRegEx.test.bind(integerRegEx)) + ) { + if (numberRegEx.test(parts[3])) { + return exports.TYPES.COLOR; + } + } + return undefined; + } + + if (colorRegEx4.test(val)) { + return exports.TYPES.COLOR; + } + + // could still be a color, one of the standard keyword colors + val = val.toLowerCase(); + + if (namedColors.includes(val)) { + return exports.TYPES.COLOR; + } + + switch (val) { + // the following are deprecated in CSS3 + case 'activeborder': + case 'activecaption': + case 'appworkspace': + case 'background': + case 'buttonface': + case 'buttonhighlight': + case 'buttonshadow': + case 'buttontext': + case 'captiontext': + case 'graytext': + case 'highlight': + case 'highlighttext': + case 'inactiveborder': + case 'inactivecaption': + case 'inactivecaptiontext': + case 'infobackground': + case 'infotext': + case 'menu': + case 'menutext': + case 'scrollbar': + case 'threeddarkshadow': + case 'threedface': + case 'threedhighlight': + case 'threedlightshadow': + case 'threedshadow': + case 'window': + case 'windowframe': + case 'windowtext': + return exports.TYPES.COLOR; + default: + return exports.TYPES.KEYWORD; + } +}; + +exports.parseInteger = function parseInteger(val) { + var type = exports.valueType(val); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return val; + } + if (type !== exports.TYPES.INTEGER) { + return undefined; + } + return String(parseInt(val, 10)); +}; + +exports.parseNumber = function parseNumber(val) { + var type = exports.valueType(val); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return val; + } + if (type !== exports.TYPES.NUMBER && type !== exports.TYPES.INTEGER) { + return undefined; + } + return String(parseFloat(val)); +}; + +exports.parseLength = function parseLength(val) { + if (val === 0 || val === '0') { + return '0px'; + } + var type = exports.valueType(val); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return val; + } + if (type !== exports.TYPES.LENGTH) { + return undefined; + } + return val; +}; + +exports.parsePercent = function parsePercent(val) { + if (val === 0 || val === '0') { + return '0%'; + } + var type = exports.valueType(val); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return val; + } + if (type !== exports.TYPES.PERCENT) { + return undefined; + } + return val; +}; + +// either a length or a percent +exports.parseMeasurement = function parseMeasurement(val) { + var length = exports.parseLength(val); + if (length !== undefined) { + return length; + } + return exports.parsePercent(val); +}; + +exports.parseUrl = function parseUrl(val) { + var type = exports.valueType(val); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return val; + } + var res = urlRegEx.exec(val); + // does it match the regex? + if (!res) { + return undefined; + } + var str = res[1]; + // if it starts with single or double quotes, does it end with the same? + if ((str[0] === '"' || str[0] === "'") && str[0] !== str[str.length - 1]) { + return undefined; + } + if (str[0] === '"' || str[0] === "'") { + str = str.substr(1, str.length - 2); + } + + var i; + for (i = 0; i < str.length; i++) { + switch (str[i]) { + case '(': + case ')': + case ' ': + case '\t': + case '\n': + case "'": + case '"': + return undefined; + case '\\': + i++; + break; + } + } + + return 'url(' + str + ')'; +}; + +exports.parseString = function parseString(val) { + var type = exports.valueType(val); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return val; + } + if (type !== exports.TYPES.STRING) { + return undefined; + } + var i; + for (i = 1; i < val.length - 1; i++) { + switch (val[i]) { + case val[0]: + return undefined; + case '\\': + i++; + while (i < val.length - 1 && /[0-9A-Fa-f]/.test(val[i])) { + i++; + } + break; + } + } + if (i >= val.length) { + return undefined; + } + return val; +}; + +exports.parseColor = function parseColor(val) { + var type = exports.valueType(val); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return val; + } + var red, + green, + blue, + hue, + saturation, + lightness, + alpha = 1; + var parts; + var res = colorRegEx1.exec(val); + // is it #aaa or #ababab + if (res) { + var hex = val.substr(1); + if (hex.length === 3) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; + } + red = parseInt(hex.substr(0, 2), 16); + green = parseInt(hex.substr(2, 2), 16); + blue = parseInt(hex.substr(4, 2), 16); + return 'rgb(' + red + ', ' + green + ', ' + blue + ')'; + } + + res = colorRegEx2.exec(val); + if (res) { + parts = res[1].split(/\s*,\s*/); + if (parts.length !== 3) { + return undefined; + } + if (parts.every(percentRegEx.test.bind(percentRegEx))) { + red = Math.floor((parseFloat(parts[0].slice(0, -1)) * 255) / 100); + green = Math.floor((parseFloat(parts[1].slice(0, -1)) * 255) / 100); + blue = Math.floor((parseFloat(parts[2].slice(0, -1)) * 255) / 100); + } else if (parts.every(integerRegEx.test.bind(integerRegEx))) { + red = parseInt(parts[0], 10); + green = parseInt(parts[1], 10); + blue = parseInt(parts[2], 10); + } else { + return undefined; + } + red = Math.min(255, Math.max(0, red)); + green = Math.min(255, Math.max(0, green)); + blue = Math.min(255, Math.max(0, blue)); + return 'rgb(' + red + ', ' + green + ', ' + blue + ')'; + } + + res = colorRegEx3.exec(val); + if (res) { + parts = res[1].split(/\s*,\s*/); + if (parts.length !== 4) { + return undefined; + } + if (parts.slice(0, 3).every(percentRegEx.test.bind(percentRegEx))) { + red = Math.floor((parseFloat(parts[0].slice(0, -1)) * 255) / 100); + green = Math.floor((parseFloat(parts[1].slice(0, -1)) * 255) / 100); + blue = Math.floor((parseFloat(parts[2].slice(0, -1)) * 255) / 100); + alpha = parseFloat(parts[3]); + } else if (parts.slice(0, 3).every(integerRegEx.test.bind(integerRegEx))) { + red = parseInt(parts[0], 10); + green = parseInt(parts[1], 10); + blue = parseInt(parts[2], 10); + alpha = parseFloat(parts[3]); + } else { + return undefined; + } + if (isNaN(alpha)) { + alpha = 1; + } + red = Math.min(255, Math.max(0, red)); + green = Math.min(255, Math.max(0, green)); + blue = Math.min(255, Math.max(0, blue)); + alpha = Math.min(1, Math.max(0, alpha)); + if (alpha === 1) { + return 'rgb(' + red + ', ' + green + ', ' + blue + ')'; + } + return 'rgba(' + red + ', ' + green + ', ' + blue + ', ' + alpha + ')'; + } + + res = colorRegEx4.exec(val); + if (res) { + const [, _hue, _saturation, _lightness, _alphaString = ''] = res; + const _alpha = parseFloat(_alphaString.replace(',', '').trim()); + if (!_hue || !_saturation || !_lightness) { + return undefined; + } + hue = parseFloat(_hue); + saturation = parseInt(_saturation, 10); + lightness = parseInt(_lightness, 10); + if (_alpha && numberRegEx.test(_alpha)) { + alpha = parseFloat(_alpha); + } + if (!_alphaString || alpha === 1) { + return 'hsl(' + hue + ', ' + saturation + '%, ' + lightness + '%)'; + } + return 'hsla(' + hue + ', ' + saturation + '%, ' + lightness + '%, ' + alpha + ')'; + } + + if (type === exports.TYPES.COLOR) { + return val; + } + return undefined; +}; + +exports.parseAngle = function parseAngle(val) { + var type = exports.valueType(val); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return val; + } + if (type !== exports.TYPES.ANGLE) { + return undefined; + } + var res = angleRegEx.exec(val); + var flt = parseFloat(res[1]); + if (res[2] === 'rad') { + flt *= 180 / Math.PI; + } else if (res[2] === 'grad') { + flt *= 360 / 400; + } + + while (flt < 0) { + flt += 360; + } + while (flt > 360) { + flt -= 360; + } + return flt + 'deg'; +}; + +exports.parseKeyword = function parseKeyword(val, valid_keywords) { + var type = exports.valueType(val); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + return val; + } + if (type !== exports.TYPES.KEYWORD) { + return undefined; + } + val = val.toString().toLowerCase(); + var i; + for (i = 0; i < valid_keywords.length; i++) { + if (valid_keywords[i].toLowerCase() === val) { + return valid_keywords[i]; + } + } + return undefined; +}; + +// utility to translate from border-width to borderWidth +var dashedToCamelCase = function(dashed) { + var i; + var camel = ''; + var nextCap = false; + for (i = 0; i < dashed.length; i++) { + if (dashed[i] !== '-') { + camel += nextCap ? dashed[i].toUpperCase() : dashed[i]; + nextCap = false; + } else { + nextCap = true; + } + } + return camel; +}; +exports.dashedToCamelCase = dashedToCamelCase; + +var is_space = /\s/; +var opening_deliminators = ['"', "'", '(']; +var closing_deliminators = ['"', "'", ')']; +// this splits on whitespace, but keeps quoted and parened parts together +var getParts = function(str) { + var deliminator_stack = []; + var length = str.length; + var i; + var parts = []; + var current_part = ''; + var opening_index; + var closing_index; + for (i = 0; i < length; i++) { + opening_index = opening_deliminators.indexOf(str[i]); + closing_index = closing_deliminators.indexOf(str[i]); + if (is_space.test(str[i])) { + if (deliminator_stack.length === 0) { + if (current_part !== '') { + parts.push(current_part); + } + current_part = ''; + } else { + current_part += str[i]; + } + } else { + if (str[i] === '\\') { + i++; + current_part += str[i]; + } else { + current_part += str[i]; + if ( + closing_index !== -1 && + closing_index === deliminator_stack[deliminator_stack.length - 1] + ) { + deliminator_stack.pop(); + } else if (opening_index !== -1) { + deliminator_stack.push(opening_index); + } + } + } + } + if (current_part !== '') { + parts.push(current_part); + } + return parts; +}; + +/* + * this either returns undefined meaning that it isn't valid + * or returns an object where the keys are dashed short + * hand properties and the values are the values to set + * on them + */ +exports.shorthandParser = function parse(v, shorthand_for) { + var obj = {}; + var type = exports.valueType(v); + if (type === exports.TYPES.NULL_OR_EMPTY_STR) { + Object.keys(shorthand_for).forEach(function(property) { + obj[property] = ''; + }); + return obj; + } + + if (typeof v === 'number') { + v = v.toString(); + } + + if (typeof v !== 'string') { + return undefined; + } + + if (v.toLowerCase() === 'inherit') { + return {}; + } + var parts = getParts(v); + var valid = true; + parts.forEach(function(part, i) { + var part_valid = false; + Object.keys(shorthand_for).forEach(function(property) { + if (shorthand_for[property].isValid(part, i)) { + part_valid = true; + obj[property] = part; + } + }); + valid = valid && part_valid; + }); + if (!valid) { + return undefined; + } + return obj; +}; + +exports.shorthandSetter = function(property, shorthand_for) { + return function(v) { + var obj = exports.shorthandParser(v, shorthand_for); + if (obj === undefined) { + return; + } + //console.log('shorthandSetter for:', property, 'obj:', obj); + Object.keys(obj).forEach(function(subprop) { + // in case subprop is an implicit property, this will clear + // *its* subpropertiesX + var camel = dashedToCamelCase(subprop); + this[camel] = obj[subprop]; + // in case it gets translated into something else (0 -> 0px) + obj[subprop] = this[camel]; + this.removeProperty(subprop); + // don't add in empty properties + if (obj[subprop] !== '') { + this._values[subprop] = obj[subprop]; + } + }, this); + Object.keys(shorthand_for).forEach(function(subprop) { + if (!obj.hasOwnProperty(subprop)) { + this.removeProperty(subprop); + delete this._values[subprop]; + } + }, this); + // in case the value is something like 'none' that removes all values, + // check that the generated one is not empty, first remove the property + // if it already exists, then call the shorthandGetter, if it's an empty + // string, don't set the property + this.removeProperty(property); + var calculated = exports.shorthandGetter(property, shorthand_for).call(this); + if (calculated !== '') { + this._setProperty(property, calculated); + } + }; +}; + +exports.shorthandGetter = function(property, shorthand_for) { + return function() { + if (this._values[property] !== undefined) { + return this.getPropertyValue(property); + } + return Object.keys(shorthand_for) + .map(function(subprop) { + return this.getPropertyValue(subprop); + }, this) + .filter(function(value) { + return value !== ''; + }) + .join(' '); + }; +}; + +// isValid(){1,4} | inherit +// if one, it applies to all +// if two, the first applies to the top and bottom, and the second to left and right +// if three, the first applies to the top, the second to left and right, the third bottom +// if four, top, right, bottom, left +exports.implicitSetter = function(property_before, property_after, isValid, parser) { + property_after = property_after || ''; + if (property_after !== '') { + property_after = '-' + property_after; + } + var part_names = ['top', 'right', 'bottom', 'left']; + + return function(v) { + if (typeof v === 'number') { + v = v.toString(); + } + if (typeof v !== 'string') { + return undefined; + } + var parts; + if (v.toLowerCase() === 'inherit' || v === '') { + parts = [v]; + } else { + parts = getParts(v); + } + if (parts.length < 1 || parts.length > 4) { + return undefined; + } + + if (!parts.every(isValid)) { + return undefined; + } + + parts = parts.map(function(part) { + return parser(part); + }); + this._setProperty(property_before + property_after, parts.join(' ')); + if (parts.length === 1) { + parts[1] = parts[0]; + } + if (parts.length === 2) { + parts[2] = parts[0]; + } + if (parts.length === 3) { + parts[3] = parts[1]; + } + + for (var i = 0; i < 4; i++) { + var property = property_before + '-' + part_names[i] + property_after; + this.removeProperty(property); + if (parts[i] !== '') { + this._values[property] = parts[i]; + } + } + return v; + }; +}; + +// +// Companion to implicitSetter, but for the individual parts. +// This sets the individual value, and checks to see if all four +// sub-parts are set. If so, it sets the shorthand version and removes +// the individual parts from the cssText. +// +exports.subImplicitSetter = function(prefix, part, isValid, parser) { + var property = prefix + '-' + part; + var subparts = [prefix + '-top', prefix + '-right', prefix + '-bottom', prefix + '-left']; + + return function(v) { + if (typeof v === 'number') { + v = v.toString(); + } + if (typeof v !== 'string') { + return undefined; + } + if (!isValid(v)) { + return undefined; + } + v = parser(v); + this._setProperty(property, v); + var parts = []; + for (var i = 0; i < 4; i++) { + if (this._values[subparts[i]] == null || this._values[subparts[i]] === '') { + break; + } + parts.push(this._values[subparts[i]]); + } + if (parts.length === 4) { + for (i = 0; i < 4; i++) { + this.removeProperty(subparts[i]); + this._values[subparts[i]] = parts[i]; + } + this._setProperty(prefix, parts.join(' ')); + } + return v; + }; +}; + +var camel_to_dashed = /[A-Z]/g; +var first_segment = /^\([^-]\)-/; +var vendor_prefixes = ['o', 'moz', 'ms', 'webkit']; +exports.camelToDashed = function(camel_case) { + var match; + var dashed = camel_case.replace(camel_to_dashed, '-$&').toLowerCase(); + match = dashed.match(first_segment); + if (match && vendor_prefixes.indexOf(match[1]) !== -1) { + dashed = '-' + dashed; + } + return dashed; +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties.js b/docs/js/node_modules/cssstyle/lib/properties.js new file mode 100644 index 000000000..2b4939c83 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties.js @@ -0,0 +1,1826 @@ +'use strict'; + +// autogenerated - 7/15/2019 + +/* + * + * https://www.w3.org/Style/CSS/all-properties.en.html + */ + +var external_dependency_parsers_0 = require("./parsers.js"); + +var external_dependency_constants_1 = require("./constants.js"); + +var azimuth_export_definition; +azimuth_export_definition = { + set: function (v) { + var valueType = external_dependency_parsers_0.valueType(v); + + if (valueType === external_dependency_parsers_0.TYPES.ANGLE) { + return this._setProperty('azimuth', external_dependency_parsers_0.parseAngle(v)); + } + + if (valueType === external_dependency_parsers_0.TYPES.KEYWORD) { + var keywords = v.toLowerCase().trim().split(/\s+/); + var hasBehind = false; + + if (keywords.length > 2) { + return; + } + + var behindIndex = keywords.indexOf('behind'); + hasBehind = behindIndex !== -1; + + if (keywords.length === 2) { + if (!hasBehind) { + return; + } + + keywords.splice(behindIndex, 1); + } + + if (keywords[0] === 'leftwards' || keywords[0] === 'rightwards') { + if (hasBehind) { + return; + } + + return this._setProperty('azimuth', keywords[0]); + } + + if (keywords[0] === 'behind') { + return this._setProperty('azimuth', '180deg'); + } + + switch (keywords[0]) { + case 'left-side': + return this._setProperty('azimuth', '270deg'); + + case 'far-left': + return this._setProperty('azimuth', (hasBehind ? 240 : 300) + 'deg'); + + case 'left': + return this._setProperty('azimuth', (hasBehind ? 220 : 320) + 'deg'); + + case 'center-left': + return this._setProperty('azimuth', (hasBehind ? 200 : 340) + 'deg'); + + case 'center': + return this._setProperty('azimuth', (hasBehind ? 180 : 0) + 'deg'); + + case 'center-right': + return this._setProperty('azimuth', (hasBehind ? 160 : 20) + 'deg'); + + case 'right': + return this._setProperty('azimuth', (hasBehind ? 140 : 40) + 'deg'); + + case 'far-right': + return this._setProperty('azimuth', (hasBehind ? 120 : 60) + 'deg'); + + case 'right-side': + return this._setProperty('azimuth', '90deg'); + + default: + return; + } + } + }, + get: function () { + return this.getPropertyValue('azimuth'); + }, + enumerable: true, + configurable: true +}; +var backgroundColor_export_isValid, backgroundColor_export_definition; + +var backgroundColor_local_var_parse = function parse(v) { + var parsed = external_dependency_parsers_0.parseColor(v); + + if (parsed !== undefined) { + return parsed; + } + + if (external_dependency_parsers_0.valueType(v) === external_dependency_parsers_0.TYPES.KEYWORD && (v.toLowerCase() === 'transparent' || v.toLowerCase() === 'inherit')) { + return v; + } + + return undefined; +}; + +backgroundColor_export_isValid = function isValid(v) { + return backgroundColor_local_var_parse(v) !== undefined; +}; + +backgroundColor_export_definition = { + set: function (v) { + var parsed = backgroundColor_local_var_parse(v); + + if (parsed === undefined) { + return; + } + + this._setProperty('background-color', parsed); + }, + get: function () { + return this.getPropertyValue('background-color'); + }, + enumerable: true, + configurable: true +}; +var backgroundImage_export_isValid, backgroundImage_export_definition; + +var backgroundImage_local_var_parse = function parse(v) { + var parsed = external_dependency_parsers_0.parseUrl(v); + + if (parsed !== undefined) { + return parsed; + } + + if (external_dependency_parsers_0.valueType(v) === external_dependency_parsers_0.TYPES.KEYWORD && (v.toLowerCase() === 'none' || v.toLowerCase() === 'inherit')) { + return v; + } + + return undefined; +}; + +backgroundImage_export_isValid = function isValid(v) { + return backgroundImage_local_var_parse(v) !== undefined; +}; + +backgroundImage_export_definition = { + set: function (v) { + this._setProperty('background-image', backgroundImage_local_var_parse(v)); + }, + get: function () { + return this.getPropertyValue('background-image'); + }, + enumerable: true, + configurable: true +}; +var backgroundRepeat_export_isValid, backgroundRepeat_export_definition; + +var backgroundRepeat_local_var_parse = function parse(v) { + if (external_dependency_parsers_0.valueType(v) === external_dependency_parsers_0.TYPES.KEYWORD && (v.toLowerCase() === 'repeat' || v.toLowerCase() === 'repeat-x' || v.toLowerCase() === 'repeat-y' || v.toLowerCase() === 'no-repeat' || v.toLowerCase() === 'inherit')) { + return v; + } + + return undefined; +}; + +backgroundRepeat_export_isValid = function isValid(v) { + return backgroundRepeat_local_var_parse(v) !== undefined; +}; + +backgroundRepeat_export_definition = { + set: function (v) { + this._setProperty('background-repeat', backgroundRepeat_local_var_parse(v)); + }, + get: function () { + return this.getPropertyValue('background-repeat'); + }, + enumerable: true, + configurable: true +}; +var backgroundAttachment_export_isValid, backgroundAttachment_export_definition; + +var backgroundAttachment_local_var_isValid = backgroundAttachment_export_isValid = function isValid(v) { + return external_dependency_parsers_0.valueType(v) === external_dependency_parsers_0.TYPES.KEYWORD && (v.toLowerCase() === 'scroll' || v.toLowerCase() === 'fixed' || v.toLowerCase() === 'inherit'); +}; + +backgroundAttachment_export_definition = { + set: function (v) { + if (!backgroundAttachment_local_var_isValid(v)) { + return; + } + + this._setProperty('background-attachment', v); + }, + get: function () { + return this.getPropertyValue('background-attachment'); + }, + enumerable: true, + configurable: true +}; +var backgroundPosition_export_isValid, backgroundPosition_export_definition; +var backgroundPosition_local_var_valid_keywords = ['top', 'center', 'bottom', 'left', 'right']; + +var backgroundPosition_local_var_parse = function parse(v) { + if (v === '' || v === null) { + return undefined; + } + + var parts = v.split(/\s+/); + + if (parts.length > 2 || parts.length < 1) { + return undefined; + } + + var types = []; + parts.forEach(function (part, index) { + types[index] = external_dependency_parsers_0.valueType(part); + }); + + if (parts.length === 1) { + if (types[0] === external_dependency_parsers_0.TYPES.LENGTH || types[0] === external_dependency_parsers_0.TYPES.PERCENT) { + return v; + } + + if (types[0] === external_dependency_parsers_0.TYPES.KEYWORD) { + if (backgroundPosition_local_var_valid_keywords.indexOf(v.toLowerCase()) !== -1 || v.toLowerCase() === 'inherit') { + return v; + } + } + + return undefined; + } + + if ((types[0] === external_dependency_parsers_0.TYPES.LENGTH || types[0] === external_dependency_parsers_0.TYPES.PERCENT) && (types[1] === external_dependency_parsers_0.TYPES.LENGTH || types[1] === external_dependency_parsers_0.TYPES.PERCENT)) { + return v; + } + + if (types[0] !== external_dependency_parsers_0.TYPES.KEYWORD || types[1] !== external_dependency_parsers_0.TYPES.KEYWORD) { + return undefined; + } + + if (backgroundPosition_local_var_valid_keywords.indexOf(parts[0]) !== -1 && backgroundPosition_local_var_valid_keywords.indexOf(parts[1]) !== -1) { + return v; + } + + return undefined; +}; + +backgroundPosition_export_isValid = function isValid(v) { + return backgroundPosition_local_var_parse(v) !== undefined; +}; + +backgroundPosition_export_definition = { + set: function (v) { + this._setProperty('background-position', backgroundPosition_local_var_parse(v)); + }, + get: function () { + return this.getPropertyValue('background-position'); + }, + enumerable: true, + configurable: true +}; +var background_export_definition; +var background_local_var_shorthand_for = { + 'background-color': { + isValid: backgroundColor_export_isValid, + definition: backgroundColor_export_definition + }, + 'background-image': { + isValid: backgroundImage_export_isValid, + definition: backgroundImage_export_definition + }, + 'background-repeat': { + isValid: backgroundRepeat_export_isValid, + definition: backgroundRepeat_export_definition + }, + 'background-attachment': { + isValid: backgroundAttachment_export_isValid, + definition: backgroundAttachment_export_definition + }, + 'background-position': { + isValid: backgroundPosition_export_isValid, + definition: backgroundPosition_export_definition + } +}; +background_export_definition = { + set: external_dependency_parsers_0.shorthandSetter('background', background_local_var_shorthand_for), + get: external_dependency_parsers_0.shorthandGetter('background', background_local_var_shorthand_for), + enumerable: true, + configurable: true +}; +var borderWidth_export_isValid, borderWidth_export_definition; +// the valid border-widths: +var borderWidth_local_var_widths = ['thin', 'medium', 'thick']; + +borderWidth_export_isValid = function parse(v) { + var length = external_dependency_parsers_0.parseLength(v); + + if (length !== undefined) { + return true; + } + + if (typeof v !== 'string') { + return false; + } + + if (v === '') { + return true; + } + + v = v.toLowerCase(); + + if (borderWidth_local_var_widths.indexOf(v) === -1) { + return false; + } + + return true; +}; + +var borderWidth_local_var_isValid = borderWidth_export_isValid; + +var borderWidth_local_var_parser = function (v) { + var length = external_dependency_parsers_0.parseLength(v); + + if (length !== undefined) { + return length; + } + + if (borderWidth_local_var_isValid(v)) { + return v.toLowerCase(); + } + + return undefined; +}; + +borderWidth_export_definition = { + set: external_dependency_parsers_0.implicitSetter('border', 'width', borderWidth_local_var_isValid, borderWidth_local_var_parser), + get: function () { + return this.getPropertyValue('border-width'); + }, + enumerable: true, + configurable: true +}; +var borderStyle_export_isValid, borderStyle_export_definition; +// the valid border-styles: +var borderStyle_local_var_styles = ['none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset']; + +borderStyle_export_isValid = function parse(v) { + return typeof v === 'string' && (v === '' || borderStyle_local_var_styles.indexOf(v) !== -1); +}; + +var borderStyle_local_var_isValid = borderStyle_export_isValid; + +var borderStyle_local_var_parser = function (v) { + if (borderStyle_local_var_isValid(v)) { + return v.toLowerCase(); + } + + return undefined; +}; + +borderStyle_export_definition = { + set: external_dependency_parsers_0.implicitSetter('border', 'style', borderStyle_local_var_isValid, borderStyle_local_var_parser), + get: function () { + return this.getPropertyValue('border-style'); + }, + enumerable: true, + configurable: true +}; +var borderColor_export_isValid, borderColor_export_definition; + +borderColor_export_isValid = function parse(v) { + if (typeof v !== 'string') { + return false; + } + + return v === '' || v.toLowerCase() === 'transparent' || external_dependency_parsers_0.valueType(v) === external_dependency_parsers_0.TYPES.COLOR; +}; + +var borderColor_local_var_isValid = borderColor_export_isValid; + +var borderColor_local_var_parser = function (v) { + if (borderColor_local_var_isValid(v)) { + return v.toLowerCase(); + } + + return undefined; +}; + +borderColor_export_definition = { + set: external_dependency_parsers_0.implicitSetter('border', 'color', borderColor_local_var_isValid, borderColor_local_var_parser), + get: function () { + return this.getPropertyValue('border-color'); + }, + enumerable: true, + configurable: true +}; +var border_export_definition; +var border_local_var_shorthand_for = { + 'border-width': { + isValid: borderWidth_export_isValid, + definition: borderWidth_export_definition + }, + 'border-style': { + isValid: borderStyle_export_isValid, + definition: borderStyle_export_definition + }, + 'border-color': { + isValid: borderColor_export_isValid, + definition: borderColor_export_definition + } +}; +var border_local_var_myShorthandSetter = external_dependency_parsers_0.shorthandSetter('border', border_local_var_shorthand_for); +var border_local_var_myShorthandGetter = external_dependency_parsers_0.shorthandGetter('border', border_local_var_shorthand_for); +border_export_definition = { + set: function (v) { + if (v.toString().toLowerCase() === 'none') { + v = ''; + } + + border_local_var_myShorthandSetter.call(this, v); + this.removeProperty('border-top'); + this.removeProperty('border-left'); + this.removeProperty('border-right'); + this.removeProperty('border-bottom'); + this._values['border-top'] = this._values.border; + this._values['border-left'] = this._values.border; + this._values['border-right'] = this._values.border; + this._values['border-bottom'] = this._values.border; + }, + get: border_local_var_myShorthandGetter, + enumerable: true, + configurable: true +}; +var borderBottomWidth_export_isValid, borderBottomWidth_export_definition; +var borderBottomWidth_local_var_isValid = borderBottomWidth_export_isValid = borderWidth_export_isValid; +borderBottomWidth_export_definition = { + set: function (v) { + if (borderBottomWidth_local_var_isValid(v)) { + this._setProperty('border-bottom-width', v); + } + }, + get: function () { + return this.getPropertyValue('border-bottom-width'); + }, + enumerable: true, + configurable: true +}; +var borderBottomStyle_export_isValid, borderBottomStyle_export_definition; +borderBottomStyle_export_isValid = borderStyle_export_isValid; +borderBottomStyle_export_definition = { + set: function (v) { + if (borderStyle_export_isValid(v)) { + if (v.toLowerCase() === 'none') { + v = ''; + this.removeProperty('border-bottom-width'); + } + + this._setProperty('border-bottom-style', v); + } + }, + get: function () { + return this.getPropertyValue('border-bottom-style'); + }, + enumerable: true, + configurable: true +}; +var borderBottomColor_export_isValid, borderBottomColor_export_definition; +var borderBottomColor_local_var_isValid = borderBottomColor_export_isValid = borderColor_export_isValid; +borderBottomColor_export_definition = { + set: function (v) { + if (borderBottomColor_local_var_isValid(v)) { + this._setProperty('border-bottom-color', v); + } + }, + get: function () { + return this.getPropertyValue('border-bottom-color'); + }, + enumerable: true, + configurable: true +}; +var borderBottom_export_definition; +var borderBottom_local_var_shorthand_for = { + 'border-bottom-width': { + isValid: borderBottomWidth_export_isValid, + definition: borderBottomWidth_export_definition + }, + 'border-bottom-style': { + isValid: borderBottomStyle_export_isValid, + definition: borderBottomStyle_export_definition + }, + 'border-bottom-color': { + isValid: borderBottomColor_export_isValid, + definition: borderBottomColor_export_definition + } +}; +borderBottom_export_definition = { + set: external_dependency_parsers_0.shorthandSetter('border-bottom', borderBottom_local_var_shorthand_for), + get: external_dependency_parsers_0.shorthandGetter('border-bottom', borderBottom_local_var_shorthand_for), + enumerable: true, + configurable: true +}; +var borderCollapse_export_definition; + +var borderCollapse_local_var_parse = function parse(v) { + if (external_dependency_parsers_0.valueType(v) === external_dependency_parsers_0.TYPES.KEYWORD && (v.toLowerCase() === 'collapse' || v.toLowerCase() === 'separate' || v.toLowerCase() === 'inherit')) { + return v; + } + + return undefined; +}; + +borderCollapse_export_definition = { + set: function (v) { + this._setProperty('border-collapse', borderCollapse_local_var_parse(v)); + }, + get: function () { + return this.getPropertyValue('border-collapse'); + }, + enumerable: true, + configurable: true +}; +var borderLeftWidth_export_isValid, borderLeftWidth_export_definition; +var borderLeftWidth_local_var_isValid = borderLeftWidth_export_isValid = borderWidth_export_isValid; +borderLeftWidth_export_definition = { + set: function (v) { + if (borderLeftWidth_local_var_isValid(v)) { + this._setProperty('border-left-width', v); + } + }, + get: function () { + return this.getPropertyValue('border-left-width'); + }, + enumerable: true, + configurable: true +}; +var borderLeftStyle_export_isValid, borderLeftStyle_export_definition; +borderLeftStyle_export_isValid = borderStyle_export_isValid; +borderLeftStyle_export_definition = { + set: function (v) { + if (borderStyle_export_isValid(v)) { + if (v.toLowerCase() === 'none') { + v = ''; + this.removeProperty('border-left-width'); + } + + this._setProperty('border-left-style', v); + } + }, + get: function () { + return this.getPropertyValue('border-left-style'); + }, + enumerable: true, + configurable: true +}; +var borderLeftColor_export_isValid, borderLeftColor_export_definition; +var borderLeftColor_local_var_isValid = borderLeftColor_export_isValid = borderColor_export_isValid; +borderLeftColor_export_definition = { + set: function (v) { + if (borderLeftColor_local_var_isValid(v)) { + this._setProperty('border-left-color', v); + } + }, + get: function () { + return this.getPropertyValue('border-left-color'); + }, + enumerable: true, + configurable: true +}; +var borderLeft_export_definition; +var borderLeft_local_var_shorthand_for = { + 'border-left-width': { + isValid: borderLeftWidth_export_isValid, + definition: borderLeftWidth_export_definition + }, + 'border-left-style': { + isValid: borderLeftStyle_export_isValid, + definition: borderLeftStyle_export_definition + }, + 'border-left-color': { + isValid: borderLeftColor_export_isValid, + definition: borderLeftColor_export_definition + } +}; +borderLeft_export_definition = { + set: external_dependency_parsers_0.shorthandSetter('border-left', borderLeft_local_var_shorthand_for), + get: external_dependency_parsers_0.shorthandGetter('border-left', borderLeft_local_var_shorthand_for), + enumerable: true, + configurable: true +}; +var borderRightWidth_export_isValid, borderRightWidth_export_definition; +var borderRightWidth_local_var_isValid = borderRightWidth_export_isValid = borderWidth_export_isValid; +borderRightWidth_export_definition = { + set: function (v) { + if (borderRightWidth_local_var_isValid(v)) { + this._setProperty('border-right-width', v); + } + }, + get: function () { + return this.getPropertyValue('border-right-width'); + }, + enumerable: true, + configurable: true +}; +var borderRightStyle_export_isValid, borderRightStyle_export_definition; +borderRightStyle_export_isValid = borderStyle_export_isValid; +borderRightStyle_export_definition = { + set: function (v) { + if (borderStyle_export_isValid(v)) { + if (v.toLowerCase() === 'none') { + v = ''; + this.removeProperty('border-right-width'); + } + + this._setProperty('border-right-style', v); + } + }, + get: function () { + return this.getPropertyValue('border-right-style'); + }, + enumerable: true, + configurable: true +}; +var borderRightColor_export_isValid, borderRightColor_export_definition; +var borderRightColor_local_var_isValid = borderRightColor_export_isValid = borderColor_export_isValid; +borderRightColor_export_definition = { + set: function (v) { + if (borderRightColor_local_var_isValid(v)) { + this._setProperty('border-right-color', v); + } + }, + get: function () { + return this.getPropertyValue('border-right-color'); + }, + enumerable: true, + configurable: true +}; +var borderRight_export_definition; +var borderRight_local_var_shorthand_for = { + 'border-right-width': { + isValid: borderRightWidth_export_isValid, + definition: borderRightWidth_export_definition + }, + 'border-right-style': { + isValid: borderRightStyle_export_isValid, + definition: borderRightStyle_export_definition + }, + 'border-right-color': { + isValid: borderRightColor_export_isValid, + definition: borderRightColor_export_definition + } +}; +borderRight_export_definition = { + set: external_dependency_parsers_0.shorthandSetter('border-right', borderRight_local_var_shorthand_for), + get: external_dependency_parsers_0.shorthandGetter('border-right', borderRight_local_var_shorthand_for), + enumerable: true, + configurable: true +}; +var borderSpacing_export_definition; + +// ? | inherit +// if one, it applies to both horizontal and verical spacing +// if two, the first applies to the horizontal and the second applies to vertical spacing +var borderSpacing_local_var_parse = function parse(v) { + if (v === '' || v === null) { + return undefined; + } + + if (v === 0) { + return '0px'; + } + + if (v.toLowerCase() === 'inherit') { + return v; + } + + var parts = v.split(/\s+/); + + if (parts.length !== 1 && parts.length !== 2) { + return undefined; + } + + parts.forEach(function (part) { + if (external_dependency_parsers_0.valueType(part) !== external_dependency_parsers_0.TYPES.LENGTH) { + return undefined; + } + }); + return v; +}; + +borderSpacing_export_definition = { + set: function (v) { + this._setProperty('border-spacing', borderSpacing_local_var_parse(v)); + }, + get: function () { + return this.getPropertyValue('border-spacing'); + }, + enumerable: true, + configurable: true +}; +var borderTopWidth_export_isValid, borderTopWidth_export_definition; +borderTopWidth_export_isValid = borderWidth_export_isValid; +borderTopWidth_export_definition = { + set: function (v) { + if (borderWidth_export_isValid(v)) { + this._setProperty('border-top-width', v); + } + }, + get: function () { + return this.getPropertyValue('border-top-width'); + }, + enumerable: true, + configurable: true +}; +var borderTopStyle_export_isValid, borderTopStyle_export_definition; +borderTopStyle_export_isValid = borderStyle_export_isValid; +borderTopStyle_export_definition = { + set: function (v) { + if (borderStyle_export_isValid(v)) { + if (v.toLowerCase() === 'none') { + v = ''; + this.removeProperty('border-top-width'); + } + + this._setProperty('border-top-style', v); + } + }, + get: function () { + return this.getPropertyValue('border-top-style'); + }, + enumerable: true, + configurable: true +}; +var borderTopColor_export_isValid, borderTopColor_export_definition; +var borderTopColor_local_var_isValid = borderTopColor_export_isValid = borderColor_export_isValid; +borderTopColor_export_definition = { + set: function (v) { + if (borderTopColor_local_var_isValid(v)) { + this._setProperty('border-top-color', v); + } + }, + get: function () { + return this.getPropertyValue('border-top-color'); + }, + enumerable: true, + configurable: true +}; +var borderTop_export_definition; +var borderTop_local_var_shorthand_for = { + 'border-top-width': { + isValid: borderTopWidth_export_isValid, + definition: borderTopWidth_export_definition + }, + 'border-top-style': { + isValid: borderTopStyle_export_isValid, + definition: borderTopStyle_export_definition + }, + 'border-top-color': { + isValid: borderTopColor_export_isValid, + definition: borderTopColor_export_definition + } +}; +borderTop_export_definition = { + set: external_dependency_parsers_0.shorthandSetter('border-top', borderTop_local_var_shorthand_for), + get: external_dependency_parsers_0.shorthandGetter('border-top', borderTop_local_var_shorthand_for), + enumerable: true, + configurable: true +}; +var bottom_export_definition; +bottom_export_definition = { + set: function (v) { + this._setProperty('bottom', external_dependency_parsers_0.parseMeasurement(v)); + }, + get: function () { + return this.getPropertyValue('bottom'); + }, + enumerable: true, + configurable: true +}; +var clear_export_definition; +var clear_local_var_clear_keywords = ['none', 'left', 'right', 'both', 'inherit']; +clear_export_definition = { + set: function (v) { + this._setProperty('clear', external_dependency_parsers_0.parseKeyword(v, clear_local_var_clear_keywords)); + }, + get: function () { + return this.getPropertyValue('clear'); + }, + enumerable: true, + configurable: true +}; +var clip_export_definition; +var clip_local_var_shape_regex = /^rect\((.*)\)$/i; + +var clip_local_var_parse = function (val) { + if (val === '' || val === null) { + return val; + } + + if (typeof val !== 'string') { + return undefined; + } + + val = val.toLowerCase(); + + if (val === 'auto' || val === 'inherit') { + return val; + } + + var matches = val.match(clip_local_var_shape_regex); + + if (!matches) { + return undefined; + } + + var parts = matches[1].split(/\s*,\s*/); + + if (parts.length !== 4) { + return undefined; + } + + var valid = parts.every(function (part, index) { + var measurement = external_dependency_parsers_0.parseMeasurement(part); + parts[index] = measurement; + return measurement !== undefined; + }); + + if (!valid) { + return undefined; + } + + parts = parts.join(', '); + return val.replace(matches[1], parts); +}; + +clip_export_definition = { + set: function (v) { + this._setProperty('clip', clip_local_var_parse(v)); + }, + get: function () { + return this.getPropertyValue('clip'); + }, + enumerable: true, + configurable: true +}; +var color_export_definition; +color_export_definition = { + set: function (v) { + this._setProperty('color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('color'); + }, + enumerable: true, + configurable: true +}; +var cssFloat_export_definition; +cssFloat_export_definition = { + set: function (v) { + this._setProperty('float', v); + }, + get: function () { + return this.getPropertyValue('float'); + }, + enumerable: true, + configurable: true +}; +var flexGrow_export_isValid, flexGrow_export_definition; + +flexGrow_export_isValid = function isValid(v, positionAtFlexShorthand) { + return external_dependency_parsers_0.parseNumber(v) !== undefined && positionAtFlexShorthand === external_dependency_constants_1.POSITION_AT_SHORTHAND.first; +}; + +flexGrow_export_definition = { + set: function (v) { + this._setProperty('flex-grow', external_dependency_parsers_0.parseNumber(v)); + }, + get: function () { + return this.getPropertyValue('flex-grow'); + }, + enumerable: true, + configurable: true +}; +var flexShrink_export_isValid, flexShrink_export_definition; + +flexShrink_export_isValid = function isValid(v, positionAtFlexShorthand) { + return external_dependency_parsers_0.parseNumber(v) !== undefined && positionAtFlexShorthand === external_dependency_constants_1.POSITION_AT_SHORTHAND.second; +}; + +flexShrink_export_definition = { + set: function (v) { + this._setProperty('flex-shrink', external_dependency_parsers_0.parseNumber(v)); + }, + get: function () { + return this.getPropertyValue('flex-shrink'); + }, + enumerable: true, + configurable: true +}; +var flexBasis_export_isValid, flexBasis_export_definition; + +function flexBasis_local_fn_parse(v) { + if (String(v).toLowerCase() === 'auto') { + return 'auto'; + } + + if (String(v).toLowerCase() === 'inherit') { + return 'inherit'; + } + + return external_dependency_parsers_0.parseMeasurement(v); +} + +flexBasis_export_isValid = function isValid(v) { + return flexBasis_local_fn_parse(v) !== undefined; +}; + +flexBasis_export_definition = { + set: function (v) { + this._setProperty('flex-basis', flexBasis_local_fn_parse(v)); + }, + get: function () { + return this.getPropertyValue('flex-basis'); + }, + enumerable: true, + configurable: true +}; +var flex_export_isValid, flex_export_definition; +var flex_local_var_shorthand_for = { + 'flex-grow': { + isValid: flexGrow_export_isValid, + definition: flexGrow_export_definition + }, + 'flex-shrink': { + isValid: flexShrink_export_isValid, + definition: flexShrink_export_definition + }, + 'flex-basis': { + isValid: flexBasis_export_isValid, + definition: flexBasis_export_definition + } +}; +var flex_local_var_myShorthandSetter = external_dependency_parsers_0.shorthandSetter('flex', flex_local_var_shorthand_for); + +flex_export_isValid = function isValid(v) { + return external_dependency_parsers_0.shorthandParser(v, flex_local_var_shorthand_for) !== undefined; +}; + +flex_export_definition = { + set: function (v) { + var normalizedValue = String(v).trim().toLowerCase(); + + if (normalizedValue === 'none') { + flex_local_var_myShorthandSetter.call(this, '0 0 auto'); + return; + } + + if (normalizedValue === 'initial') { + flex_local_var_myShorthandSetter.call(this, '0 1 auto'); + return; + } + + if (normalizedValue === 'auto') { + this.removeProperty('flex-grow'); + this.removeProperty('flex-shrink'); + this.setProperty('flex-basis', normalizedValue); + return; + } + + flex_local_var_myShorthandSetter.call(this, v); + }, + get: external_dependency_parsers_0.shorthandGetter('flex', flex_local_var_shorthand_for), + enumerable: true, + configurable: true +}; +var float_export_definition; +float_export_definition = { + set: function (v) { + this._setProperty('float', v); + }, + get: function () { + return this.getPropertyValue('float'); + }, + enumerable: true, + configurable: true +}; +var floodColor_export_definition; +floodColor_export_definition = { + set: function (v) { + this._setProperty('flood-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('flood-color'); + }, + enumerable: true, + configurable: true +}; +var fontFamily_export_isValid, fontFamily_export_definition; +var fontFamily_local_var_partsRegEx = /\s*,\s*/; + +fontFamily_export_isValid = function isValid(v) { + if (v === '' || v === null) { + return true; + } + + var parts = v.split(fontFamily_local_var_partsRegEx); + var len = parts.length; + var i; + var type; + + for (i = 0; i < len; i++) { + type = external_dependency_parsers_0.valueType(parts[i]); + + if (type === external_dependency_parsers_0.TYPES.STRING || type === external_dependency_parsers_0.TYPES.KEYWORD) { + return true; + } + } + + return false; +}; + +fontFamily_export_definition = { + set: function (v) { + this._setProperty('font-family', v); + }, + get: function () { + return this.getPropertyValue('font-family'); + }, + enumerable: true, + configurable: true +}; +var fontSize_export_isValid, fontSize_export_definition; +var fontSize_local_var_absoluteSizes = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large']; +var fontSize_local_var_relativeSizes = ['larger', 'smaller']; + +fontSize_export_isValid = function (v) { + var type = external_dependency_parsers_0.valueType(v.toLowerCase()); + return type === external_dependency_parsers_0.TYPES.LENGTH || type === external_dependency_parsers_0.TYPES.PERCENT || type === external_dependency_parsers_0.TYPES.KEYWORD && fontSize_local_var_absoluteSizes.indexOf(v.toLowerCase()) !== -1 || type === external_dependency_parsers_0.TYPES.KEYWORD && fontSize_local_var_relativeSizes.indexOf(v.toLowerCase()) !== -1; +}; + +fontSize_export_definition = { + set: function (v) { + this._setProperty('font-size', v); + }, + get: function () { + return this.getPropertyValue('font-size'); + }, + enumerable: true, + configurable: true +}; +var fontStyle_export_isValid, fontStyle_export_definition; +var fontStyle_local_var_valid_styles = ['normal', 'italic', 'oblique', 'inherit']; + +fontStyle_export_isValid = function (v) { + return fontStyle_local_var_valid_styles.indexOf(v.toLowerCase()) !== -1; +}; + +fontStyle_export_definition = { + set: function (v) { + this._setProperty('font-style', v); + }, + get: function () { + return this.getPropertyValue('font-style'); + }, + enumerable: true, + configurable: true +}; +var fontVariant_export_isValid, fontVariant_export_definition; +var fontVariant_local_var_valid_variants = ['normal', 'small-caps', 'inherit']; + +fontVariant_export_isValid = function isValid(v) { + return fontVariant_local_var_valid_variants.indexOf(v.toLowerCase()) !== -1; +}; + +fontVariant_export_definition = { + set: function (v) { + this._setProperty('font-variant', v); + }, + get: function () { + return this.getPropertyValue('font-variant'); + }, + enumerable: true, + configurable: true +}; +var fontWeight_export_isValid, fontWeight_export_definition; +var fontWeight_local_var_valid_weights = ['normal', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '700', '800', '900', 'inherit']; + +fontWeight_export_isValid = function isValid(v) { + return fontWeight_local_var_valid_weights.indexOf(v.toLowerCase()) !== -1; +}; + +fontWeight_export_definition = { + set: function (v) { + this._setProperty('font-weight', v); + }, + get: function () { + return this.getPropertyValue('font-weight'); + }, + enumerable: true, + configurable: true +}; +var lineHeight_export_isValid, lineHeight_export_definition; + +lineHeight_export_isValid = function isValid(v) { + var type = external_dependency_parsers_0.valueType(v); + return type === external_dependency_parsers_0.TYPES.KEYWORD && v.toLowerCase() === 'normal' || v.toLowerCase() === 'inherit' || type === external_dependency_parsers_0.TYPES.NUMBER || type === external_dependency_parsers_0.TYPES.LENGTH || type === external_dependency_parsers_0.TYPES.PERCENT; +}; + +lineHeight_export_definition = { + set: function (v) { + this._setProperty('line-height', v); + }, + get: function () { + return this.getPropertyValue('line-height'); + }, + enumerable: true, + configurable: true +}; +var font_export_definition; +var font_local_var_shorthand_for = { + 'font-family': { + isValid: fontFamily_export_isValid, + definition: fontFamily_export_definition + }, + 'font-size': { + isValid: fontSize_export_isValid, + definition: fontSize_export_definition + }, + 'font-style': { + isValid: fontStyle_export_isValid, + definition: fontStyle_export_definition + }, + 'font-variant': { + isValid: fontVariant_export_isValid, + definition: fontVariant_export_definition + }, + 'font-weight': { + isValid: fontWeight_export_isValid, + definition: fontWeight_export_definition + }, + 'line-height': { + isValid: lineHeight_export_isValid, + definition: lineHeight_export_definition + } +}; +var font_local_var_static_fonts = ['caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar', 'inherit']; +var font_local_var_setter = external_dependency_parsers_0.shorthandSetter('font', font_local_var_shorthand_for); +font_export_definition = { + set: function (v) { + var short = external_dependency_parsers_0.shorthandParser(v, font_local_var_shorthand_for); + + if (short !== undefined) { + return font_local_var_setter.call(this, v); + } + + if (external_dependency_parsers_0.valueType(v) === external_dependency_parsers_0.TYPES.KEYWORD && font_local_var_static_fonts.indexOf(v.toLowerCase()) !== -1) { + this._setProperty('font', v); + } + }, + get: external_dependency_parsers_0.shorthandGetter('font', font_local_var_shorthand_for), + enumerable: true, + configurable: true +}; +var height_export_definition; + +function height_local_fn_parse(v) { + if (String(v).toLowerCase() === 'auto') { + return 'auto'; + } + + if (String(v).toLowerCase() === 'inherit') { + return 'inherit'; + } + + return external_dependency_parsers_0.parseMeasurement(v); +} + +height_export_definition = { + set: function (v) { + this._setProperty('height', height_local_fn_parse(v)); + }, + get: function () { + return this.getPropertyValue('height'); + }, + enumerable: true, + configurable: true +}; +var left_export_definition; +left_export_definition = { + set: function (v) { + this._setProperty('left', external_dependency_parsers_0.parseMeasurement(v)); + }, + get: function () { + return this.getPropertyValue('left'); + }, + enumerable: true, + configurable: true +}; +var lightingColor_export_definition; +lightingColor_export_definition = { + set: function (v) { + this._setProperty('lighting-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('lighting-color'); + }, + enumerable: true, + configurable: true +}; +var margin_export_definition, margin_export_isValid, margin_export_parser; +var margin_local_var_TYPES = external_dependency_parsers_0.TYPES; + +var margin_local_var_isValid = function (v) { + if (v.toLowerCase() === 'auto') { + return true; + } + + var type = external_dependency_parsers_0.valueType(v); + return type === margin_local_var_TYPES.LENGTH || type === margin_local_var_TYPES.PERCENT || type === margin_local_var_TYPES.INTEGER && (v === '0' || v === 0); +}; + +var margin_local_var_parser = function (v) { + var V = v.toLowerCase(); + + if (V === 'auto') { + return V; + } + + return external_dependency_parsers_0.parseMeasurement(v); +}; + +var margin_local_var_mySetter = external_dependency_parsers_0.implicitSetter('margin', '', margin_local_var_isValid, margin_local_var_parser); +var margin_local_var_myGlobal = external_dependency_parsers_0.implicitSetter('margin', '', function () { + return true; +}, function (v) { + return v; +}); +margin_export_definition = { + set: function (v) { + if (typeof v === 'number') { + v = String(v); + } + + if (typeof v !== 'string') { + return; + } + + var V = v.toLowerCase(); + + switch (V) { + case 'inherit': + case 'initial': + case 'unset': + case '': + margin_local_var_myGlobal.call(this, V); + break; + + default: + margin_local_var_mySetter.call(this, v); + break; + } + }, + get: function () { + return this.getPropertyValue('margin'); + }, + enumerable: true, + configurable: true +}; +margin_export_isValid = margin_local_var_isValid; +margin_export_parser = margin_local_var_parser; +var marginBottom_export_definition; +marginBottom_export_definition = { + set: external_dependency_parsers_0.subImplicitSetter('margin', 'bottom', { + definition: margin_export_definition, + isValid: margin_export_isValid, + parser: margin_export_parser + }.isValid, { + definition: margin_export_definition, + isValid: margin_export_isValid, + parser: margin_export_parser + }.parser), + get: function () { + return this.getPropertyValue('margin-bottom'); + }, + enumerable: true, + configurable: true +}; +var marginLeft_export_definition; +marginLeft_export_definition = { + set: external_dependency_parsers_0.subImplicitSetter('margin', 'left', { + definition: margin_export_definition, + isValid: margin_export_isValid, + parser: margin_export_parser + }.isValid, { + definition: margin_export_definition, + isValid: margin_export_isValid, + parser: margin_export_parser + }.parser), + get: function () { + return this.getPropertyValue('margin-left'); + }, + enumerable: true, + configurable: true +}; +var marginRight_export_definition; +marginRight_export_definition = { + set: external_dependency_parsers_0.subImplicitSetter('margin', 'right', { + definition: margin_export_definition, + isValid: margin_export_isValid, + parser: margin_export_parser + }.isValid, { + definition: margin_export_definition, + isValid: margin_export_isValid, + parser: margin_export_parser + }.parser), + get: function () { + return this.getPropertyValue('margin-right'); + }, + enumerable: true, + configurable: true +}; +var marginTop_export_definition; +marginTop_export_definition = { + set: external_dependency_parsers_0.subImplicitSetter('margin', 'top', { + definition: margin_export_definition, + isValid: margin_export_isValid, + parser: margin_export_parser + }.isValid, { + definition: margin_export_definition, + isValid: margin_export_isValid, + parser: margin_export_parser + }.parser), + get: function () { + return this.getPropertyValue('margin-top'); + }, + enumerable: true, + configurable: true +}; +var opacity_export_definition; +opacity_export_definition = { + set: function (v) { + this._setProperty('opacity', external_dependency_parsers_0.parseNumber(v)); + }, + get: function () { + return this.getPropertyValue('opacity'); + }, + enumerable: true, + configurable: true +}; +var outlineColor_export_definition; +outlineColor_export_definition = { + set: function (v) { + this._setProperty('outline-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('outline-color'); + }, + enumerable: true, + configurable: true +}; +var padding_export_definition, padding_export_isValid, padding_export_parser; +var padding_local_var_TYPES = external_dependency_parsers_0.TYPES; + +var padding_local_var_isValid = function (v) { + var type = external_dependency_parsers_0.valueType(v); + return type === padding_local_var_TYPES.LENGTH || type === padding_local_var_TYPES.PERCENT || type === padding_local_var_TYPES.INTEGER && (v === '0' || v === 0); +}; + +var padding_local_var_parser = function (v) { + return external_dependency_parsers_0.parseMeasurement(v); +}; + +var padding_local_var_mySetter = external_dependency_parsers_0.implicitSetter('padding', '', padding_local_var_isValid, padding_local_var_parser); +var padding_local_var_myGlobal = external_dependency_parsers_0.implicitSetter('padding', '', function () { + return true; +}, function (v) { + return v; +}); +padding_export_definition = { + set: function (v) { + if (typeof v === 'number') { + v = String(v); + } + + if (typeof v !== 'string') { + return; + } + + var V = v.toLowerCase(); + + switch (V) { + case 'inherit': + case 'initial': + case 'unset': + case '': + padding_local_var_myGlobal.call(this, V); + break; + + default: + padding_local_var_mySetter.call(this, v); + break; + } + }, + get: function () { + return this.getPropertyValue('padding'); + }, + enumerable: true, + configurable: true +}; +padding_export_isValid = padding_local_var_isValid; +padding_export_parser = padding_local_var_parser; +var paddingBottom_export_definition; +paddingBottom_export_definition = { + set: external_dependency_parsers_0.subImplicitSetter('padding', 'bottom', { + definition: padding_export_definition, + isValid: padding_export_isValid, + parser: padding_export_parser + }.isValid, { + definition: padding_export_definition, + isValid: padding_export_isValid, + parser: padding_export_parser + }.parser), + get: function () { + return this.getPropertyValue('padding-bottom'); + }, + enumerable: true, + configurable: true +}; +var paddingLeft_export_definition; +paddingLeft_export_definition = { + set: external_dependency_parsers_0.subImplicitSetter('padding', 'left', { + definition: padding_export_definition, + isValid: padding_export_isValid, + parser: padding_export_parser + }.isValid, { + definition: padding_export_definition, + isValid: padding_export_isValid, + parser: padding_export_parser + }.parser), + get: function () { + return this.getPropertyValue('padding-left'); + }, + enumerable: true, + configurable: true +}; +var paddingRight_export_definition; +paddingRight_export_definition = { + set: external_dependency_parsers_0.subImplicitSetter('padding', 'right', { + definition: padding_export_definition, + isValid: padding_export_isValid, + parser: padding_export_parser + }.isValid, { + definition: padding_export_definition, + isValid: padding_export_isValid, + parser: padding_export_parser + }.parser), + get: function () { + return this.getPropertyValue('padding-right'); + }, + enumerable: true, + configurable: true +}; +var paddingTop_export_definition; +paddingTop_export_definition = { + set: external_dependency_parsers_0.subImplicitSetter('padding', 'top', { + definition: padding_export_definition, + isValid: padding_export_isValid, + parser: padding_export_parser + }.isValid, { + definition: padding_export_definition, + isValid: padding_export_isValid, + parser: padding_export_parser + }.parser), + get: function () { + return this.getPropertyValue('padding-top'); + }, + enumerable: true, + configurable: true +}; +var right_export_definition; +right_export_definition = { + set: function (v) { + this._setProperty('right', external_dependency_parsers_0.parseMeasurement(v)); + }, + get: function () { + return this.getPropertyValue('right'); + }, + enumerable: true, + configurable: true +}; +var stopColor_export_definition; +stopColor_export_definition = { + set: function (v) { + this._setProperty('stop-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('stop-color'); + }, + enumerable: true, + configurable: true +}; +var textLineThroughColor_export_definition; +textLineThroughColor_export_definition = { + set: function (v) { + this._setProperty('text-line-through-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('text-line-through-color'); + }, + enumerable: true, + configurable: true +}; +var textOverlineColor_export_definition; +textOverlineColor_export_definition = { + set: function (v) { + this._setProperty('text-overline-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('text-overline-color'); + }, + enumerable: true, + configurable: true +}; +var textUnderlineColor_export_definition; +textUnderlineColor_export_definition = { + set: function (v) { + this._setProperty('text-underline-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('text-underline-color'); + }, + enumerable: true, + configurable: true +}; +var top_export_definition; +top_export_definition = { + set: function (v) { + this._setProperty('top', external_dependency_parsers_0.parseMeasurement(v)); + }, + get: function () { + return this.getPropertyValue('top'); + }, + enumerable: true, + configurable: true +}; +var webkitBorderAfterColor_export_definition; +webkitBorderAfterColor_export_definition = { + set: function (v) { + this._setProperty('-webkit-border-after-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('-webkit-border-after-color'); + }, + enumerable: true, + configurable: true +}; +var webkitBorderBeforeColor_export_definition; +webkitBorderBeforeColor_export_definition = { + set: function (v) { + this._setProperty('-webkit-border-before-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('-webkit-border-before-color'); + }, + enumerable: true, + configurable: true +}; +var webkitBorderEndColor_export_definition; +webkitBorderEndColor_export_definition = { + set: function (v) { + this._setProperty('-webkit-border-end-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('-webkit-border-end-color'); + }, + enumerable: true, + configurable: true +}; +var webkitBorderStartColor_export_definition; +webkitBorderStartColor_export_definition = { + set: function (v) { + this._setProperty('-webkit-border-start-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('-webkit-border-start-color'); + }, + enumerable: true, + configurable: true +}; +var webkitColumnRuleColor_export_definition; +webkitColumnRuleColor_export_definition = { + set: function (v) { + this._setProperty('-webkit-column-rule-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('-webkit-column-rule-color'); + }, + enumerable: true, + configurable: true +}; +var webkitMatchNearestMailBlockquoteColor_export_definition; +webkitMatchNearestMailBlockquoteColor_export_definition = { + set: function (v) { + this._setProperty('-webkit-match-nearest-mail-blockquote-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('-webkit-match-nearest-mail-blockquote-color'); + }, + enumerable: true, + configurable: true +}; +var webkitTapHighlightColor_export_definition; +webkitTapHighlightColor_export_definition = { + set: function (v) { + this._setProperty('-webkit-tap-highlight-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('-webkit-tap-highlight-color'); + }, + enumerable: true, + configurable: true +}; +var webkitTextEmphasisColor_export_definition; +webkitTextEmphasisColor_export_definition = { + set: function (v) { + this._setProperty('-webkit-text-emphasis-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('-webkit-text-emphasis-color'); + }, + enumerable: true, + configurable: true +}; +var webkitTextFillColor_export_definition; +webkitTextFillColor_export_definition = { + set: function (v) { + this._setProperty('-webkit-text-fill-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('-webkit-text-fill-color'); + }, + enumerable: true, + configurable: true +}; +var webkitTextStrokeColor_export_definition; +webkitTextStrokeColor_export_definition = { + set: function (v) { + this._setProperty('-webkit-text-stroke-color', external_dependency_parsers_0.parseColor(v)); + }, + get: function () { + return this.getPropertyValue('-webkit-text-stroke-color'); + }, + enumerable: true, + configurable: true +}; +var width_export_definition; + +function width_local_fn_parse(v) { + if (String(v).toLowerCase() === 'auto') { + return 'auto'; + } + + if (String(v).toLowerCase() === 'inherit') { + return 'inherit'; + } + + return external_dependency_parsers_0.parseMeasurement(v); +} + +width_export_definition = { + set: function (v) { + this._setProperty('width', width_local_fn_parse(v)); + }, + get: function () { + return this.getPropertyValue('width'); + }, + enumerable: true, + configurable: true +}; + +module.exports = function (prototype) { + Object.defineProperties(prototype, { + azimuth: azimuth_export_definition, + backgroundColor: backgroundColor_export_definition, + "background-color": backgroundColor_export_definition, + backgroundImage: backgroundImage_export_definition, + "background-image": backgroundImage_export_definition, + backgroundRepeat: backgroundRepeat_export_definition, + "background-repeat": backgroundRepeat_export_definition, + backgroundAttachment: backgroundAttachment_export_definition, + "background-attachment": backgroundAttachment_export_definition, + backgroundPosition: backgroundPosition_export_definition, + "background-position": backgroundPosition_export_definition, + background: background_export_definition, + borderWidth: borderWidth_export_definition, + "border-width": borderWidth_export_definition, + borderStyle: borderStyle_export_definition, + "border-style": borderStyle_export_definition, + borderColor: borderColor_export_definition, + "border-color": borderColor_export_definition, + border: border_export_definition, + borderBottomWidth: borderBottomWidth_export_definition, + "border-bottom-width": borderBottomWidth_export_definition, + borderBottomStyle: borderBottomStyle_export_definition, + "border-bottom-style": borderBottomStyle_export_definition, + borderBottomColor: borderBottomColor_export_definition, + "border-bottom-color": borderBottomColor_export_definition, + borderBottom: borderBottom_export_definition, + "border-bottom": borderBottom_export_definition, + borderCollapse: borderCollapse_export_definition, + "border-collapse": borderCollapse_export_definition, + borderLeftWidth: borderLeftWidth_export_definition, + "border-left-width": borderLeftWidth_export_definition, + borderLeftStyle: borderLeftStyle_export_definition, + "border-left-style": borderLeftStyle_export_definition, + borderLeftColor: borderLeftColor_export_definition, + "border-left-color": borderLeftColor_export_definition, + borderLeft: borderLeft_export_definition, + "border-left": borderLeft_export_definition, + borderRightWidth: borderRightWidth_export_definition, + "border-right-width": borderRightWidth_export_definition, + borderRightStyle: borderRightStyle_export_definition, + "border-right-style": borderRightStyle_export_definition, + borderRightColor: borderRightColor_export_definition, + "border-right-color": borderRightColor_export_definition, + borderRight: borderRight_export_definition, + "border-right": borderRight_export_definition, + borderSpacing: borderSpacing_export_definition, + "border-spacing": borderSpacing_export_definition, + borderTopWidth: borderTopWidth_export_definition, + "border-top-width": borderTopWidth_export_definition, + borderTopStyle: borderTopStyle_export_definition, + "border-top-style": borderTopStyle_export_definition, + borderTopColor: borderTopColor_export_definition, + "border-top-color": borderTopColor_export_definition, + borderTop: borderTop_export_definition, + "border-top": borderTop_export_definition, + bottom: bottom_export_definition, + clear: clear_export_definition, + clip: clip_export_definition, + color: color_export_definition, + cssFloat: cssFloat_export_definition, + "css-float": cssFloat_export_definition, + flexGrow: flexGrow_export_definition, + "flex-grow": flexGrow_export_definition, + flexShrink: flexShrink_export_definition, + "flex-shrink": flexShrink_export_definition, + flexBasis: flexBasis_export_definition, + "flex-basis": flexBasis_export_definition, + flex: flex_export_definition, + float: float_export_definition, + floodColor: floodColor_export_definition, + "flood-color": floodColor_export_definition, + fontFamily: fontFamily_export_definition, + "font-family": fontFamily_export_definition, + fontSize: fontSize_export_definition, + "font-size": fontSize_export_definition, + fontStyle: fontStyle_export_definition, + "font-style": fontStyle_export_definition, + fontVariant: fontVariant_export_definition, + "font-variant": fontVariant_export_definition, + fontWeight: fontWeight_export_definition, + "font-weight": fontWeight_export_definition, + lineHeight: lineHeight_export_definition, + "line-height": lineHeight_export_definition, + font: font_export_definition, + height: height_export_definition, + left: left_export_definition, + lightingColor: lightingColor_export_definition, + "lighting-color": lightingColor_export_definition, + margin: margin_export_definition, + marginBottom: marginBottom_export_definition, + "margin-bottom": marginBottom_export_definition, + marginLeft: marginLeft_export_definition, + "margin-left": marginLeft_export_definition, + marginRight: marginRight_export_definition, + "margin-right": marginRight_export_definition, + marginTop: marginTop_export_definition, + "margin-top": marginTop_export_definition, + opacity: opacity_export_definition, + outlineColor: outlineColor_export_definition, + "outline-color": outlineColor_export_definition, + padding: padding_export_definition, + paddingBottom: paddingBottom_export_definition, + "padding-bottom": paddingBottom_export_definition, + paddingLeft: paddingLeft_export_definition, + "padding-left": paddingLeft_export_definition, + paddingRight: paddingRight_export_definition, + "padding-right": paddingRight_export_definition, + paddingTop: paddingTop_export_definition, + "padding-top": paddingTop_export_definition, + right: right_export_definition, + stopColor: stopColor_export_definition, + "stop-color": stopColor_export_definition, + textLineThroughColor: textLineThroughColor_export_definition, + "text-line-through-color": textLineThroughColor_export_definition, + textOverlineColor: textOverlineColor_export_definition, + "text-overline-color": textOverlineColor_export_definition, + textUnderlineColor: textUnderlineColor_export_definition, + "text-underline-color": textUnderlineColor_export_definition, + top: top_export_definition, + webkitBorderAfterColor: webkitBorderAfterColor_export_definition, + "webkit-border-after-color": webkitBorderAfterColor_export_definition, + webkitBorderBeforeColor: webkitBorderBeforeColor_export_definition, + "webkit-border-before-color": webkitBorderBeforeColor_export_definition, + webkitBorderEndColor: webkitBorderEndColor_export_definition, + "webkit-border-end-color": webkitBorderEndColor_export_definition, + webkitBorderStartColor: webkitBorderStartColor_export_definition, + "webkit-border-start-color": webkitBorderStartColor_export_definition, + webkitColumnRuleColor: webkitColumnRuleColor_export_definition, + "webkit-column-rule-color": webkitColumnRuleColor_export_definition, + webkitMatchNearestMailBlockquoteColor: webkitMatchNearestMailBlockquoteColor_export_definition, + "webkit-match-nearest-mail-blockquote-color": webkitMatchNearestMailBlockquoteColor_export_definition, + webkitTapHighlightColor: webkitTapHighlightColor_export_definition, + "webkit-tap-highlight-color": webkitTapHighlightColor_export_definition, + webkitTextEmphasisColor: webkitTextEmphasisColor_export_definition, + "webkit-text-emphasis-color": webkitTextEmphasisColor_export_definition, + webkitTextFillColor: webkitTextFillColor_export_definition, + "webkit-text-fill-color": webkitTextFillColor_export_definition, + webkitTextStrokeColor: webkitTextStrokeColor_export_definition, + "webkit-text-stroke-color": webkitTextStrokeColor_export_definition, + width: width_export_definition + }); +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/azimuth.js b/docs/js/node_modules/cssstyle/lib/properties/azimuth.js new file mode 100644 index 000000000..f23a68df6 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/azimuth.js @@ -0,0 +1,67 @@ +'use strict'; + +var parsers = require('../parsers'); + +module.exports.definition = { + set: function(v) { + var valueType = parsers.valueType(v); + if (valueType === parsers.TYPES.ANGLE) { + return this._setProperty('azimuth', parsers.parseAngle(v)); + } + if (valueType === parsers.TYPES.KEYWORD) { + var keywords = v + .toLowerCase() + .trim() + .split(/\s+/); + var hasBehind = false; + if (keywords.length > 2) { + return; + } + var behindIndex = keywords.indexOf('behind'); + hasBehind = behindIndex !== -1; + + if (keywords.length === 2) { + if (!hasBehind) { + return; + } + keywords.splice(behindIndex, 1); + } + if (keywords[0] === 'leftwards' || keywords[0] === 'rightwards') { + if (hasBehind) { + return; + } + return this._setProperty('azimuth', keywords[0]); + } + if (keywords[0] === 'behind') { + return this._setProperty('azimuth', '180deg'); + } + switch (keywords[0]) { + case 'left-side': + return this._setProperty('azimuth', '270deg'); + case 'far-left': + return this._setProperty('azimuth', (hasBehind ? 240 : 300) + 'deg'); + case 'left': + return this._setProperty('azimuth', (hasBehind ? 220 : 320) + 'deg'); + case 'center-left': + return this._setProperty('azimuth', (hasBehind ? 200 : 340) + 'deg'); + case 'center': + return this._setProperty('azimuth', (hasBehind ? 180 : 0) + 'deg'); + case 'center-right': + return this._setProperty('azimuth', (hasBehind ? 160 : 20) + 'deg'); + case 'right': + return this._setProperty('azimuth', (hasBehind ? 140 : 40) + 'deg'); + case 'far-right': + return this._setProperty('azimuth', (hasBehind ? 120 : 60) + 'deg'); + case 'right-side': + return this._setProperty('azimuth', '90deg'); + default: + return; + } + } + }, + get: function() { + return this.getPropertyValue('azimuth'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/background.js b/docs/js/node_modules/cssstyle/lib/properties/background.js new file mode 100644 index 000000000..b843e0c7e --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/background.js @@ -0,0 +1,19 @@ +'use strict'; + +var shorthandSetter = require('../parsers').shorthandSetter; +var shorthandGetter = require('../parsers').shorthandGetter; + +var shorthand_for = { + 'background-color': require('./backgroundColor'), + 'background-image': require('./backgroundImage'), + 'background-repeat': require('./backgroundRepeat'), + 'background-attachment': require('./backgroundAttachment'), + 'background-position': require('./backgroundPosition'), +}; + +module.exports.definition = { + set: shorthandSetter('background', shorthand_for), + get: shorthandGetter('background', shorthand_for), + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/backgroundAttachment.js b/docs/js/node_modules/cssstyle/lib/properties/backgroundAttachment.js new file mode 100644 index 000000000..98c8f76b7 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/backgroundAttachment.js @@ -0,0 +1,24 @@ +'use strict'; + +var parsers = require('../parsers'); + +var isValid = (module.exports.isValid = function isValid(v) { + return ( + parsers.valueType(v) === parsers.TYPES.KEYWORD && + (v.toLowerCase() === 'scroll' || v.toLowerCase() === 'fixed' || v.toLowerCase() === 'inherit') + ); +}); + +module.exports.definition = { + set: function(v) { + if (!isValid(v)) { + return; + } + this._setProperty('background-attachment', v); + }, + get: function() { + return this.getPropertyValue('background-attachment'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/backgroundColor.js b/docs/js/node_modules/cssstyle/lib/properties/backgroundColor.js new file mode 100644 index 000000000..5cee7176f --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/backgroundColor.js @@ -0,0 +1,36 @@ +'use strict'; + +var parsers = require('../parsers'); + +var parse = function parse(v) { + var parsed = parsers.parseColor(v); + if (parsed !== undefined) { + return parsed; + } + if ( + parsers.valueType(v) === parsers.TYPES.KEYWORD && + (v.toLowerCase() === 'transparent' || v.toLowerCase() === 'inherit') + ) { + return v; + } + return undefined; +}; + +module.exports.isValid = function isValid(v) { + return parse(v) !== undefined; +}; + +module.exports.definition = { + set: function(v) { + var parsed = parse(v); + if (parsed === undefined) { + return; + } + this._setProperty('background-color', parsed); + }, + get: function() { + return this.getPropertyValue('background-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/backgroundImage.js b/docs/js/node_modules/cssstyle/lib/properties/backgroundImage.js new file mode 100644 index 000000000..b6479a1f3 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/backgroundImage.js @@ -0,0 +1,32 @@ +'use strict'; + +var parsers = require('../parsers'); + +var parse = function parse(v) { + var parsed = parsers.parseUrl(v); + if (parsed !== undefined) { + return parsed; + } + if ( + parsers.valueType(v) === parsers.TYPES.KEYWORD && + (v.toLowerCase() === 'none' || v.toLowerCase() === 'inherit') + ) { + return v; + } + return undefined; +}; + +module.exports.isValid = function isValid(v) { + return parse(v) !== undefined; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('background-image', parse(v)); + }, + get: function() { + return this.getPropertyValue('background-image'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/backgroundPosition.js b/docs/js/node_modules/cssstyle/lib/properties/backgroundPosition.js new file mode 100644 index 000000000..4405fe6fe --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/backgroundPosition.js @@ -0,0 +1,58 @@ +'use strict'; + +var parsers = require('../parsers'); + +var valid_keywords = ['top', 'center', 'bottom', 'left', 'right']; + +var parse = function parse(v) { + if (v === '' || v === null) { + return undefined; + } + var parts = v.split(/\s+/); + if (parts.length > 2 || parts.length < 1) { + return undefined; + } + var types = []; + parts.forEach(function(part, index) { + types[index] = parsers.valueType(part); + }); + if (parts.length === 1) { + if (types[0] === parsers.TYPES.LENGTH || types[0] === parsers.TYPES.PERCENT) { + return v; + } + if (types[0] === parsers.TYPES.KEYWORD) { + if (valid_keywords.indexOf(v.toLowerCase()) !== -1 || v.toLowerCase() === 'inherit') { + return v; + } + } + return undefined; + } + if ( + (types[0] === parsers.TYPES.LENGTH || types[0] === parsers.TYPES.PERCENT) && + (types[1] === parsers.TYPES.LENGTH || types[1] === parsers.TYPES.PERCENT) + ) { + return v; + } + if (types[0] !== parsers.TYPES.KEYWORD || types[1] !== parsers.TYPES.KEYWORD) { + return undefined; + } + if (valid_keywords.indexOf(parts[0]) !== -1 && valid_keywords.indexOf(parts[1]) !== -1) { + return v; + } + return undefined; +}; + +module.exports.isValid = function isValid(v) { + return parse(v) !== undefined; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('background-position', parse(v)); + }, + get: function() { + return this.getPropertyValue('background-position'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/backgroundRepeat.js b/docs/js/node_modules/cssstyle/lib/properties/backgroundRepeat.js new file mode 100644 index 000000000..379ade0cb --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/backgroundRepeat.js @@ -0,0 +1,32 @@ +'use strict'; + +var parsers = require('../parsers'); + +var parse = function parse(v) { + if ( + parsers.valueType(v) === parsers.TYPES.KEYWORD && + (v.toLowerCase() === 'repeat' || + v.toLowerCase() === 'repeat-x' || + v.toLowerCase() === 'repeat-y' || + v.toLowerCase() === 'no-repeat' || + v.toLowerCase() === 'inherit') + ) { + return v; + } + return undefined; +}; + +module.exports.isValid = function isValid(v) { + return parse(v) !== undefined; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('background-repeat', parse(v)); + }, + get: function() { + return this.getPropertyValue('background-repeat'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/border.js b/docs/js/node_modules/cssstyle/lib/properties/border.js new file mode 100644 index 000000000..bf5b5d649 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/border.js @@ -0,0 +1,33 @@ +'use strict'; + +var shorthandSetter = require('../parsers').shorthandSetter; +var shorthandGetter = require('../parsers').shorthandGetter; + +var shorthand_for = { + 'border-width': require('./borderWidth'), + 'border-style': require('./borderStyle'), + 'border-color': require('./borderColor'), +}; + +var myShorthandSetter = shorthandSetter('border', shorthand_for); +var myShorthandGetter = shorthandGetter('border', shorthand_for); + +module.exports.definition = { + set: function(v) { + if (v.toString().toLowerCase() === 'none') { + v = ''; + } + myShorthandSetter.call(this, v); + this.removeProperty('border-top'); + this.removeProperty('border-left'); + this.removeProperty('border-right'); + this.removeProperty('border-bottom'); + this._values['border-top'] = this._values.border; + this._values['border-left'] = this._values.border; + this._values['border-right'] = this._values.border; + this._values['border-bottom'] = this._values.border; + }, + get: myShorthandGetter, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderBottom.js b/docs/js/node_modules/cssstyle/lib/properties/borderBottom.js new file mode 100644 index 000000000..aae2e5f7c --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderBottom.js @@ -0,0 +1,17 @@ +'use strict'; + +var shorthandSetter = require('../parsers').shorthandSetter; +var shorthandGetter = require('../parsers').shorthandGetter; + +var shorthand_for = { + 'border-bottom-width': require('./borderBottomWidth'), + 'border-bottom-style': require('./borderBottomStyle'), + 'border-bottom-color': require('./borderBottomColor'), +}; + +module.exports.definition = { + set: shorthandSetter('border-bottom', shorthand_for), + get: shorthandGetter('border-bottom', shorthand_for), + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderBottomColor.js b/docs/js/node_modules/cssstyle/lib/properties/borderBottomColor.js new file mode 100644 index 000000000..da5a4b560 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderBottomColor.js @@ -0,0 +1,16 @@ +'use strict'; + +var isValid = (module.exports.isValid = require('./borderColor').isValid); + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + this._setProperty('border-bottom-color', v); + } + }, + get: function() { + return this.getPropertyValue('border-bottom-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderBottomStyle.js b/docs/js/node_modules/cssstyle/lib/properties/borderBottomStyle.js new file mode 100644 index 000000000..35c44f058 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderBottomStyle.js @@ -0,0 +1,21 @@ +'use strict'; + +var isValid = require('./borderStyle').isValid; +module.exports.isValid = isValid; + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + if (v.toLowerCase() === 'none') { + v = ''; + this.removeProperty('border-bottom-width'); + } + this._setProperty('border-bottom-style', v); + } + }, + get: function() { + return this.getPropertyValue('border-bottom-style'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderBottomWidth.js b/docs/js/node_modules/cssstyle/lib/properties/borderBottomWidth.js new file mode 100644 index 000000000..db2e3b87b --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderBottomWidth.js @@ -0,0 +1,16 @@ +'use strict'; + +var isValid = (module.exports.isValid = require('./borderWidth').isValid); + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + this._setProperty('border-bottom-width', v); + } + }, + get: function() { + return this.getPropertyValue('border-bottom-width'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderCollapse.js b/docs/js/node_modules/cssstyle/lib/properties/borderCollapse.js new file mode 100644 index 000000000..49bdeb069 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderCollapse.js @@ -0,0 +1,26 @@ +'use strict'; + +var parsers = require('../parsers'); + +var parse = function parse(v) { + if ( + parsers.valueType(v) === parsers.TYPES.KEYWORD && + (v.toLowerCase() === 'collapse' || + v.toLowerCase() === 'separate' || + v.toLowerCase() === 'inherit') + ) { + return v; + } + return undefined; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('border-collapse', parse(v)); + }, + get: function() { + return this.getPropertyValue('border-collapse'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderColor.js b/docs/js/node_modules/cssstyle/lib/properties/borderColor.js new file mode 100644 index 000000000..6605e0713 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderColor.js @@ -0,0 +1,30 @@ +'use strict'; + +var parsers = require('../parsers'); +var implicitSetter = require('../parsers').implicitSetter; + +module.exports.isValid = function parse(v) { + if (typeof v !== 'string') { + return false; + } + return ( + v === '' || v.toLowerCase() === 'transparent' || parsers.valueType(v) === parsers.TYPES.COLOR + ); +}; +var isValid = module.exports.isValid; + +var parser = function(v) { + if (isValid(v)) { + return v.toLowerCase(); + } + return undefined; +}; + +module.exports.definition = { + set: implicitSetter('border', 'color', isValid, parser), + get: function() { + return this.getPropertyValue('border-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderLeft.js b/docs/js/node_modules/cssstyle/lib/properties/borderLeft.js new file mode 100644 index 000000000..a05945e99 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderLeft.js @@ -0,0 +1,17 @@ +'use strict'; + +var shorthandSetter = require('../parsers').shorthandSetter; +var shorthandGetter = require('../parsers').shorthandGetter; + +var shorthand_for = { + 'border-left-width': require('./borderLeftWidth'), + 'border-left-style': require('./borderLeftStyle'), + 'border-left-color': require('./borderLeftColor'), +}; + +module.exports.definition = { + set: shorthandSetter('border-left', shorthand_for), + get: shorthandGetter('border-left', shorthand_for), + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderLeftColor.js b/docs/js/node_modules/cssstyle/lib/properties/borderLeftColor.js new file mode 100644 index 000000000..eb3f2735c --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderLeftColor.js @@ -0,0 +1,16 @@ +'use strict'; + +var isValid = (module.exports.isValid = require('./borderColor').isValid); + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + this._setProperty('border-left-color', v); + } + }, + get: function() { + return this.getPropertyValue('border-left-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderLeftStyle.js b/docs/js/node_modules/cssstyle/lib/properties/borderLeftStyle.js new file mode 100644 index 000000000..5e8a11335 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderLeftStyle.js @@ -0,0 +1,21 @@ +'use strict'; + +var isValid = require('./borderStyle').isValid; +module.exports.isValid = isValid; + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + if (v.toLowerCase() === 'none') { + v = ''; + this.removeProperty('border-left-width'); + } + this._setProperty('border-left-style', v); + } + }, + get: function() { + return this.getPropertyValue('border-left-style'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderLeftWidth.js b/docs/js/node_modules/cssstyle/lib/properties/borderLeftWidth.js new file mode 100644 index 000000000..8c680b10c --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderLeftWidth.js @@ -0,0 +1,16 @@ +'use strict'; + +var isValid = (module.exports.isValid = require('./borderWidth').isValid); + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + this._setProperty('border-left-width', v); + } + }, + get: function() { + return this.getPropertyValue('border-left-width'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderRight.js b/docs/js/node_modules/cssstyle/lib/properties/borderRight.js new file mode 100644 index 000000000..17e26df96 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderRight.js @@ -0,0 +1,17 @@ +'use strict'; + +var shorthandSetter = require('../parsers').shorthandSetter; +var shorthandGetter = require('../parsers').shorthandGetter; + +var shorthand_for = { + 'border-right-width': require('./borderRightWidth'), + 'border-right-style': require('./borderRightStyle'), + 'border-right-color': require('./borderRightColor'), +}; + +module.exports.definition = { + set: shorthandSetter('border-right', shorthand_for), + get: shorthandGetter('border-right', shorthand_for), + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderRightColor.js b/docs/js/node_modules/cssstyle/lib/properties/borderRightColor.js new file mode 100644 index 000000000..7c188f248 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderRightColor.js @@ -0,0 +1,16 @@ +'use strict'; + +var isValid = (module.exports.isValid = require('./borderColor').isValid); + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + this._setProperty('border-right-color', v); + } + }, + get: function() { + return this.getPropertyValue('border-right-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderRightStyle.js b/docs/js/node_modules/cssstyle/lib/properties/borderRightStyle.js new file mode 100644 index 000000000..68e820930 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderRightStyle.js @@ -0,0 +1,21 @@ +'use strict'; + +var isValid = require('./borderStyle').isValid; +module.exports.isValid = isValid; + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + if (v.toLowerCase() === 'none') { + v = ''; + this.removeProperty('border-right-width'); + } + this._setProperty('border-right-style', v); + } + }, + get: function() { + return this.getPropertyValue('border-right-style'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderRightWidth.js b/docs/js/node_modules/cssstyle/lib/properties/borderRightWidth.js new file mode 100644 index 000000000..d1090d717 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderRightWidth.js @@ -0,0 +1,16 @@ +'use strict'; + +var isValid = (module.exports.isValid = require('./borderWidth').isValid); + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + this._setProperty('border-right-width', v); + } + }, + get: function() { + return this.getPropertyValue('border-right-width'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderSpacing.js b/docs/js/node_modules/cssstyle/lib/properties/borderSpacing.js new file mode 100644 index 000000000..ff1ce882e --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderSpacing.js @@ -0,0 +1,41 @@ +'use strict'; + +var parsers = require('../parsers'); + +// ? | inherit +// if one, it applies to both horizontal and verical spacing +// if two, the first applies to the horizontal and the second applies to vertical spacing + +var parse = function parse(v) { + if (v === '' || v === null) { + return undefined; + } + if (v === 0) { + return '0px'; + } + if (v.toLowerCase() === 'inherit') { + return v; + } + var parts = v.split(/\s+/); + if (parts.length !== 1 && parts.length !== 2) { + return undefined; + } + parts.forEach(function(part) { + if (parsers.valueType(part) !== parsers.TYPES.LENGTH) { + return undefined; + } + }); + + return v; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('border-spacing', parse(v)); + }, + get: function() { + return this.getPropertyValue('border-spacing'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderStyle.js b/docs/js/node_modules/cssstyle/lib/properties/borderStyle.js new file mode 100644 index 000000000..6e3e674f1 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderStyle.js @@ -0,0 +1,38 @@ +'use strict'; + +var implicitSetter = require('../parsers').implicitSetter; + +// the valid border-styles: +var styles = [ + 'none', + 'hidden', + 'dotted', + 'dashed', + 'solid', + 'double', + 'groove', + 'ridge', + 'inset', + 'outset', +]; + +module.exports.isValid = function parse(v) { + return typeof v === 'string' && (v === '' || styles.indexOf(v) !== -1); +}; +var isValid = module.exports.isValid; + +var parser = function(v) { + if (isValid(v)) { + return v.toLowerCase(); + } + return undefined; +}; + +module.exports.definition = { + set: implicitSetter('border', 'style', isValid, parser), + get: function() { + return this.getPropertyValue('border-style'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderTop.js b/docs/js/node_modules/cssstyle/lib/properties/borderTop.js new file mode 100644 index 000000000..c56d592d3 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderTop.js @@ -0,0 +1,17 @@ +'use strict'; + +var shorthandSetter = require('../parsers').shorthandSetter; +var shorthandGetter = require('../parsers').shorthandGetter; + +var shorthand_for = { + 'border-top-width': require('./borderTopWidth'), + 'border-top-style': require('./borderTopStyle'), + 'border-top-color': require('./borderTopColor'), +}; + +module.exports.definition = { + set: shorthandSetter('border-top', shorthand_for), + get: shorthandGetter('border-top', shorthand_for), + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderTopColor.js b/docs/js/node_modules/cssstyle/lib/properties/borderTopColor.js new file mode 100644 index 000000000..cc353924a --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderTopColor.js @@ -0,0 +1,16 @@ +'use strict'; + +var isValid = (module.exports.isValid = require('./borderColor').isValid); + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + this._setProperty('border-top-color', v); + } + }, + get: function() { + return this.getPropertyValue('border-top-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderTopStyle.js b/docs/js/node_modules/cssstyle/lib/properties/borderTopStyle.js new file mode 100644 index 000000000..938ea408f --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderTopStyle.js @@ -0,0 +1,21 @@ +'use strict'; + +var isValid = require('./borderStyle').isValid; +module.exports.isValid = isValid; + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + if (v.toLowerCase() === 'none') { + v = ''; + this.removeProperty('border-top-width'); + } + this._setProperty('border-top-style', v); + } + }, + get: function() { + return this.getPropertyValue('border-top-style'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderTopWidth.js b/docs/js/node_modules/cssstyle/lib/properties/borderTopWidth.js new file mode 100644 index 000000000..940725351 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderTopWidth.js @@ -0,0 +1,17 @@ +'use strict'; + +var isValid = require('./borderWidth').isValid; +module.exports.isValid = isValid; + +module.exports.definition = { + set: function(v) { + if (isValid(v)) { + this._setProperty('border-top-width', v); + } + }, + get: function() { + return this.getPropertyValue('border-top-width'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/borderWidth.js b/docs/js/node_modules/cssstyle/lib/properties/borderWidth.js new file mode 100644 index 000000000..2b6d87183 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/borderWidth.js @@ -0,0 +1,46 @@ +'use strict'; + +var parsers = require('../parsers'); +var implicitSetter = require('../parsers').implicitSetter; + +// the valid border-widths: +var widths = ['thin', 'medium', 'thick']; + +module.exports.isValid = function parse(v) { + var length = parsers.parseLength(v); + if (length !== undefined) { + return true; + } + if (typeof v !== 'string') { + return false; + } + if (v === '') { + return true; + } + v = v.toLowerCase(); + if (widths.indexOf(v) === -1) { + return false; + } + return true; +}; +var isValid = module.exports.isValid; + +var parser = function(v) { + var length = parsers.parseLength(v); + if (length !== undefined) { + return length; + } + if (isValid(v)) { + return v.toLowerCase(); + } + return undefined; +}; + +module.exports.definition = { + set: implicitSetter('border', 'width', isValid, parser), + get: function() { + return this.getPropertyValue('border-width'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/bottom.js b/docs/js/node_modules/cssstyle/lib/properties/bottom.js new file mode 100644 index 000000000..e9d72b221 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/bottom.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseMeasurement = require('../parsers').parseMeasurement; + +module.exports.definition = { + set: function(v) { + this._setProperty('bottom', parseMeasurement(v)); + }, + get: function() { + return this.getPropertyValue('bottom'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/clear.js b/docs/js/node_modules/cssstyle/lib/properties/clear.js new file mode 100644 index 000000000..22d980290 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/clear.js @@ -0,0 +1,16 @@ +'use strict'; + +var parseKeyword = require('../parsers').parseKeyword; + +var clear_keywords = ['none', 'left', 'right', 'both', 'inherit']; + +module.exports.definition = { + set: function(v) { + this._setProperty('clear', parseKeyword(v, clear_keywords)); + }, + get: function() { + return this.getPropertyValue('clear'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/clip.js b/docs/js/node_modules/cssstyle/lib/properties/clip.js new file mode 100644 index 000000000..91ba675e9 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/clip.js @@ -0,0 +1,47 @@ +'use strict'; + +var parseMeasurement = require('../parsers').parseMeasurement; + +var shape_regex = /^rect\((.*)\)$/i; + +var parse = function(val) { + if (val === '' || val === null) { + return val; + } + if (typeof val !== 'string') { + return undefined; + } + val = val.toLowerCase(); + if (val === 'auto' || val === 'inherit') { + return val; + } + var matches = val.match(shape_regex); + if (!matches) { + return undefined; + } + var parts = matches[1].split(/\s*,\s*/); + if (parts.length !== 4) { + return undefined; + } + var valid = parts.every(function(part, index) { + var measurement = parseMeasurement(part); + parts[index] = measurement; + return measurement !== undefined; + }); + if (!valid) { + return undefined; + } + parts = parts.join(', '); + return val.replace(matches[1], parts); +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('clip', parse(v)); + }, + get: function() { + return this.getPropertyValue('clip'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/color.js b/docs/js/node_modules/cssstyle/lib/properties/color.js new file mode 100644 index 000000000..1b5ca3dcd --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/color.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/cssFloat.js b/docs/js/node_modules/cssstyle/lib/properties/cssFloat.js new file mode 100644 index 000000000..1c619cc7a --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/cssFloat.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports.definition = { + set: function(v) { + this._setProperty('float', v); + }, + get: function() { + return this.getPropertyValue('float'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/flex.js b/docs/js/node_modules/cssstyle/lib/properties/flex.js new file mode 100644 index 000000000..b56fd55de --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/flex.js @@ -0,0 +1,45 @@ +'use strict'; + +var shorthandParser = require('../parsers').shorthandParser; +var shorthandSetter = require('../parsers').shorthandSetter; +var shorthandGetter = require('../parsers').shorthandGetter; + +var shorthand_for = { + 'flex-grow': require('./flexGrow'), + 'flex-shrink': require('./flexShrink'), + 'flex-basis': require('./flexBasis'), +}; + +var myShorthandSetter = shorthandSetter('flex', shorthand_for); + +module.exports.isValid = function isValid(v) { + return shorthandParser(v, shorthand_for) !== undefined; +}; + +module.exports.definition = { + set: function(v) { + var normalizedValue = String(v) + .trim() + .toLowerCase(); + + if (normalizedValue === 'none') { + myShorthandSetter.call(this, '0 0 auto'); + return; + } + if (normalizedValue === 'initial') { + myShorthandSetter.call(this, '0 1 auto'); + return; + } + if (normalizedValue === 'auto') { + this.removeProperty('flex-grow'); + this.removeProperty('flex-shrink'); + this.setProperty('flex-basis', normalizedValue); + return; + } + + myShorthandSetter.call(this, v); + }, + get: shorthandGetter('flex', shorthand_for), + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/flexBasis.js b/docs/js/node_modules/cssstyle/lib/properties/flexBasis.js new file mode 100644 index 000000000..0c7cddf0e --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/flexBasis.js @@ -0,0 +1,28 @@ +'use strict'; + +var parseMeasurement = require('../parsers').parseMeasurement; + +function parse(v) { + if (String(v).toLowerCase() === 'auto') { + return 'auto'; + } + if (String(v).toLowerCase() === 'inherit') { + return 'inherit'; + } + return parseMeasurement(v); +} + +module.exports.isValid = function isValid(v) { + return parse(v) !== undefined; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('flex-basis', parse(v)); + }, + get: function() { + return this.getPropertyValue('flex-basis'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/flexGrow.js b/docs/js/node_modules/cssstyle/lib/properties/flexGrow.js new file mode 100644 index 000000000..6e2966362 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/flexGrow.js @@ -0,0 +1,19 @@ +'use strict'; + +var parseNumber = require('../parsers').parseNumber; +var POSITION_AT_SHORTHAND = require('../constants').POSITION_AT_SHORTHAND; + +module.exports.isValid = function isValid(v, positionAtFlexShorthand) { + return parseNumber(v) !== undefined && positionAtFlexShorthand === POSITION_AT_SHORTHAND.first; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('flex-grow', parseNumber(v)); + }, + get: function() { + return this.getPropertyValue('flex-grow'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/flexShrink.js b/docs/js/node_modules/cssstyle/lib/properties/flexShrink.js new file mode 100644 index 000000000..63ff86fd2 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/flexShrink.js @@ -0,0 +1,19 @@ +'use strict'; + +var parseNumber = require('../parsers').parseNumber; +var POSITION_AT_SHORTHAND = require('../constants').POSITION_AT_SHORTHAND; + +module.exports.isValid = function isValid(v, positionAtFlexShorthand) { + return parseNumber(v) !== undefined && positionAtFlexShorthand === POSITION_AT_SHORTHAND.second; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('flex-shrink', parseNumber(v)); + }, + get: function() { + return this.getPropertyValue('flex-shrink'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/float.js b/docs/js/node_modules/cssstyle/lib/properties/float.js new file mode 100644 index 000000000..1c619cc7a --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/float.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports.definition = { + set: function(v) { + this._setProperty('float', v); + }, + get: function() { + return this.getPropertyValue('float'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/floodColor.js b/docs/js/node_modules/cssstyle/lib/properties/floodColor.js new file mode 100644 index 000000000..8a4f29c69 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/floodColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('flood-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('flood-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/font.js b/docs/js/node_modules/cssstyle/lib/properties/font.js new file mode 100644 index 000000000..9492dc63c --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/font.js @@ -0,0 +1,43 @@ +'use strict'; + +var TYPES = require('../parsers').TYPES; +var valueType = require('../parsers').valueType; +var shorthandParser = require('../parsers').shorthandParser; +var shorthandSetter = require('../parsers').shorthandSetter; +var shorthandGetter = require('../parsers').shorthandGetter; + +var shorthand_for = { + 'font-family': require('./fontFamily'), + 'font-size': require('./fontSize'), + 'font-style': require('./fontStyle'), + 'font-variant': require('./fontVariant'), + 'font-weight': require('./fontWeight'), + 'line-height': require('./lineHeight'), +}; + +var static_fonts = [ + 'caption', + 'icon', + 'menu', + 'message-box', + 'small-caption', + 'status-bar', + 'inherit', +]; + +var setter = shorthandSetter('font', shorthand_for); + +module.exports.definition = { + set: function(v) { + var short = shorthandParser(v, shorthand_for); + if (short !== undefined) { + return setter.call(this, v); + } + if (valueType(v) === TYPES.KEYWORD && static_fonts.indexOf(v.toLowerCase()) !== -1) { + this._setProperty('font', v); + } + }, + get: shorthandGetter('font', shorthand_for), + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/fontFamily.js b/docs/js/node_modules/cssstyle/lib/properties/fontFamily.js new file mode 100644 index 000000000..40bd1c132 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/fontFamily.js @@ -0,0 +1,33 @@ +'use strict'; + +var TYPES = require('../parsers').TYPES; +var valueType = require('../parsers').valueType; + +var partsRegEx = /\s*,\s*/; +module.exports.isValid = function isValid(v) { + if (v === '' || v === null) { + return true; + } + var parts = v.split(partsRegEx); + var len = parts.length; + var i; + var type; + for (i = 0; i < len; i++) { + type = valueType(parts[i]); + if (type === TYPES.STRING || type === TYPES.KEYWORD) { + return true; + } + } + return false; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('font-family', v); + }, + get: function() { + return this.getPropertyValue('font-family'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/fontSize.js b/docs/js/node_modules/cssstyle/lib/properties/fontSize.js new file mode 100644 index 000000000..287c82a80 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/fontSize.js @@ -0,0 +1,28 @@ +'use strict'; + +var TYPES = require('../parsers').TYPES; +var valueType = require('../parsers').valueType; + +var absoluteSizes = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large']; +var relativeSizes = ['larger', 'smaller']; + +module.exports.isValid = function(v) { + var type = valueType(v.toLowerCase()); + return ( + type === TYPES.LENGTH || + type === TYPES.PERCENT || + (type === TYPES.KEYWORD && absoluteSizes.indexOf(v.toLowerCase()) !== -1) || + (type === TYPES.KEYWORD && relativeSizes.indexOf(v.toLowerCase()) !== -1) + ); +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('font-size', v); + }, + get: function() { + return this.getPropertyValue('font-size'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/fontStyle.js b/docs/js/node_modules/cssstyle/lib/properties/fontStyle.js new file mode 100644 index 000000000..63d5e921f --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/fontStyle.js @@ -0,0 +1,18 @@ +'use strict'; + +var valid_styles = ['normal', 'italic', 'oblique', 'inherit']; + +module.exports.isValid = function(v) { + return valid_styles.indexOf(v.toLowerCase()) !== -1; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('font-style', v); + }, + get: function() { + return this.getPropertyValue('font-style'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/fontVariant.js b/docs/js/node_modules/cssstyle/lib/properties/fontVariant.js new file mode 100644 index 000000000..f03b5ea22 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/fontVariant.js @@ -0,0 +1,18 @@ +'use strict'; + +var valid_variants = ['normal', 'small-caps', 'inherit']; + +module.exports.isValid = function isValid(v) { + return valid_variants.indexOf(v.toLowerCase()) !== -1; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('font-variant', v); + }, + get: function() { + return this.getPropertyValue('font-variant'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/fontWeight.js b/docs/js/node_modules/cssstyle/lib/properties/fontWeight.js new file mode 100644 index 000000000..b854f6ab7 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/fontWeight.js @@ -0,0 +1,33 @@ +'use strict'; + +var valid_weights = [ + 'normal', + 'bold', + 'bolder', + 'lighter', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900', + 'inherit', +]; + +module.exports.isValid = function isValid(v) { + return valid_weights.indexOf(v.toLowerCase()) !== -1; +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('font-weight', v); + }, + get: function() { + return this.getPropertyValue('font-weight'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/height.js b/docs/js/node_modules/cssstyle/lib/properties/height.js new file mode 100644 index 000000000..82543c05d --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/height.js @@ -0,0 +1,24 @@ +'use strict'; + +var parseMeasurement = require('../parsers').parseMeasurement; + +function parse(v) { + if (String(v).toLowerCase() === 'auto') { + return 'auto'; + } + if (String(v).toLowerCase() === 'inherit') { + return 'inherit'; + } + return parseMeasurement(v); +} + +module.exports.definition = { + set: function(v) { + this._setProperty('height', parse(v)); + }, + get: function() { + return this.getPropertyValue('height'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/left.js b/docs/js/node_modules/cssstyle/lib/properties/left.js new file mode 100644 index 000000000..72bb2fafd --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/left.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseMeasurement = require('../parsers').parseMeasurement; + +module.exports.definition = { + set: function(v) { + this._setProperty('left', parseMeasurement(v)); + }, + get: function() { + return this.getPropertyValue('left'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/lightingColor.js b/docs/js/node_modules/cssstyle/lib/properties/lightingColor.js new file mode 100644 index 000000000..9f9643df4 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/lightingColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('lighting-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('lighting-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/lineHeight.js b/docs/js/node_modules/cssstyle/lib/properties/lineHeight.js new file mode 100644 index 000000000..6f7a037f6 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/lineHeight.js @@ -0,0 +1,26 @@ +'use strict'; + +var TYPES = require('../parsers').TYPES; +var valueType = require('../parsers').valueType; + +module.exports.isValid = function isValid(v) { + var type = valueType(v); + return ( + (type === TYPES.KEYWORD && v.toLowerCase() === 'normal') || + v.toLowerCase() === 'inherit' || + type === TYPES.NUMBER || + type === TYPES.LENGTH || + type === TYPES.PERCENT + ); +}; + +module.exports.definition = { + set: function(v) { + this._setProperty('line-height', v); + }, + get: function() { + return this.getPropertyValue('line-height'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/margin.js b/docs/js/node_modules/cssstyle/lib/properties/margin.js new file mode 100644 index 000000000..2a8f972bd --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/margin.js @@ -0,0 +1,68 @@ +'use strict'; + +var parsers = require('../parsers.js'); +var TYPES = parsers.TYPES; + +var isValid = function(v) { + if (v.toLowerCase() === 'auto') { + return true; + } + var type = parsers.valueType(v); + return ( + type === TYPES.LENGTH || + type === TYPES.PERCENT || + (type === TYPES.INTEGER && (v === '0' || v === 0)) + ); +}; + +var parser = function(v) { + var V = v.toLowerCase(); + if (V === 'auto') { + return V; + } + return parsers.parseMeasurement(v); +}; + +var mySetter = parsers.implicitSetter('margin', '', isValid, parser); +var myGlobal = parsers.implicitSetter( + 'margin', + '', + function() { + return true; + }, + function(v) { + return v; + } +); + +module.exports.definition = { + set: function(v) { + if (typeof v === 'number') { + v = String(v); + } + if (typeof v !== 'string') { + return; + } + var V = v.toLowerCase(); + switch (V) { + case 'inherit': + case 'initial': + case 'unset': + case '': + myGlobal.call(this, V); + break; + + default: + mySetter.call(this, v); + break; + } + }, + get: function() { + return this.getPropertyValue('margin'); + }, + enumerable: true, + configurable: true, +}; + +module.exports.isValid = isValid; +module.exports.parser = parser; diff --git a/docs/js/node_modules/cssstyle/lib/properties/marginBottom.js b/docs/js/node_modules/cssstyle/lib/properties/marginBottom.js new file mode 100644 index 000000000..378172e24 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/marginBottom.js @@ -0,0 +1,13 @@ +'use strict'; + +var margin = require('./margin.js'); +var parsers = require('../parsers.js'); + +module.exports.definition = { + set: parsers.subImplicitSetter('margin', 'bottom', margin.isValid, margin.parser), + get: function() { + return this.getPropertyValue('margin-bottom'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/marginLeft.js b/docs/js/node_modules/cssstyle/lib/properties/marginLeft.js new file mode 100644 index 000000000..0c67317be --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/marginLeft.js @@ -0,0 +1,13 @@ +'use strict'; + +var margin = require('./margin.js'); +var parsers = require('../parsers.js'); + +module.exports.definition = { + set: parsers.subImplicitSetter('margin', 'left', margin.isValid, margin.parser), + get: function() { + return this.getPropertyValue('margin-left'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/marginRight.js b/docs/js/node_modules/cssstyle/lib/properties/marginRight.js new file mode 100644 index 000000000..6cdf26bd2 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/marginRight.js @@ -0,0 +1,13 @@ +'use strict'; + +var margin = require('./margin.js'); +var parsers = require('../parsers.js'); + +module.exports.definition = { + set: parsers.subImplicitSetter('margin', 'right', margin.isValid, margin.parser), + get: function() { + return this.getPropertyValue('margin-right'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/marginTop.js b/docs/js/node_modules/cssstyle/lib/properties/marginTop.js new file mode 100644 index 000000000..6a57621b3 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/marginTop.js @@ -0,0 +1,13 @@ +'use strict'; + +var margin = require('./margin.js'); +var parsers = require('../parsers.js'); + +module.exports.definition = { + set: parsers.subImplicitSetter('margin', 'top', margin.isValid, margin.parser), + get: function() { + return this.getPropertyValue('margin-top'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/opacity.js b/docs/js/node_modules/cssstyle/lib/properties/opacity.js new file mode 100644 index 000000000..b26a3b68d --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/opacity.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseNumber = require('../parsers').parseNumber; + +module.exports.definition = { + set: function(v) { + this._setProperty('opacity', parseNumber(v)); + }, + get: function() { + return this.getPropertyValue('opacity'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/outlineColor.js b/docs/js/node_modules/cssstyle/lib/properties/outlineColor.js new file mode 100644 index 000000000..fc8093dfd --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/outlineColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('outline-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('outline-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/padding.js b/docs/js/node_modules/cssstyle/lib/properties/padding.js new file mode 100644 index 000000000..1287b19ec --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/padding.js @@ -0,0 +1,61 @@ +'use strict'; + +var parsers = require('../parsers.js'); +var TYPES = parsers.TYPES; + +var isValid = function(v) { + var type = parsers.valueType(v); + return ( + type === TYPES.LENGTH || + type === TYPES.PERCENT || + (type === TYPES.INTEGER && (v === '0' || v === 0)) + ); +}; + +var parser = function(v) { + return parsers.parseMeasurement(v); +}; + +var mySetter = parsers.implicitSetter('padding', '', isValid, parser); +var myGlobal = parsers.implicitSetter( + 'padding', + '', + function() { + return true; + }, + function(v) { + return v; + } +); + +module.exports.definition = { + set: function(v) { + if (typeof v === 'number') { + v = String(v); + } + if (typeof v !== 'string') { + return; + } + var V = v.toLowerCase(); + switch (V) { + case 'inherit': + case 'initial': + case 'unset': + case '': + myGlobal.call(this, V); + break; + + default: + mySetter.call(this, v); + break; + } + }, + get: function() { + return this.getPropertyValue('padding'); + }, + enumerable: true, + configurable: true, +}; + +module.exports.isValid = isValid; +module.exports.parser = parser; diff --git a/docs/js/node_modules/cssstyle/lib/properties/paddingBottom.js b/docs/js/node_modules/cssstyle/lib/properties/paddingBottom.js new file mode 100644 index 000000000..3ce88e576 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/paddingBottom.js @@ -0,0 +1,13 @@ +'use strict'; + +var padding = require('./padding.js'); +var parsers = require('../parsers.js'); + +module.exports.definition = { + set: parsers.subImplicitSetter('padding', 'bottom', padding.isValid, padding.parser), + get: function() { + return this.getPropertyValue('padding-bottom'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/paddingLeft.js b/docs/js/node_modules/cssstyle/lib/properties/paddingLeft.js new file mode 100644 index 000000000..04363385f --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/paddingLeft.js @@ -0,0 +1,13 @@ +'use strict'; + +var padding = require('./padding.js'); +var parsers = require('../parsers.js'); + +module.exports.definition = { + set: parsers.subImplicitSetter('padding', 'left', padding.isValid, padding.parser), + get: function() { + return this.getPropertyValue('padding-left'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/paddingRight.js b/docs/js/node_modules/cssstyle/lib/properties/paddingRight.js new file mode 100644 index 000000000..ff9bd34eb --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/paddingRight.js @@ -0,0 +1,13 @@ +'use strict'; + +var padding = require('./padding.js'); +var parsers = require('../parsers.js'); + +module.exports.definition = { + set: parsers.subImplicitSetter('padding', 'right', padding.isValid, padding.parser), + get: function() { + return this.getPropertyValue('padding-right'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/paddingTop.js b/docs/js/node_modules/cssstyle/lib/properties/paddingTop.js new file mode 100644 index 000000000..eca878168 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/paddingTop.js @@ -0,0 +1,13 @@ +'use strict'; + +var padding = require('./padding.js'); +var parsers = require('../parsers.js'); + +module.exports.definition = { + set: parsers.subImplicitSetter('padding', 'top', padding.isValid, padding.parser), + get: function() { + return this.getPropertyValue('padding-top'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/right.js b/docs/js/node_modules/cssstyle/lib/properties/right.js new file mode 100644 index 000000000..eb4c3d496 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/right.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseMeasurement = require('../parsers').parseMeasurement; + +module.exports.definition = { + set: function(v) { + this._setProperty('right', parseMeasurement(v)); + }, + get: function() { + return this.getPropertyValue('right'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/stopColor.js b/docs/js/node_modules/cssstyle/lib/properties/stopColor.js new file mode 100644 index 000000000..912d8e207 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/stopColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('stop-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('stop-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/textLineThroughColor.js b/docs/js/node_modules/cssstyle/lib/properties/textLineThroughColor.js new file mode 100644 index 000000000..ae53dbbd1 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/textLineThroughColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('text-line-through-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('text-line-through-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/textOverlineColor.js b/docs/js/node_modules/cssstyle/lib/properties/textOverlineColor.js new file mode 100644 index 000000000..c6adf7ce6 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/textOverlineColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('text-overline-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('text-overline-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/textUnderlineColor.js b/docs/js/node_modules/cssstyle/lib/properties/textUnderlineColor.js new file mode 100644 index 000000000..a243a9ca5 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/textUnderlineColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('text-underline-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('text-underline-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/top.js b/docs/js/node_modules/cssstyle/lib/properties/top.js new file mode 100644 index 000000000..f71986fea --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/top.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseMeasurement = require('../parsers').parseMeasurement; + +module.exports.definition = { + set: function(v) { + this._setProperty('top', parseMeasurement(v)); + }, + get: function() { + return this.getPropertyValue('top'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/webkitBorderAfterColor.js b/docs/js/node_modules/cssstyle/lib/properties/webkitBorderAfterColor.js new file mode 100644 index 000000000..ed021949d --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/webkitBorderAfterColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('-webkit-border-after-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('-webkit-border-after-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/webkitBorderBeforeColor.js b/docs/js/node_modules/cssstyle/lib/properties/webkitBorderBeforeColor.js new file mode 100644 index 000000000..a4507a196 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/webkitBorderBeforeColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('-webkit-border-before-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('-webkit-border-before-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/webkitBorderEndColor.js b/docs/js/node_modules/cssstyle/lib/properties/webkitBorderEndColor.js new file mode 100644 index 000000000..499545dcf --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/webkitBorderEndColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('-webkit-border-end-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('-webkit-border-end-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/webkitBorderStartColor.js b/docs/js/node_modules/cssstyle/lib/properties/webkitBorderStartColor.js new file mode 100644 index 000000000..8429e3284 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/webkitBorderStartColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('-webkit-border-start-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('-webkit-border-start-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/webkitColumnRuleColor.js b/docs/js/node_modules/cssstyle/lib/properties/webkitColumnRuleColor.js new file mode 100644 index 000000000..7130d5f19 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/webkitColumnRuleColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('-webkit-column-rule-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('-webkit-column-rule-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/webkitMatchNearestMailBlockquoteColor.js b/docs/js/node_modules/cssstyle/lib/properties/webkitMatchNearestMailBlockquoteColor.js new file mode 100644 index 000000000..e075891c5 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/webkitMatchNearestMailBlockquoteColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('-webkit-match-nearest-mail-blockquote-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('-webkit-match-nearest-mail-blockquote-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/webkitTapHighlightColor.js b/docs/js/node_modules/cssstyle/lib/properties/webkitTapHighlightColor.js new file mode 100644 index 000000000..d01932947 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/webkitTapHighlightColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('-webkit-tap-highlight-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('-webkit-tap-highlight-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/webkitTextEmphasisColor.js b/docs/js/node_modules/cssstyle/lib/properties/webkitTextEmphasisColor.js new file mode 100644 index 000000000..cdeab5356 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/webkitTextEmphasisColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('-webkit-text-emphasis-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('-webkit-text-emphasis-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/webkitTextFillColor.js b/docs/js/node_modules/cssstyle/lib/properties/webkitTextFillColor.js new file mode 100644 index 000000000..ef5bd6732 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/webkitTextFillColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('-webkit-text-fill-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('-webkit-text-fill-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/webkitTextStrokeColor.js b/docs/js/node_modules/cssstyle/lib/properties/webkitTextStrokeColor.js new file mode 100644 index 000000000..72a227793 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/webkitTextStrokeColor.js @@ -0,0 +1,14 @@ +'use strict'; + +var parseColor = require('../parsers').parseColor; + +module.exports.definition = { + set: function(v) { + this._setProperty('-webkit-text-stroke-color', parseColor(v)); + }, + get: function() { + return this.getPropertyValue('-webkit-text-stroke-color'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/properties/width.js b/docs/js/node_modules/cssstyle/lib/properties/width.js new file mode 100644 index 000000000..a8c365daa --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/properties/width.js @@ -0,0 +1,24 @@ +'use strict'; + +var parseMeasurement = require('../parsers').parseMeasurement; + +function parse(v) { + if (String(v).toLowerCase() === 'auto') { + return 'auto'; + } + if (String(v).toLowerCase() === 'inherit') { + return 'inherit'; + } + return parseMeasurement(v); +} + +module.exports.definition = { + set: function(v) { + this._setProperty('width', parse(v)); + }, + get: function() { + return this.getPropertyValue('width'); + }, + enumerable: true, + configurable: true, +}; diff --git a/docs/js/node_modules/cssstyle/lib/utils/getBasicPropertyDescriptor.js b/docs/js/node_modules/cssstyle/lib/utils/getBasicPropertyDescriptor.js new file mode 100644 index 000000000..ded2cc457 --- /dev/null +++ b/docs/js/node_modules/cssstyle/lib/utils/getBasicPropertyDescriptor.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = function getBasicPropertyDescriptor(name) { + return { + set: function(v) { + this._setProperty(name, v); + }, + get: function() { + return this.getPropertyValue(name); + }, + enumerable: true, + configurable: true, + }; +}; diff --git a/docs/js/node_modules/cssstyle/package.json b/docs/js/node_modules/cssstyle/package.json new file mode 100644 index 000000000..e6aab8b56 --- /dev/null +++ b/docs/js/node_modules/cssstyle/package.json @@ -0,0 +1,105 @@ +{ + "_from": "cssstyle@^1.0.0", + "_id": "cssstyle@1.4.0", + "_inBundle": false, + "_integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "_location": "/cssstyle", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": true, + "raw": "cssstyle@^1.0.0", + "name": "cssstyle", + "escapedName": "cssstyle", + "rawSpec": "^1.0.0", + "saveSpec": null, + "fetchSpec": "^1.0.0" + }, + "_requiredBy": [ + "/jsdom" + ], + "_resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", + "_shasum": "9d31328229d3c565c61e586b02041a28fccdccf1", + "_spec": "cssstyle@^1.0.0", + "_where": "/Users/hectorip/Education/Eloquent-JavaScript-ES/node_modules/jsdom", + "bugs": { + "url": "https://github.com/jsakas/CSSStyleDeclaration/issues" + }, + "bundleDependencies": false, + "contributors": [ + { + "name": "Chad Walker", + "email": "chad@chad-cat-lore-eddie.com", + "url": "https://github.com/chad3814" + }, + { + "name": "Rafał Ruciński", + "email": "fatfisz@gmail.com", + "url": "https://fatfisz.com" + }, + { + "name": "Nikita Vasilyev", + "email": "me@elv1s.ru" + }, + { + "name": "Davide P. Cervone" + }, + { + "name": "Forbes Lindesay" + } + ], + "dependencies": { + "cssom": "0.3.x" + }, + "deprecated": false, + "description": "CSSStyleDeclaration Object Model implementation", + "devDependencies": { + "babel-generator": "~6.26.1", + "babel-traverse": "~6.26.0", + "babel-types": "~6.26.0", + "babylon": "~6.18.0", + "eslint": "5.13.0", + "eslint-config-prettier": "4.0.0", + "eslint-plugin-prettier": "3.0.1", + "nodeunit": "~0.11.3", + "npm-run-all": "^4.1.5", + "prettier": "1.16.4", + "request": "^2.88.0", + "resolve": "~1.8.1" + }, + "directories": { + "lib": "./lib" + }, + "homepage": "https://github.com/jsakas/CSSStyleDeclaration", + "keywords": [ + "CSS", + "CSSStyleDeclaration", + "StyleSheet" + ], + "license": "MIT", + "main": "./lib/CSSStyleDeclaration.js", + "maintainers": [ + { + "name": "Jon Sakas", + "email": "jon.sakas@gmail.com", + "url": "https://jon.sakas.co/" + } + ], + "name": "cssstyle", + "repository": { + "type": "git", + "url": "git+https://github.com/jsakas/CSSStyleDeclaration.git" + }, + "scripts": { + "download": "node ./scripts/download_latest_properties.js && eslint lib/allProperties.js --fix", + "generate": "run-p generate:*", + "generate:implemented_properties": "node ./scripts/generate_implemented_properties.js", + "generate:properties": "node ./scripts/generate_properties.js", + "lint": "npm run generate && eslint . --max-warnings 0", + "lint:fix": "eslint . --fix --max-warnings 0", + "prepublishOnly": "npm run test-ci", + "test": "npm run generate && nodeunit tests", + "test-ci": "npm run lint && npm run test" + }, + "version": "1.4.0" +} diff --git a/docs/js/node_modules/cssstyle/scripts/download_latest_properties.js b/docs/js/node_modules/cssstyle/scripts/download_latest_properties.js new file mode 100644 index 000000000..5f17ef580 --- /dev/null +++ b/docs/js/node_modules/cssstyle/scripts/download_latest_properties.js @@ -0,0 +1,88 @@ +'use strict'; + +/* + * W3C provides JSON list of all CSS properties and their status in the standard + * + * documentation: https://www.w3.org/Style/CSS/all-properties.en.html + * JSON url: ( https://www.w3.org/Style/CSS/all-properties.en.json ) + * + * Download that file, filter out duplicates and filter the properties based on the wanted standard level + * + * ED - Editors' Draft (not a W3C Technical Report) + * FPWD - First Public Working Draft + * WD - Working Draft + * LC - Last Call Working Draft + * CR - Candidate Recommendation + * PR - Proposed Recommendation + * REC - Recommendation + * NOTE - Working Group Note + */ + +var fs = require('fs'); +var path = require('path'); + +var request = require('request'); + +const { camelToDashed } = require('../lib/parsers'); + +var url = 'https://www.w3.org/Style/CSS/all-properties.en.json'; + +console.log('Downloading CSS properties...'); + +function toCamelCase(propName) { + return propName.replace(/-([a-z])/g, function(g) { + return g[1].toUpperCase(); + }); +} + +request(url, function(error, response, body) { + if (!error && response.statusCode === 200) { + var allCSSProperties = JSON.parse(body); + + // Filter out all properties newer than Working Draft + var workingDraftAndOlderProperties = allCSSProperties.filter(function(cssProp) { + // TODO: --* css Needs additional logic to this module, so filter it out for now + return cssProp.status !== 'ED' && cssProp.status !== 'FPWD' && cssProp.property !== '--*'; + }); + + // Remove duplicates, there can be many properties in different states of standard + // and add only property names to the list + var CSSpropertyNames = []; + workingDraftAndOlderProperties.forEach(function(cssProp) { + const camelCaseName = toCamelCase(cssProp.property); + + if (CSSpropertyNames.indexOf(camelCaseName) === -1) { + CSSpropertyNames.push(camelCaseName); + } + }); + + var out_file = fs.createWriteStream(path.resolve(__dirname, './../lib/allProperties.js'), { + encoding: 'utf-8', + }); + + var date_today = new Date(); + out_file.write( + "'use strict';\n\n// autogenerated - " + + (date_today.getMonth() + 1 + '/' + date_today.getDate() + '/' + date_today.getFullYear()) + + '\n\n' + ); + out_file.write('/*\n *\n * https://www.w3.org/Style/CSS/all-properties.en.html\n */\n\n'); + + out_file.write('var allProperties = new Set();\n'); + out_file.write('module.exports = allProperties;\n'); + + CSSpropertyNames.forEach(function(property) { + out_file.write('allProperties.add(' + JSON.stringify(camelToDashed(property)) + ');\n'); + }); + + out_file.end(function(err) { + if (err) { + throw err; + } + + console.log('Generated ' + Object.keys(CSSpropertyNames).length + ' properties.'); + }); + } else { + throw error; + } +}); diff --git a/docs/js/node_modules/cssstyle/scripts/generate_implemented_properties.js b/docs/js/node_modules/cssstyle/scripts/generate_implemented_properties.js new file mode 100644 index 000000000..caa88f12c --- /dev/null +++ b/docs/js/node_modules/cssstyle/scripts/generate_implemented_properties.js @@ -0,0 +1,61 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const t = require('babel-types'); +const generate = require('babel-generator').default; +const camelToDashed = require('../lib/parsers').camelToDashed; + +const dashedProperties = fs + .readdirSync(path.resolve(__dirname, '../lib/properties')) + .filter(propertyFile => propertyFile.substr(-3) === '.js') + .map(propertyFile => camelToDashed(propertyFile.replace('.js', ''))); + +const out_file = fs.createWriteStream(path.resolve(__dirname, '../lib/implementedProperties.js'), { + encoding: 'utf-8', +}); +var date_today = new Date(); +out_file.write( + "'use strict';\n\n// autogenerated - " + + (date_today.getMonth() + 1 + '/' + date_today.getDate() + '/' + date_today.getFullYear()) + + '\n\n' +); +out_file.write('/*\n *\n * https://www.w3.org/Style/CSS/all-properties.en.html\n */\n\n'); + +const statements = []; +statements.push( + t.variableDeclaration('var', [ + t.variableDeclarator( + t.identifier('implementedProperties'), + t.newExpression(t.identifier('Set'), []) + ), + ]) +); + +dashedProperties.forEach(property => { + statements.push( + t.expressionStatement( + t.callExpression( + t.memberExpression(t.identifier('implementedProperties'), t.identifier('add')), + [t.stringLiteral(property)] + ) + ) + ); +}); + +statements.push( + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression(t.identifier('module'), t.identifier('exports')), + t.identifier('implementedProperties') + ) + ) +); + +out_file.write(generate(t.program(statements)).code + '\n'); +out_file.end(function(err) { + if (err) { + throw err; + } +}); diff --git a/docs/js/node_modules/cssstyle/scripts/generate_properties.js b/docs/js/node_modules/cssstyle/scripts/generate_properties.js new file mode 100644 index 000000000..33a42728b --- /dev/null +++ b/docs/js/node_modules/cssstyle/scripts/generate_properties.js @@ -0,0 +1,292 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var babylon = require('babylon'); +var t = require('babel-types'); +var generate = require('babel-generator').default; +var traverse = require('babel-traverse').default; +var resolve = require('resolve'); + +var camelToDashed = require('../lib/parsers').camelToDashed; + +var basename = path.basename; +var dirname = path.dirname; + +var uniqueIndex = 0; +function getUniqueIndex() { + return uniqueIndex++; +} + +var property_files = fs + .readdirSync(path.resolve(__dirname, '../lib/properties')) + .filter(function(property) { + return property.substr(-3) === '.js'; + }); +var out_file = fs.createWriteStream(path.resolve(__dirname, '../lib/properties.js'), { + encoding: 'utf-8', +}); +var date_today = new Date(); +out_file.write( + "'use strict';\n\n// autogenerated - " + + (date_today.getMonth() + 1 + '/' + date_today.getDate() + '/' + date_today.getFullYear()) + + '\n\n' +); +out_file.write('/*\n *\n * https://www.w3.org/Style/CSS/all-properties.en.html\n */\n\n'); + +function isModuleDotExports(node) { + return ( + t.isMemberExpression(node, { computed: false }) && + t.isIdentifier(node.object, { name: 'module' }) && + t.isIdentifier(node.property, { name: 'exports' }) + ); +} +function isRequire(node, filename) { + if ( + t.isCallExpression(node) && + t.isIdentifier(node.callee, { name: 'require' }) && + node.arguments.length === 1 && + t.isStringLiteral(node.arguments[0]) + ) { + var relative = node.arguments[0].value; + var fullPath = resolve.sync(relative, { basedir: dirname(filename) }); + return { relative: relative, fullPath: fullPath }; + } else { + return false; + } +} + +// step 1: parse all files and figure out their dependencies +var parsedFilesByPath = {}; +property_files.map(function(property) { + var filename = path.resolve(__dirname, '../lib/properties/' + property); + var src = fs.readFileSync(filename, 'utf8'); + property = basename(property, '.js'); + var ast = babylon.parse(src); + var dependencies = []; + traverse(ast, { + enter(path) { + var r; + if ((r = isRequire(path.node, filename))) { + dependencies.push(r.fullPath); + } + }, + }); + parsedFilesByPath[filename] = { + filename: filename, + property: property, + ast: ast, + dependencies: dependencies, + }; +}); + +// step 2: serialize the files in an order where dependencies are always above +// the files they depend on +var externalDependencies = []; +var parsedFiles = []; +var addedFiles = {}; +function addFile(filename, dependencyPath) { + if (dependencyPath.indexOf(filename) !== -1) { + throw new Error( + 'Circular dependency: ' + + dependencyPath + .slice(dependencyPath.indexOf(filename)) + .concat([filename]) + .join(' -> ') + ); + } + var file = parsedFilesByPath[filename]; + if (addedFiles[filename]) { + return; + } + if (!file) { + externalDependencies.push(filename); + } else { + file.dependencies.forEach(function(dependency) { + addFile(dependency, dependencyPath.concat([filename])); + }); + parsedFiles.push(parsedFilesByPath[filename]); + } + addedFiles[filename] = true; +} +Object.keys(parsedFilesByPath).forEach(function(filename) { + addFile(filename, []); +}); +// Step 3: add files to output +// renaming exports to local variables `moduleName_export_exportName` +// and updating require calls as appropriate +var moduleExportsByPath = {}; +var statements = []; +externalDependencies.forEach(function(filename, i) { + var id = t.identifier( + 'external_dependency_' + basename(filename, '.js').replace(/[^A-Za-z]/g, '') + '_' + i + ); + moduleExportsByPath[filename] = { defaultExports: id }; + var relativePath = path.relative(path.resolve(__dirname + '/../lib'), filename); + if (relativePath[0] !== '.') { + relativePath = './' + relativePath; + } + statements.push( + t.variableDeclaration('var', [ + t.variableDeclarator( + id, + t.callExpression(t.identifier('require'), [t.stringLiteral(relativePath)]) + ), + ]) + ); +}); +function getRequireValue(node, file) { + var r, e; + // replace require("./foo").bar with the named export from foo + if ( + t.isMemberExpression(node, { computed: false }) && + (r = isRequire(node.object, file.filename)) + ) { + e = moduleExportsByPath[r.fullPath]; + if (!e) { + return; + } + if (!e.namedExports) { + return t.memberExpression(e.defaultExports, node.property); + } + if (!e.namedExports[node.property.name]) { + throw new Error(r.relative + ' does not export ' + node.property.name); + } + return e.namedExports[node.property.name]; + + // replace require("./foo") with the default export of foo + } else if ((r = isRequire(node, file.filename))) { + e = moduleExportsByPath[r.fullPath]; + if (!e) { + if (/^\.\.\//.test(r.relative)) { + node.arguments[0].value = r.relative.substr(1); + } + return; + } + return e.defaultExports; + } +} +parsedFiles.forEach(function(file) { + var namedExports = {}; + var localVariableMap = {}; + + traverse(file.ast, { + enter(path) { + // replace require calls with the corresponding value + var r; + if ((r = getRequireValue(path.node, file))) { + path.replaceWith(r); + return; + } + + // if we see `var foo = require('bar')` we can just inline the variable + // representing `require('bar')` wherever `foo` was used. + if ( + t.isVariableDeclaration(path.node) && + path.node.declarations.length === 1 && + t.isIdentifier(path.node.declarations[0].id) && + (r = getRequireValue(path.node.declarations[0].init, file)) + ) { + var newName = 'compiled_local_variable_reference_' + getUniqueIndex(); + path.scope.rename(path.node.declarations[0].id.name, newName); + localVariableMap[newName] = r; + path.remove(); + return; + } + + // rename all top level functions to keep them local to the module + if (t.isFunctionDeclaration(path.node) && t.isProgram(path.parent)) { + path.scope.rename(path.node.id.name, file.property + '_local_fn_' + path.node.id.name); + return; + } + + // rename all top level variables to keep them local to the module + if (t.isVariableDeclaration(path.node) && t.isProgram(path.parent)) { + path.node.declarations.forEach(function(declaration) { + path.scope.rename( + declaration.id.name, + file.property + '_local_var_' + declaration.id.name + ); + }); + return; + } + + // replace module.exports.bar with a variable for the named export + if ( + t.isMemberExpression(path.node, { computed: false }) && + isModuleDotExports(path.node.object) + ) { + var name = path.node.property.name; + var identifier = t.identifier(file.property + '_export_' + name); + path.replaceWith(identifier); + namedExports[name] = identifier; + } + }, + }); + traverse(file.ast, { + enter(path) { + if ( + t.isIdentifier(path.node) && + Object.prototype.hasOwnProperty.call(localVariableMap, path.node.name) + ) { + path.replaceWith(localVariableMap[path.node.name]); + } + }, + }); + var defaultExports = t.objectExpression( + Object.keys(namedExports).map(function(name) { + return t.objectProperty(t.identifier(name), namedExports[name]); + }) + ); + moduleExportsByPath[file.filename] = { + namedExports: namedExports, + defaultExports: defaultExports, + }; + statements.push( + t.variableDeclaration( + 'var', + Object.keys(namedExports).map(function(name) { + return t.variableDeclarator(namedExports[name]); + }) + ) + ); + statements.push.apply(statements, file.ast.program.body); +}); +var propertyDefinitions = []; +parsedFiles.forEach(function(file) { + var dashed = camelToDashed(file.property); + propertyDefinitions.push( + t.objectProperty( + t.identifier(file.property), + t.identifier(file.property + '_export_definition') + ) + ); + if (file.property !== dashed) { + propertyDefinitions.push( + t.objectProperty(t.stringLiteral(dashed), t.identifier(file.property + '_export_definition')) + ); + } +}); +var definePropertiesCall = t.callExpression( + t.memberExpression(t.identifier('Object'), t.identifier('defineProperties')), + [t.identifier('prototype'), t.objectExpression(propertyDefinitions)] +); +statements.push( + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression(t.identifier('module'), t.identifier('exports')), + t.functionExpression( + null, + [t.identifier('prototype')], + t.blockStatement([t.expressionStatement(definePropertiesCall)]) + ) + ) + ) +); +out_file.write(generate(t.program(statements)).code + '\n'); +out_file.end(function(err) { + if (err) { + throw err; + } +}); diff --git a/docs/js/node_modules/cssstyle/tests/tests.js b/docs/js/node_modules/cssstyle/tests/tests.js new file mode 100644 index 000000000..945040c20 --- /dev/null +++ b/docs/js/node_modules/cssstyle/tests/tests.js @@ -0,0 +1,669 @@ +'use strict'; +var cssstyle = require('../lib/CSSStyleDeclaration'); + +var { dashedToCamelCase } = require('../lib/parsers'); + +var dashedProperties = [ + ...require('../lib/allProperties'), + ...require('../lib/allExtraProperties'), +]; +var allowedProperties = dashedProperties.map(dashedToCamelCase); +var implementedProperties = Array.from(require('../lib/implementedProperties')).map( + dashedToCamelCase +); +var invalidProperties = implementedProperties.filter(function(property) { + return !allowedProperties.includes(property); +}); + +module.exports = { + 'Verify Has Only Valid Properties Implemented': function(test) { + test.expect(1); + test.ok( + invalidProperties.length === 0, + invalidProperties.length + + ' invalid properties implemented: ' + + Array.from(invalidProperties).join(', ') + ); + test.done(); + }, + 'Verify Has All Properties': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(allowedProperties.length * 2); + allowedProperties.forEach(function(property) { + test.ok(style.__lookupGetter__(property), 'missing ' + property + ' property'); + test.ok(style.__lookupSetter__(property), 'missing ' + property + ' property'); + }); + test.done(); + }, + 'Verify Has All Dashed Properties': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(dashedProperties.length * 2); + dashedProperties.forEach(function(property) { + test.ok(style.__lookupGetter__(property), 'missing ' + property + ' property'); + test.ok(style.__lookupSetter__(property), 'missing ' + property + ' property'); + }); + test.done(); + }, + 'Verify Has Functions': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(6); + test.ok(typeof style.getPropertyValue === 'function', 'missing getPropertyValue()'); + test.ok(typeof style.getPropertyCSSValue === 'function', 'missing getPropertyCSSValue()'); + test.ok(typeof style.removeProperty === 'function', 'missing removeProperty()'); + test.ok(typeof style.getPropertyPriority === 'function', 'missing getPropertyPriority()'); + test.ok(typeof style.setProperty === 'function', 'missing setProperty()'); + test.ok(typeof style.item === 'function', 'missing item()'); + test.done(); + }, + 'Verify Has Special Properties': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(5); + test.ok(style.__lookupGetter__('cssText'), 'missing cssText getter'); + test.ok(style.__lookupSetter__('cssText'), 'missing cssText setter'); + test.ok(style.__lookupGetter__('length'), 'missing length getter'); + test.ok(style.__lookupSetter__('length'), 'missing length setter'); + test.ok(style.__lookupGetter__('parentRule'), 'missing parentRule getter'); + test.done(); + }, + 'Test From Style String': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(8); + style.cssText = 'color: blue; background-color: red; width: 78%; height: 50vh;'; + test.ok(4 === style.length, 'length is not 4'); + test.ok( + 'color: blue; background-color: red; width: 78%; height: 50vh;' === style.cssText, + 'cssText is wrong' + ); + test.ok('blue' === style.getPropertyValue('color'), "getPropertyValue('color') failed"); + test.ok('color' === style.item(0), 'item(0) failed'); + test.ok('background-color' === style[1], 'style[1] failed'); + test.ok( + 'red' === style.backgroundColor, + 'style.backgroundColor failed with "' + style.backgroundColor + '"' + ); + style.cssText = ''; + test.ok('' === style.cssText, 'cssText is not empty'); + test.ok(0 === style.length, 'length is not 0'); + test.done(); + }, + 'Test From Properties': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(11); + style.color = 'blue'; + test.ok(1 === style.length, 'length is not 1'); + test.ok('color' === style[0], 'style[0] is not color'); + test.ok('color: blue;' === style.cssText, 'cssText is wrong'); + test.ok('color' === style.item(0), 'item(0) is not color'); + test.ok('blue' === style.color, 'color is not blue'); + style.backgroundColor = 'red'; + test.ok(2 === style.length, 'length is not 2'); + test.ok('color' === style[0], 'style[0] is not color'); + test.ok('background-color' === style[1], 'style[1] is not background-color'); + test.ok('color: blue; background-color: red;' === style.cssText, 'cssText is wrong'); + test.ok('red' === style.backgroundColor, 'backgroundColor is not red'); + style.removeProperty('color'); + test.ok('background-color' === style[0], 'style[0] is not background-color'); + test.done(); + }, + 'Test Shorthand Properties': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(11); + style.background = 'blue url(http://www.example.com/some_img.jpg)'; + test.ok('blue' === style.backgroundColor, 'backgroundColor is not blue'); + test.ok( + 'url(http://www.example.com/some_img.jpg)' === style.backgroundImage, + 'backgroundImage is wrong' + ); + test.ok( + 'blue url(http://www.example.com/some_img.jpg)' === style.background, + 'background is different' + ); + style.border = '0 solid black'; + test.ok('0px' === style.borderWidth, 'borderWidth is not 0px'); + test.ok('solid' === style.borderStyle, 'borderStyle is not solid'); + test.ok('black' === style.borderColor, 'borderColor is not black'); + test.ok('0px' === style.borderTopWidth, 'borderTopWidth is not 0px'); + test.ok('solid' === style.borderLeftStyle, 'borderLeftStyle is not solid'); + test.ok('black' === style.borderBottomColor, 'borderBottomColor is not black'); + style.font = '12em monospace'; + test.ok('12em' === style.fontSize, 'fontSize is not 12em'); + test.ok('monospace' === style.fontFamily, 'fontFamily is not monospace'); + test.done(); + }, + 'Test width and height Properties and null and empty strings': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(9); + style.height = 6; + test.ok('' === style.height, 'height does not remain unset'); + style.width = 0; + test.ok('0px' === style.width, 'width is not 0px'); + style.height = '34%'; + test.ok('34%' === style.height, 'height is not 34%'); + style.height = '100vh'; + test.ok('100vh' === style.height, 'height is not 100vh'); + style.height = '100vw'; + test.ok('100vw' === style.height, 'height is not 100vw'); + style.height = ''; + test.ok(style.length === 1, 'length is not 1'); + test.ok('width: 0px;' === style.cssText, 'cssText is not "width: 0px;"'); + style.width = null; + test.ok(style.length === 0, 'length is not 0'); + test.ok('' === style.cssText, 'cssText is not empty string'); + test.done(); + }, + 'Test Implicit Properties': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(7); + style.borderWidth = 0; + test.ok(1 === style.length, 'length is not 1'); + test.ok('0px' === style.borderWidth, 'borderWidth is not 0px'); + test.ok('0px' === style.borderTopWidth, 'borderTopWidth is not 0px'); + test.ok('0px' === style.borderBottomWidth, 'borderBottomWidth is not 0px'); + test.ok('0px' === style.borderLeftWidth, 'borderLeftWidth is not 0px'); + test.ok('0px' === style.borderRightWidth, 'borderRightWidth is not 0px'); + test.ok( + 'border-width: 0px;' === style.cssText, + 'cssText is not "border-width: 0px", "' + style.cssText + '"' + ); + test.done(); + }, + 'Test Top, Left, Right, Bottom Properties': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(6); + style.top = 0; + style.left = '0%'; + style.right = '5em'; + style.bottom = '12pt'; + test.ok('0px' === style.top, 'top is not 0px'); + test.ok('0%' === style.left, 'left is not 0%'); + test.ok('5em' === style.right, 'right is not 5em'); + test.ok('12pt' === style.bottom, 'bottom is not 12pt'); + test.ok(4 === style.length, 'length is not 4'); + test.ok( + 'top: 0px; left: 0%; right: 5em; bottom: 12pt;' === style.cssText, + 'text is not "top: 0px; left: 0%; right: 5em; bottom: 12pt;"' + ); + test.done(); + }, + 'Test Clear and Clip Properties': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(10); + style.clear = 'none'; + test.ok('none' === style.clear, 'clear is not none'); + style.clear = 'lfet'; // intentionally wrong + test.ok('none' === style.clear, 'clear is not still none'); + style.clear = 'left'; + test.ok('left' === style.clear, 'clear is not left'); + style.clear = 'right'; + test.ok('right' === style.clear, 'clear is not right'); + style.clear = 'both'; + test.ok('both' === style.clear, 'clear is not both'); + style.clip = 'elipse(5px, 10px)'; + test.ok('' === style.clip, 'clip should not be set'); + test.ok(1 === style.length, 'length is not 1'); + style.clip = 'rect(0, 3Em, 2pt, 50%)'; + test.ok( + 'rect(0px, 3em, 2pt, 50%)' === style.clip, + 'clip is not "rect(0px, 3em, 2pt, 50%)", "' + style.clip + '"' + ); + test.ok(2 === style.length, 'length is not 2'); + test.ok( + 'clear: both; clip: rect(0px, 3em, 2pt, 50%);' === style.cssText, + 'cssText is not "clear: both; clip: rect(0px, 3em, 2pt, 50%);"' + ); + test.done(); + }, + 'Test colors': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(9); + style.color = 'rgba(0,0,0,0)'; + test.ok('rgba(0, 0, 0, 0)' === style.color, 'color is not rgba(0, 0, 0, 0)'); + style.color = 'rgba(5%, 10%, 20%, 0.4)'; + test.ok('rgba(12, 25, 51, 0.4)' === style.color, 'color is not rgba(12, 25, 51, 0.4)'); + style.color = 'rgb(33%, 34%, 33%)'; + test.ok('rgb(84, 86, 84)' === style.color, 'color is not rgb(84, 86, 84)'); + style.color = 'rgba(300, 200, 100, 1.5)'; + test.ok('rgb(255, 200, 100)' === style.color, 'color is not rgb(255, 200, 100) ' + style.color); + style.color = 'hsla(0, 1%, 2%, 0.5)'; + test.ok( + 'hsla(0, 1%, 2%, 0.5)' === style.color, + 'color is not hsla(0, 1%, 2%, 0.5) ' + style.color + ); + style.color = 'hsl(0, 1%, 2%)'; + test.ok('hsl(0, 1%, 2%)' === style.color, 'color is not hsl(0, 1%, 2%) ' + style.color); + style.color = 'rebeccapurple'; + test.ok('rebeccapurple' === style.color, 'color is not rebeccapurple ' + style.color); + style.color = 'transparent'; + test.ok('transparent' === style.color, 'color is not transparent ' + style.color); + style.color = 'currentcolor'; + test.ok('currentcolor' === style.color, 'color is not currentcolor ' + style.color); + test.done(); + }, + 'Test short hand properties with embedded spaces': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(4); + style.background = 'rgb(0, 0, 0) url(/something/somewhere.jpg)'; + test.ok( + 'rgb(0, 0, 0)' === style.backgroundColor, + 'backgroundColor is not rgb(0, 0, 0): ' + style.backgroundColor + ); + test.ok( + 'url(/something/somewhere.jpg)' === style.backgroundImage, + 'backgroundImage is not url(/something/somewhere.jpg): ' + style.backgroundImage + ); + test.ok( + 'background: rgb(0, 0, 0) url(/something/somewhere.jpg);' === style.cssText, + 'cssText is not correct: ' + style.cssText + ); + style = new cssstyle.CSSStyleDeclaration(); + style.border = ' 1px solid black '; + test.ok( + '1px solid black' === style.border, + 'multiple spaces not properly parsed: ' + style.border + ); + test.done(); + }, + 'Setting shorthand properties to an empty string should clear all dependent properties': function( + test + ) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(2); + style.borderWidth = '1px'; + test.ok( + 'border-width: 1px;' === style.cssText, + 'cssText is not "border-width: 1px;": ' + style.cssText + ); + style.border = ''; + test.ok('' === style.cssText, 'cssText is not "": ' + style.cssText); + test.done(); + }, + 'Setting implicit properties to an empty string should clear all dependent properties': function( + test + ) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(2); + style.borderTopWidth = '1px'; + test.ok( + 'border-top-width: 1px;' === style.cssText, + 'cssText is not "border-top-width: 1px;": ' + style.cssText + ); + style.borderWidth = ''; + test.ok('' === style.cssText, 'cssText is not "": ' + style.cssText); + test.done(); + }, + 'Setting a shorthand property, whose shorthands are implicit properties, to an empty string should clear all dependent properties': function( + test + ) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(4); + style.borderTopWidth = '1px'; + test.ok( + 'border-top-width: 1px;' === style.cssText, + 'cssText is not "border-top-width: 1px;": ' + style.cssText + ); + style.border = ''; + test.ok('' === style.cssText, 'cssText is not "": ' + style.cssText); + style.borderTop = '1px solid black'; + test.ok( + 'border-top: 1px solid black;' === style.cssText, + 'cssText is not "border-top: 1px solid black;": ' + style.cssText + ); + style.border = ''; + test.ok('' === style.cssText, 'cssText is not "": ' + style.cssText); + test.done(); + }, + 'Setting border values to "none" should clear dependent values': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(8); + style.borderTopWidth = '1px'; + test.ok( + 'border-top-width: 1px;' === style.cssText, + 'cssText is not "border-top-width: 1px;": ' + style.cssText + ); + style.border = 'none'; + test.ok('' === style.cssText, 'cssText is not "": ' + style.cssText); + style.borderTopWidth = '1px'; + test.ok( + 'border-top-width: 1px;' === style.cssText, + 'cssText is not "border-top-width: 1px;": ' + style.cssText + ); + style.borderTopStyle = 'none'; + test.ok('' === style.cssText, 'cssText is not "": ' + style.cssText); + style.borderTopWidth = '1px'; + test.ok( + 'border-top-width: 1px;' === style.cssText, + 'cssText is not "border-top-width: 1px;": ' + style.cssText + ); + style.borderTop = 'none'; + test.ok('' === style.cssText, 'cssText is not "": ' + style.cssText); + style.borderTopWidth = '1px'; + style.borderLeftWidth = '1px'; + test.ok( + 'border-top-width: 1px; border-left-width: 1px;' === style.cssText, + 'cssText is not "border-top-width: 1px; border-left-width: 1px;": ' + style.cssText + ); + style.borderTop = 'none'; + test.ok( + 'border-left-width: 1px;' === style.cssText, + 'cssText is not "border-left-width: 1px;": ' + style.cssText + ); + test.done(); + }, + 'Setting border to 0 should be okay': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(1); + style.border = 0; + test.ok('border: 0px;' === style.cssText, 'cssText is not "border: 0px;": ' + style.cssText); + test.done(); + }, + 'Setting values implicit and shorthand properties via cssText and setProperty should propagate to dependent properties': function( + test + ) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(4); + style.cssText = 'border: 1px solid black;'; + test.ok( + 'border: 1px solid black;' === style.cssText, + 'cssText is not "border: 1px solid black;": ' + style.cssText + ); + test.ok( + '1px solid black' === style.borderTop, + 'borderTop is not "1px solid black": ' + style.borderTop + ); + style.border = ''; + test.ok('' === style.cssText, 'cssText is not "": ' + style.cssText); + style.setProperty('border', '1px solid black'); + test.ok( + 'border: 1px solid black;' === style.cssText, + 'cssText is not "border: 1px solid black;": ' + style.cssText + ); + test.done(); + }, + 'Setting opacity should work': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(3); + style.setProperty('opacity', 0.75); + test.ok( + 'opacity: 0.75;' === style.cssText, + 'cssText is not "opacity: 0.75;": ' + style.cssText + ); + style.opacity = '0.50'; + test.ok('opacity: 0.5;' === style.cssText, 'cssText is not "opacity: 0.5;": ' + style.cssText); + style.opacity = 1.0; + test.ok('opacity: 1;' === style.cssText, 'cssText is not "opacity: 1;": ' + style.cssText); + test.done(); + }, + 'Width and height of auto should work': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(4); + style.width = 'auto'; + test.equal(style.cssText, 'width: auto;', 'cssText is not "width: auto;": ' + style.cssText); + test.equal(style.width, 'auto', 'width is not "auto": ' + style.width); + style = new cssstyle.CSSStyleDeclaration(); + style.height = 'auto'; + test.equal(style.cssText, 'height: auto;', 'cssText is not "height: auto;": ' + style.cssText); + test.equal(style.height, 'auto', 'height is not "auto": ' + style.height); + test.done(); + }, + 'Padding and margin should set/clear shorthand properties': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + var parts = ['Top', 'Right', 'Bottom', 'Left']; + var testParts = function(name, v, V) { + style[name] = v; + for (var i = 0; i < 4; i++) { + var part = name + parts[i]; + test.equal(V[i], style[part], part + ' is not "' + V[i] + '": "' + style[part] + '"'); + } + test.equal(v, style[name], name + ' is not "' + v + '": "' + style[name] + '"'); + style[name] = ''; + }; + test.expect(50); + testParts('padding', '1px', ['1px', '1px', '1px', '1px']); + testParts('padding', '1px 2%', ['1px', '2%', '1px', '2%']); + testParts('padding', '1px 2px 3px', ['1px', '2px', '3px', '2px']); + testParts('padding', '1px 2px 3px 4px', ['1px', '2px', '3px', '4px']); + style.paddingTop = style.paddingRight = style.paddingBottom = style.paddingLeft = '1px'; + testParts('padding', '', ['', '', '', '']); + testParts('margin', '1px', ['1px', '1px', '1px', '1px']); + testParts('margin', '1px auto', ['1px', 'auto', '1px', 'auto']); + testParts('margin', '1px 2% 3px', ['1px', '2%', '3px', '2%']); + testParts('margin', '1px 2px 3px 4px', ['1px', '2px', '3px', '4px']); + style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '1px'; + testParts('margin', '', ['', '', '', '']); + test.done(); + }, + 'Padding and margin shorthands should set main properties': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + var parts = ['Top', 'Right', 'Bottom', 'Left']; + var testParts = function(name, v, V) { + var expect; + for (var i = 0; i < 4; i++) { + style[name] = v; + style[name + parts[i]] = V; + expect = v.split(/ /); + expect[i] = V; + expect = expect.join(' '); + test.equal(expect, style[name], name + ' is not "' + expect + '": "' + style[name] + '"'); + } + }; + test.expect(12); + testParts('padding', '1px 2px 3px 4px', '10px'); + testParts('margin', '1px 2px 3px 4px', '10px'); + testParts('margin', '1px 2px 3px 4px', 'auto'); + test.done(); + }, + 'Setting a value to 0 should return the string value': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(1); + style.setProperty('fill-opacity', 0); + test.ok('0' === style.fillOpacity, 'fillOpacity is not "0": ' + style.fillOpacity); + test.done(); + }, + 'onChange callback should be called when the cssText changes': function(test) { + var style = new cssstyle.CSSStyleDeclaration(function(cssText) { + test.ok('opacity: 0;' === cssText, 'cssText is not "opacity: 0;": ' + cssText); + test.done(); + }); + test.expect(1); + style.setProperty('opacity', 0); + }, + 'Setting float should work the same as cssFloat': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(1); + style.float = 'left'; + test.ok('left' === style.cssFloat, 'cssFloat is not "left": ' + style.cssFloat); + test.done(); + }, + 'Setting improper css to cssText should not throw': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(2); + style.cssText = 'color: '; + test.ok('' === style.cssText, "cssText wasn't cleared: " + style.cssText); + style.color = 'black'; + style.cssText = 'float: '; + test.ok('' === style.cssText, "cssText wasn't cleared: " + style.cssText); + test.done(); + }, + 'Make sure url parsing works with quotes': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(3); + style.backgroundImage = 'url(http://some/url/here1.png)'; + test.ok( + 'url(http://some/url/here1.png)' === style.backgroundImage, + "background-image wasn't url(http://some/url/here1.png): " + style.backgroundImage + ); + style.backgroundImage = "url('http://some/url/here2.png')"; + test.ok( + 'url(http://some/url/here2.png)' === style.backgroundImage, + "background-image wasn't url(http://some/url/here2.png): " + style.backgroundImage + ); + style.backgroundImage = 'url("http://some/url/here3.png")'; + test.ok( + 'url(http://some/url/here3.png)' === style.backgroundImage, + "background-image wasn't url(http://some/url/here3.png): " + style.backgroundImage + ); + test.done(); + }, + 'Make sure setting 0 to a padding or margin works': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(2); + style.padding = 0; + test.equal(style.cssText, 'padding: 0px;', 'padding is not 0px'); + style.margin = '1em'; + style.marginTop = '0'; + test.equal(style.marginTop, '0px', 'margin-top is not 0px'); + test.done(); + }, + 'Make sure setting ex units to a padding or margin works': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(2); + style.padding = '1ex'; + test.equal(style.cssText, 'padding: 1ex;', 'padding is not 1ex'); + style.margin = '1em'; + style.marginTop = '0.5ex'; + test.equal(style.marginTop, '0.5ex', 'margin-top is not 0.5ex'); + test.done(); + }, + 'Make sure setting null to background works': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(2); + style.background = 'red'; + test.equal(style.cssText, 'background: red;', 'background is not red'); + style.background = null; + test.equal(style.cssText, '', 'cssText is not empty'); + test.done(); + }, + 'Flex properties should keep their values': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(2); + style.flexDirection = 'column'; + test.equal(style.cssText, 'flex-direction: column;', 'flex-direction is not column'); + style.flexDirection = 'row'; + test.equal(style.cssText, 'flex-direction: row;', 'flex-direction is not column'); + test.done(); + }, + 'Make sure camelCase properties are not assigned with `.setProperty()`': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(1); + style.setProperty('fontSize', '12px'); + test.equal(style.cssText, '', 'cssText is not empty'); + test.done(); + }, + 'Make sure casing is ignored in `.setProperty()`': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(2); + style.setProperty('FoNt-SiZe', '12px'); + test.equal(style.fontSize, '12px', 'font-size: 12px'); + test.equal(style.getPropertyValue('font-size'), '12px', 'font-size: 12px'); + test.done(); + }, + 'Support non string entries in border-spacing': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(1); + style.borderSpacing = 0; + test.equal(style.cssText, 'border-spacing: 0px;', 'border-spacing is not 0'); + test.done(); + }, + 'Float should be valid property for `.setProperty()`': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(2); + style.setProperty('float', 'left'); + test.equal(style.float, 'left'); + test.equal(style.getPropertyValue('float'), 'left', 'float: left'); + test.done(); + }, + 'Make sure flex-shrink works': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(3); + style.setProperty('flex-shrink', 0); + test.equal(style.getPropertyValue('flex-shrink'), '0', 'flex-shrink is not 0'); + style.setProperty('flex-shrink', 1); + test.equal(style.getPropertyValue('flex-shrink'), '1', 'flex-shrink is not 1'); + test.equal(style.cssText, 'flex-shrink: 1;', 'flex-shrink cssText is incorrect'); + test.done(); + }, + 'Make sure flex-grow works': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(2); + style.setProperty('flex-grow', 2); + test.equal(style.getPropertyValue('flex-grow'), '2', 'flex-grow is not 2'); + test.equal(style.cssText, 'flex-grow: 2;', 'flex-grow cssText is incorrect'); + test.done(); + }, + 'Make sure flex-basis works': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(5); + + style.setProperty('flex-basis', 0); + test.equal(style.getPropertyValue('flex-basis'), '0px', 'flex-basis is not 0px'); + + style.setProperty('flex-basis', '250px'); + test.equal(style.getPropertyValue('flex-basis'), '250px', 'flex-basis is not 250px'); + + style.setProperty('flex-basis', '10em'); + test.equal(style.getPropertyValue('flex-basis'), '10em', 'flex-basis is not 10em'); + + style.setProperty('flex-basis', '30%'); + test.equal(style.getPropertyValue('flex-basis'), '30%', 'flex-basis is not 30%'); + + test.equal(style.cssText, 'flex-basis: 30%;', 'flex-basis cssText is incorrect'); + + test.done(); + }, + 'Make sure shorthand flex works': function(test) { + var style = new cssstyle.CSSStyleDeclaration(); + test.expect(19); + + style.setProperty('flex', 'none'); + test.equal(style.getPropertyValue('flex-grow'), '0', 'flex-grow is not 0 if flex: none;'); + test.equal(style.getPropertyValue('flex-shrink'), '0', 'flex-shrink is not 0 if flex: none;'); + test.equal( + style.getPropertyValue('flex-basis'), + 'auto', + 'flex-basis is not `auto` if flex: none;' + ); + style.removeProperty('flex'); + style.removeProperty('flex-basis'); + + style.setProperty('flex', 'auto'); + test.equal(style.getPropertyValue('flex-grow'), '', 'flex-grow is not empty if flex: auto;'); + test.equal( + style.getPropertyValue('flex-shrink'), + '', + 'flex-shrink is not empty if flex: auto;' + ); + test.equal( + style.getPropertyValue('flex-basis'), + 'auto', + 'flex-basis is not `auto` if flex: auto;' + ); + style.removeProperty('flex'); + + style.setProperty('flex', '0 1 250px'); + test.equal(style.getPropertyValue('flex'), '0 1 250px', 'flex value is not `0 1 250px`'); + test.equal(style.getPropertyValue('flex-grow'), '0', 'flex-grow is not 0'); + test.equal(style.getPropertyValue('flex-shrink'), '1', 'flex-shrink is not 1'); + test.equal(style.getPropertyValue('flex-basis'), '250px', 'flex-basis is not 250px'); + style.removeProperty('flex'); + + style.setProperty('flex', '2'); + test.equal(style.getPropertyValue('flex-grow'), '2', 'flex-grow is not 2'); + test.equal(style.getPropertyValue('flex-shrink'), '', 'flex-shrink is not empty'); + test.equal(style.getPropertyValue('flex-basis'), '', 'flex-basis is not empty'); + style.removeProperty('flex'); + + style.setProperty('flex', '20%'); + test.equal(style.getPropertyValue('flex-grow'), '', 'flex-grow is not empty'); + test.equal(style.getPropertyValue('flex-shrink'), '', 'flex-shrink is not empty'); + test.equal(style.getPropertyValue('flex-basis'), '20%', 'flex-basis is not 20%'); + style.removeProperty('flex'); + + style.setProperty('flex', '2 2'); + test.equal(style.getPropertyValue('flex-grow'), '2', 'flex-grow is not 2'); + test.equal(style.getPropertyValue('flex-shrink'), '2', 'flex-shrink is not 2'); + test.equal(style.getPropertyValue('flex-basis'), '', 'flex-basis is not empty'); + style.removeProperty('flex'); + + test.done(); + }, +}; diff --git a/docs/js/node_modules/dashdash/CHANGES.md b/docs/js/node_modules/dashdash/CHANGES.md new file mode 100644 index 000000000..d7c8f4ebe --- /dev/null +++ b/docs/js/node_modules/dashdash/CHANGES.md @@ -0,0 +1,364 @@ +# node-dashdash changelog + +## not yet released + +(nothing yet) + +## 1.14.1 + +- [issue #30] Change the output used by dashdash's Bash completion support to + indicate "there are no completions for this argument" to cope with different + sorting rules on different Bash/platforms. For example: + + $ triton -v -p test2 package get # before + ##-no -tritonpackage- completions-## + + $ triton -v -p test2 package get # after + ##-no-completion- -results-## + +## 1.14.0 + +- New `synopsisFromOpt(

      kaO8b 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