|
2 | 2 |
|
3 | 3 | An exploratory programming language. |
4 | 4 |
|
5 | | -## Quick start |
| 5 | +## Quick Start |
6 | 6 |
|
7 | 7 | ```sh |
8 | | -git clone [email protected]:mkantor/please-prototype.git |
9 | | -cd please-prototype |
| 8 | +git clone [email protected]:mkantor/please- lang-prototype.git |
| 9 | +cd please-lang-prototype |
10 | 10 | npm install |
11 | 11 | npm run build |
12 | 12 | echo '{@runtime context => :context.program.start_time}' | ./please --output-format=json |
13 | 13 | ``` |
14 | 14 |
|
15 | | -## What this repository is |
| 15 | +## What This Repository Is |
16 | 16 |
|
17 | | -**This implementation of Please is a proof of concept**. There are bugs and missing pieces, and |
18 | | -language syntax/semantics may change backwards-incompatibly on the way to an official release. |
19 | | -TypeScript was chosen because it's pretty good for rapid prototyping, but it's likely that another |
20 | | -language would be used in a non-prototype implementation. |
| 17 | +**This implementation of Please is a proof of concept**. There are bugs and |
| 18 | +missing pieces, and language syntax/semantics may change backwards-incompatibly |
| 19 | +on the way to an official release. TypeScript was chosen because it's pretty |
| 20 | +good for rapid prototyping, but different languages may be used in non-prototype |
| 21 | +implementations. |
21 | 22 |
|
22 | | -## Current state |
| 23 | +### Current State |
23 | 24 |
|
24 | | -Enough pieces exist to write basic runnable programs. There is a type system, but it's not wired up |
25 | | -in many places yet so mistakes often go unnoticed at compile time. The standard library is anemic |
26 | | -and documentation is nonexistent. |
| 25 | +Enough pieces exist to write basic runnable programs. There is a type system, |
| 26 | +but it's not wired up in many places yet so mistakes often go unnoticed at |
| 27 | +compile time. The standard library is anemic and documentation is lacking. |
27 | 28 |
|
28 | | -The current runtime is an interpreter, but the plan is to eventually add one or more backends to |
29 | | -allow building native executables. |
| 29 | +The current runtime is an interpreter, but the plan is to eventually add one or |
| 30 | +more backends to allow building native executables. |
30 | 31 |
|
31 | | -## What's next? |
| 32 | +### What's Next? |
32 | 33 |
|
33 | | -I'm focused on squashing compiler bugs and establishing solid foundations to build atop. Along the |
34 | | -way I've been slowly fleshing out the type system and standard library. |
| 34 | +I'm focused on squashing compiler bugs and establishing solid foundations to |
| 35 | +build atop. Along the way I've been slowly fleshing out the type system and |
| 36 | +standard library. |
| 37 | + |
| 38 | +## Language Design |
| 39 | + |
| 40 | +### Syntax |
| 41 | + |
| 42 | +A Please program is composed of atoms, objects, lookups, and functions. |
| 43 | + |
| 44 | +#### Atoms |
| 45 | + |
| 46 | +Atoms are the raw textual portions of your source code. They're similar to |
| 47 | +strings from other programming languages, except there isn't a specific runtime |
| 48 | +data representation implied by the fact that a value is an atom (e.g. the atom |
| 49 | +`2` may be an integer in memory). |
| 50 | + |
| 51 | +Bare words not containing any |
| 52 | +[reserved character sequences](./src/language/parsing/atom.ts#L54-L76) are |
| 53 | +atoms: |
| 54 | + |
| 55 | +``` |
| 56 | +Hello |
| 57 | +``` |
| 58 | + |
| 59 | +Atoms can be quoted: |
| 60 | + |
| 61 | +``` |
| 62 | +"Hello, World!" |
| 63 | +``` |
| 64 | + |
| 65 | +#### Objects |
| 66 | + |
| 67 | +Objects are maps of key/value pairs ("properties"), where keys must be atoms: |
| 68 | + |
| 69 | +``` |
| 70 | +{ greeting: "Hello, World!" } |
| 71 | +``` |
| 72 | + |
| 73 | +Object properties without explicitly-written keys are automatically enumerated: |
| 74 | + |
| 75 | +``` |
| 76 | +{ Hello World } // is the same as { 0: Hello, 1: World } |
| 77 | +``` |
| 78 | + |
| 79 | +#### Lookups |
| 80 | + |
| 81 | +Data can be referenced from other places in the program using lookups, like |
| 82 | +`:en` below: |
| 83 | + |
| 84 | +``` |
| 85 | +{ |
| 86 | + en: "Hello, World!" |
| 87 | + zh: "世界您好!" |
| 88 | + hi: "हैलो वर्ल्ड!" |
| 89 | + es: "¡Hola, Mundo!" |
| 90 | + default: :en // runtime value is "Hello, World!" |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +You can drill down into the properties of looked-up values: |
| 95 | + |
| 96 | +``` |
| 97 | +{ |
| 98 | + deeply: { |
| 99 | + nested: { |
| 100 | + greeting: "Hello, World!" |
| 101 | + } |
| 102 | + } |
| 103 | + greeting: :deeply.nested.greeting // or :{deeply nested greeting} |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +Lookups are lexically scoped: |
| 108 | + |
| 109 | +``` |
| 110 | +{ |
| 111 | + greeting: "Hello, World!" |
| 112 | + scope: { |
| 113 | + greeting: "Hi, Moon!" |
| 114 | + a: :greeting |
| 115 | + } |
| 116 | + b: :greeting |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +Output: |
| 121 | + |
| 122 | +``` |
| 123 | +{ |
| 124 | + greeting: "Hello, World!" |
| 125 | + scope: { |
| 126 | + greeting: "Hi, Moon!" |
| 127 | + a: "Hi, Moon!" |
| 128 | + } |
| 129 | + b: "Hello, World!" |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +#### Functions |
| 134 | + |
| 135 | +Functions take exactly one parameter and their body is exactly one expression: |
| 136 | + |
| 137 | +``` |
| 138 | +a => "Hello, World!" |
| 139 | +``` |
| 140 | + |
| 141 | +Functions can be applied: |
| 142 | + |
| 143 | +``` |
| 144 | +(a => :a)("Hello, World!") |
| 145 | +``` |
| 146 | + |
| 147 | +#### Keywords |
| 148 | + |
| 149 | +The functions and lookups shown above are syntax sugar for _keyword |
| 150 | +expressions_. Most of the interesting stuff that Please does involves evaluating |
| 151 | +keyword expressions. |
| 152 | + |
| 153 | +Under the hood, keyword expressions are modeled as objects. For example, `:foo` |
| 154 | +desugars to `{@lookup query: foo}`. All such expressions have a key `0` |
| 155 | +referring to a value that is an `@`-prefixed atom (the keyword). Keywords |
| 156 | +include `@function`, `@lookup`, `@apply`, `@check`, and `@runtime`. |
| 157 | + |
| 158 | +Currently only `@function`, `@lookup`, and `@apply` have syntax sugars. |
| 159 | + |
| 160 | +### Semantics |
| 161 | + |
| 162 | +Please is a functional programming language. Currently all functions are pure, |
| 163 | +with a sole exception: logging to stderr can happen from anywhere. The specific |
| 164 | +approach to modeling other runtime side effects is still to be decided. |
| 165 | + |
| 166 | +Once desugared, a Please program is either an atom or an object. Please code is |
| 167 | +data in the same sense as in Lisp, though without a macro system there's not |
| 168 | +much you can do with this right now. |
| 169 | + |
| 170 | +Before a Please program terminates, it prints the fully-resolved version of |
| 171 | +itself to standard output. That means Hello World can be as simple as this: |
| 172 | + |
| 173 | +``` |
| 174 | +"Hello, World!" |
| 175 | +``` |
| 176 | + |
| 177 | +`@runtime` expressions allow accessing runtime context (like command-line |
| 178 | +arguments). A `@runtime` expression is conceptually a bit like the `main` |
| 179 | +function from other programming languages, except there can be any number of |
| 180 | +`@runtime` expressions in a given program. Here's an example: |
| 181 | + |
| 182 | +``` |
| 183 | +{@runtime context => :context.program.start_time} |
| 184 | +``` |
| 185 | + |
| 186 | +Unsurprisingly, this program outputs the current time when run. |
| 187 | + |
| 188 | +Code outside of `@runtime` expressions is evaluated at compile-time as much as |
| 189 | +possible. For example, this program compiles to the literal value `2` (no |
| 190 | +computation will occur at runtime): |
| 191 | + |
| 192 | +``` |
| 193 | +:integer.add(1)(1) |
| 194 | +``` |
| 195 | + |
| 196 | +There's currently no module system and all Please programs are single files, but |
| 197 | +that's only because this is a prototype. |
| 198 | + |
| 199 | +### Layering |
| 200 | + |
| 201 | +Please is a layered language. It can be thought of as a stack of three smaller |
| 202 | +languages: |
| 203 | + |
| 204 | +- Layer 0 (`plz`) is the surface syntax. This is the language you as a human |
| 205 | + typically use to write programs. |
| 206 | +- Layer 1 (`plo`) is a desugared/normalized representation of the syntax tree. |
| 207 | +- Layer 2 (`plt`) is the result of applying semantic analysis, compile-time |
| 208 | + evaluation, and other reductions to the `plo` tree. The prototype |
| 209 | + implementation of the language runtime is a `plt` interpreter. |
| 210 | + |
| 211 | +`plz` has a specific textual representation, but `plo` & `plt` could be encoded |
| 212 | +in any format in which hierarchial key/value pairs of strings are representable |
| 213 | +(currently only JSON is implemented, but YAML, TOML, HOCON, BSON, MessagePack, |
| 214 | +etc could be supported). |
| 215 | + |
| 216 | +Take this example `plz` program: |
| 217 | + |
| 218 | +``` |
| 219 | +{ |
| 220 | + language: Please |
| 221 | + message: :atom.prepend("Welcome to ")(:language) |
| 222 | + now: {@runtime context => :context.program.start_time} |
| 223 | +} |
| 224 | +``` |
| 225 | + |
| 226 | +It desugars to the following `plo` program: |
| 227 | + |
| 228 | +``` |
| 229 | +{ |
| 230 | + language: Please |
| 231 | + message: { |
| 232 | + 0: @apply |
| 233 | + function: { |
| 234 | + 0: @apply |
| 235 | + function: { |
| 236 | + 0: @lookup |
| 237 | + query: atom.prepend |
| 238 | + } |
| 239 | + argument: "Welcome to " |
| 240 | + } |
| 241 | + argument: { |
| 242 | + 0: @lookup |
| 243 | + query: language |
| 244 | + } |
| 245 | + } |
| 246 | + now: { |
| 247 | + 0: @runtime |
| 248 | + 1: { |
| 249 | + 0: @function |
| 250 | + parameter: context |
| 251 | + body: { |
| 252 | + 0: @lookup |
| 253 | + query: context.program.start_time |
| 254 | + } |
| 255 | + } |
| 256 | + } |
| 257 | +} |
| 258 | +``` |
| 259 | + |
| 260 | +Which in turn compiles to the following `plt` program: |
| 261 | + |
| 262 | +``` |
| 263 | +{ |
| 264 | + language: Please |
| 265 | + message: "Welcome to Please" |
| 266 | + now: { |
| 267 | + 0: @runtime |
| 268 | + function: { |
| 269 | + 0: @function |
| 270 | + parameter: context |
| 271 | + body: { |
| 272 | + 0: @lookup |
| 273 | + query: context.program.start_time |
| 274 | + } |
| 275 | + } |
| 276 | + } |
| 277 | +} |
| 278 | +``` |
| 279 | + |
| 280 | +Which produces the following runtime output: |
| 281 | + |
| 282 | +``` |
| 283 | +{ |
| 284 | + language: Please |
| 285 | + message: "Welcome to Please" |
| 286 | + now: "2025-01-27T16:06:55.802Z" |
| 287 | +} |
| 288 | +``` |
| 289 | + |
| 290 | +After an eventual stable release of Please, `plo` & `plt` will be versioned to |
| 291 | +ensure backwards compatibility. |
| 292 | + |
| 293 | +Many compilers use intermediate representations (IRs) internally. Please's |
| 294 | +layers serve a similar purpose, though unlike some other IRs they are |
| 295 | +serializable, stable, and are designed to be human-readable (albeit verbose). |
| 296 | + |
| 297 | +What is this good for? Use cases include: |
| 298 | + |
| 299 | +- an eventual package manager which distributes lower-layer representations for |
| 300 | + efficiency |
| 301 | +- experimenting with alternative syntaxes while remaining compatible with the |
| 302 | + rest of the ecosystem |
| 303 | +- caching `plo` & `plt` artifacts to speed up recompiles |
| 304 | +- new optimizations can be applied to existing programs without compiling from |
| 305 | + scratch |
| 306 | +- using common serialization formats to encode `plo` & `plt` makes them easy to |
| 307 | + manipulate with a variety of tools (refactor your code with |
| 308 | + [`jq`](https://jqlang.github.io/jq/)!) |
| 309 | + |
| 310 | +### Philosophy |
| 311 | + |
| 312 | +The first-order goal of Please is to be pleasant to use. |
| 313 | + |
| 314 | +It strives to: |
| 315 | + |
| 316 | +- help you express your ideas clearly, concisely, and correctly |
| 317 | +- catch mistakes and oversights without being annoying or confusing |
| 318 | +- be useful across many different contexts & domains |
| 319 | +- be extensible to accommodate novel use cases |
| 320 | +- make it easy to pay off technical debt |
| 321 | +- emit programs that you have confidence in |
| 322 | + |
| 323 | +The prototype implementation doesn't live up to these aspirations, but hopefully |
| 324 | +it approaches them over time. |
0 commit comments