@plylang/plylang 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +390 -2
- package/bin/native.js +128 -0
- package/bin/plylang-ls.js +4 -0
- package/bin/plylang.js +4 -0
- package/package.json +46 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Algosail
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,391 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Ply in 5 minutes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Ply is a concatenative language. A program is a sequence of words. A word takes values off the single stack and puts new ones on. The type of a word is that transformation. In stack notation the top is always on the right.
|
|
4
|
+
|
|
5
|
+
The compiler translates a module into JavaScript. There is no separate virtual machine. The language version is 1. There are no subtypes: two types are compatible when they unify.
|
|
6
|
+
|
|
7
|
+
This text walks through the language once, with a short example of each feature.
|
|
8
|
+
|
|
9
|
+
The repository contains the `plylang` compiler and the `plylang-ls` server. Words that work on ready-made values are not built into the compiler: they are imported. The neighboring package `@algosail/ply-prelude` is one such place, and it has its own documentation. The imports in the examples below illustrate the notation.
|
|
10
|
+
|
|
11
|
+
## Stack
|
|
12
|
+
|
|
13
|
+
A signature says what a word takes and what it puts. The tail under that prefix stays in place.
|
|
14
|
+
|
|
15
|
+
```ply
|
|
16
|
+
~ "Num" (add)
|
|
17
|
+
|
|
18
|
+
: double ( Num -- Num )
|
|
19
|
+
dup add
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The body runs from left to right. A number is on the stack. `dup` copies the top. `add` takes both numbers and puts the sum.
|
|
23
|
+
|
|
24
|
+
A word starts with `:`. Then `foreign` and `private` may appear, in either order and each at most once, followed by the name and the scheme in parentheses. Spaces inside the parentheses do not matter. The body may continue on the same line: `: double ( Num -- Num ) dup add`.
|
|
25
|
+
|
|
26
|
+
A name in a scheme is one slot. `Num Num` is two slots. `Maybe a` is also two: the constructor needs an argument, and that argument is written in parentheses, `(Maybe a)`. `(Num)` is the same type as `Num`.
|
|
27
|
+
|
|
28
|
+
A lowercase name is a scheme variable, in order of appearance. An explicit list before the dot is also accepted: `a b. a b -- b`. The kind is inferred. A stack slot has kind `Type`. In `(f a)` the kind of `f` is `Type -> Type`.
|
|
29
|
+
|
|
30
|
+
An omitted stack bottom is the same tail on the left and on the right. An explicit empty bottom is written with a dot: `. a -- . a` requires a stack of exactly one value. The tail `..s` takes the rest as a whole. `..s` and `..s a` are different schemes.
|
|
31
|
+
|
|
32
|
+
## Values
|
|
33
|
+
|
|
34
|
+
Five built-in types and two constructors of arity 1 are visible in every module. They cannot be redefined.
|
|
35
|
+
|
|
36
|
+
| Type | Literal | In JavaScript |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `Num` | `1`, `42`, `1.5`, `1e-3` | number |
|
|
39
|
+
| `Int` | `0n`, `42n`, `-42n` | `bigint` |
|
|
40
|
+
| `Str` | `"hi"`, `"a\nb"` | string |
|
|
41
|
+
| `Bool` | `true`, `false` | boolean |
|
|
42
|
+
| `Void` | the word `Void` | `undefined` |
|
|
43
|
+
|
|
44
|
+
`(Array a)` is an array. `(StrMap a)` is a dictionary with a string key. Bare digits are `Num`, an ordinary number in JavaScript. `Int` is written with the suffix `n` and becomes a `bigint`. In the examples below a number is `Num`. `1.0` is a number. `1.` is the number `1` and a separate dot. A string accepts `\\`, `\"`, `\n`, `\t`, and `\r`.
|
|
45
|
+
|
|
46
|
+
`Void` is both a type and a word. The word puts the single value of that type.
|
|
47
|
+
|
|
48
|
+
Operations on these values are imported. This text does not list them.
|
|
49
|
+
|
|
50
|
+
## Stack words
|
|
51
|
+
|
|
52
|
+
These words are built in. They need no import. On a stack `x y` the top is `y`:
|
|
53
|
+
|
|
54
|
+
```ply
|
|
55
|
+
: copy ( a -- a a )
|
|
56
|
+
dup
|
|
57
|
+
|
|
58
|
+
: gone ( a b -- a )
|
|
59
|
+
drop
|
|
60
|
+
|
|
61
|
+
: flipped ( a b -- b a )
|
|
62
|
+
swap
|
|
63
|
+
|
|
64
|
+
: top ( a b -- b )
|
|
65
|
+
nip
|
|
66
|
+
|
|
67
|
+
: again ( a b -- a b a )
|
|
68
|
+
over
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The same permutations as in Factor follow, with names made of letters: `dupd`, `swapd`, `tuck`, `pick`, `rot`, `unrot`, `dup2`, `drop2`, `nip2`, `swap2`, `over2`. A name does not start with a digit, so the pair is written `dup2`. On `x y z` with `z` on top, `rot` yields `y z x` and `unrot` yields `z x y`.
|
|
72
|
+
|
|
73
|
+
## Quotation
|
|
74
|
+
|
|
75
|
+
`[ ... ]` is a value. It does not capture the stack outside, and it does not run by itself. Square brackets are a quotation. An array is written in angle brackets, a record in braces.
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
: call ( ..s (..s -- ..t) -- ..t )
|
|
79
|
+
: dip ( ..s a (..s -- ..t) -- ..t a )
|
|
80
|
+
: keep ( ..s a (..s a -- ..t) -- ..t a )
|
|
81
|
+
: bi ( ..s a (a -- b) (a -- c) -- ..s b c )
|
|
82
|
+
: if ( ..s Bool (..s -- ..t) (..s -- ..t) -- ..t )
|
|
83
|
+
: quote ( ..s a -- ..s (-- a) )
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`call` takes a quotation and runs it on the rest. `dip` hides the value under the quotation, runs the quotation, and returns what it hid to the top. `dip2` and `dip3` hide two and three values. `keep` runs the quotation and returns the value it took. `keep2` returns two.
|
|
87
|
+
|
|
88
|
+
`bi` applies two quotations to one value. Each returns exactly one. `tri` does the same with three quotations. `bis` gives the deeper value to the first quotation and the top to the second. `bia` applies one quotation to each of two values of the same type.
|
|
89
|
+
|
|
90
|
+
`if` takes a `Bool` and two quotations. The `true` branch is placed first and the `false` branch second, so `false` ends up on top. The branches agree on the stack.
|
|
91
|
+
|
|
92
|
+
`quote` wraps the top. `42 quote` is a quotation of type `(-- Num)`.
|
|
93
|
+
|
|
94
|
+
```ply
|
|
95
|
+
~ "Num" (eq, sub, mul)
|
|
96
|
+
|
|
97
|
+
: fact ( Num -- Num )
|
|
98
|
+
dup 0 eq [ drop 1 ] [ dup 1 sub fact mul ] if
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Zero is replaced by one. Otherwise `n` and `n - 1` remain on the stack, the recursive `fact` computes the second, and `mul` multiplies.
|
|
102
|
+
|
|
103
|
+
Names before `->` name stack values. The left name is the deeper value, the right name is the top. A quotation sees these names, and they are not part of the input.
|
|
104
|
+
|
|
105
|
+
```ply
|
|
106
|
+
: curry ( a (a b -- c) -- (b -- c) )
|
|
107
|
+
x q ->
|
|
108
|
+
[ x swap q call ]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This is an ordinary word of a module. It is not built into the compiler.
|
|
112
|
+
|
|
113
|
+
`call` requires a known finite length for the quotation's input.
|
|
114
|
+
|
|
115
|
+
## Pattern matching
|
|
116
|
+
|
|
117
|
+
When the first line of a body is patterns and `->`, the whole body is equations. The right-hand pattern is the top. A name binds a slot. A literal is compared with the value. `_` discards a value. The first match wins. A value cannot be matched in the middle of a body: write a separate word for that, and make its whole body equations.
|
|
118
|
+
|
|
119
|
+
```ply
|
|
120
|
+
: toString ( Bool -- Str )
|
|
121
|
+
true -> "true"
|
|
122
|
+
false -> "false"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`true` together with `false` covers `Bool`. `Num`, `Int`, and `Str` need a final branch with a name or `_`.
|
|
126
|
+
|
|
127
|
+
A bare constructor puts its fields on the stack from left to right, the last field on top. Parentheses hold one slot: `(Just x)` names the field and does not put it until the name is mentioned.
|
|
128
|
+
|
|
129
|
+
An array in a pattern has a fixed length. `<>` matches only the empty array. One length does not cover every `Array`.
|
|
130
|
+
|
|
131
|
+
```ply
|
|
132
|
+
: isEmpty ( (Array a) -- Bool )
|
|
133
|
+
<> -> true
|
|
134
|
+
_ -> false
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
A record in a pattern reads the named fields. The tail `| r` is written in the type of the slot, not in the pattern. `{}` fits any record of that slot.
|
|
138
|
+
|
|
139
|
+
```ply
|
|
140
|
+
: nameOf ( { name : Str | r } -- Str )
|
|
141
|
+
{ name : n } -> n
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Data
|
|
145
|
+
|
|
146
|
+
`;` in column 0 names an algebraic type. A constructor is a word. A type and a word with one name live in different spaces.
|
|
147
|
+
|
|
148
|
+
```ply
|
|
149
|
+
; Maybe a = Nothing | Just a
|
|
150
|
+
|
|
151
|
+
: fromMaybe ( a (Maybe a) -- a )
|
|
152
|
+
Nothing ->
|
|
153
|
+
Just -> nip
|
|
154
|
+
|
|
155
|
+
: mapMaybe ( (Maybe a) (a -- b) -- (Maybe b) )
|
|
156
|
+
Nothing q -> q drop Nothing
|
|
157
|
+
(Just x) q -> x q call Just
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The empty body of the `Nothing` branch leaves the fallback value. For `Just` the field lands above it, and `nip` drops what is underneath. In `mapMaybe` the quotation is the top: `q` names it, and `(Just x)` names the field.
|
|
161
|
+
|
|
162
|
+
A list with its own structure is a separate type. The head sits in the constructor, and the tail is another value of that type.
|
|
163
|
+
|
|
164
|
+
```ply
|
|
165
|
+
; List a = Nil | Cons a (List a)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
In JavaScript a constructor with no fields is `{ tag: "Nothing" }`. One field is the function `(value) => ({ tag: "Just", value })`. Several fields are `value0`, `value1`, from left to right.
|
|
169
|
+
|
|
170
|
+
## Array
|
|
171
|
+
|
|
172
|
+
`< 1 2 3 >` is an array of three numbers, of type `(Array Num)`. Index 0 is the left element. The empty array is `<>`. A word is not written inside the angle brackets: an element is a literal, a quotation, a nested array, or a record.
|
|
173
|
+
|
|
174
|
+
A word that walks an array is imported. In the example the quotation sits on top. The name comes from one module, and the instance for the array comes from another.
|
|
175
|
+
|
|
176
|
+
```ply
|
|
177
|
+
~ "Map" (map)
|
|
178
|
+
~ "Num" (add)
|
|
179
|
+
~ "Arr" ()
|
|
180
|
+
|
|
181
|
+
: shifted ( -- (Array Num) )
|
|
182
|
+
< 1 2 3 > [ 1 add ] map
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Empty parentheses bring the instances of a module and bring no names. The result is `2`, `3`, `4`.
|
|
186
|
+
|
|
187
|
+
## Record
|
|
188
|
+
|
|
189
|
+
A record is an unnamed set of fields. Field order does not matter: `{ name : Str, age : Num }` and `{ age : Num, name : Str }` are one type.
|
|
190
|
+
|
|
191
|
+
```ply
|
|
192
|
+
: person ( -- { name : Str, age : Num } )
|
|
193
|
+
{ name: "Ada", age: 36 }
|
|
194
|
+
|
|
195
|
+
: nameOf ( { name : Str | r } -- Str )
|
|
196
|
+
/name
|
|
197
|
+
|
|
198
|
+
: older ( { name : Str, age : Num } -- { name : Str, age : Num } )
|
|
199
|
+
37 =age
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`/name` reads a field. An open tail `| r` accepts extra labels, so `nameOf` also takes a record with an `age` field. A closed record `{ name : Str }` accepts no extra field.
|
|
203
|
+
|
|
204
|
+
`=age` replaces a field that is already there and returns a new record. The old JavaScript object stays as it was. `+city` adds a label that is not there yet. The record under `+name` must be closed.
|
|
205
|
+
|
|
206
|
+
```ply
|
|
207
|
+
- Person { name : Str, age : Num }
|
|
208
|
+
- PersonWithCity { city : Str | Person }
|
|
209
|
+
|
|
210
|
+
: inParis ( Person -- PersonWithCity )
|
|
211
|
+
"Paris" +city
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`| Person` substitutes the fields of a closed record. `| (Person a)` does the same when the synonym has an argument: the parentheses are required. The empty record is `{}`.
|
|
215
|
+
|
|
216
|
+
When the record is still needed after a read, copy it: `dup /name`.
|
|
217
|
+
|
|
218
|
+
A named type and a record do different jobs. A constructor may carry a record whole.
|
|
219
|
+
|
|
220
|
+
```ply
|
|
221
|
+
; Person = Mk { name : Str, age : Num }
|
|
222
|
+
|
|
223
|
+
: personName ( Person -- Str )
|
|
224
|
+
Mk -> /name
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## A name for a type
|
|
228
|
+
|
|
229
|
+
`-` names a type that already exists. The right-hand side follows immediately. It is the same type, and the name has no constructor.
|
|
230
|
+
|
|
231
|
+
```ply
|
|
232
|
+
- Person { name : Str, age : Num }
|
|
233
|
+
|
|
234
|
+
: older ( Person -- Person )
|
|
235
|
+
37 =age
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Parameters are written as they are on a type: `- Pair a b { left : a, right : b }`. In a slot a synonym is saturated, `(Pair Num Str)`.
|
|
239
|
+
|
|
240
|
+
## Wrapper
|
|
241
|
+
|
|
242
|
+
`;` with `==` is a distinct type with one constructor and one field. It does not unify with the field. In JavaScript the constructor returns its argument as it is.
|
|
243
|
+
|
|
244
|
+
```ply
|
|
245
|
+
~ "Num" (add)
|
|
246
|
+
|
|
247
|
+
; Age == Age Num
|
|
248
|
+
|
|
249
|
+
: older ( Age -- Age )
|
|
250
|
+
Age -> 1 add Age
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Zero fields, two fields, or two constructors do not fit `==`. A type with `=` is for those.
|
|
254
|
+
|
|
255
|
+
## Dictionary
|
|
256
|
+
|
|
257
|
+
`(StrMap a)` is a dictionary with key `Str`. The key becomes known at run time and is not named in the type ahead of time. There is no literal. An empty dictionary in JavaScript is `Object.create(null)`.
|
|
258
|
+
|
|
259
|
+
```ply
|
|
260
|
+
~ "Dict" (empty, put)
|
|
261
|
+
|
|
262
|
+
: ages ( -- (StrMap Num) )
|
|
263
|
+
empty "Ada" 36 put
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The order in the example is the dictionary, the key, and the value on top.
|
|
267
|
+
|
|
268
|
+
## Classes
|
|
269
|
+
|
|
270
|
+
`&` declares a class. A method is written without a colon. A constraint is not written in the class text: the compiler adds it. `#` declares an instance. The head is the type after the class name. Superclasses stand to the left of `=>`. A subclass constraint brings a superclass method when that method's name is imported. Several constraints in a scheme are separated by a comma.
|
|
271
|
+
|
|
272
|
+
```ply
|
|
273
|
+
& Equal a
|
|
274
|
+
eq ( a a -- Bool )
|
|
275
|
+
|
|
276
|
+
& Equal a => Ordered a
|
|
277
|
+
before ( a a -- Bool )
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```ply
|
|
281
|
+
~ "Equal" (Equal, eq)
|
|
282
|
+
~ "Num" ()
|
|
283
|
+
|
|
284
|
+
: same ( Equal a => a a -- Bool )
|
|
285
|
+
eq
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The instance for numbers is declared in the `Num` module. `()` brings the instances of that module and brings no names. The empty import of `Num` gives `same` that instance.
|
|
289
|
+
|
|
290
|
+
A hand-written instance is a word for each method. A module may write one when it defines the class, the type, or the wrapper in the head, or when the head contains a built-in constructor `Int`, `Num`, `Bool`, `Str`, `Void`, `Array`, or `StrMap`.
|
|
291
|
+
|
|
292
|
+
```ply
|
|
293
|
+
& Equal a
|
|
294
|
+
eq ( a a -- Bool )
|
|
295
|
+
|
|
296
|
+
; Box a = Mk a
|
|
297
|
+
|
|
298
|
+
: eqBox ( Equal a => (Box a) (Box a) -- Bool )
|
|
299
|
+
(Mk x) (Mk y) -> x y eq
|
|
300
|
+
|
|
301
|
+
# Equal (Box a)
|
|
302
|
+
eq = eqBox
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
A parameter of kind `Type -> Type` writes the constructor in the head.
|
|
306
|
+
|
|
307
|
+
```ply
|
|
308
|
+
& Mapped f
|
|
309
|
+
map ( (f a) (a -- b) -- (f b) )
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
An instance of such a class begins with `# Mapped Box`.
|
|
313
|
+
|
|
314
|
+
Two classes with one head are written with a comma. An instance arrives only through a direct import: it does not pass through a second module.
|
|
315
|
+
|
|
316
|
+
When an exported word has a constraint in its scheme, the method dictionary becomes the first argument in JavaScript.
|
|
317
|
+
|
|
318
|
+
## Module
|
|
319
|
+
|
|
320
|
+
One `*.ply` file is one module. The file itself has no name. The path from the source directory gives it: `src/Data/Maybe.ply` is called `Data.Maybe`. Each segment starts with a capital Latin letter.
|
|
321
|
+
|
|
322
|
+
Imports stand at the beginning of the file. A path inside this package is written from the source directory. A path into another package is a package specifier. `..` in the parentheses means every public name.
|
|
323
|
+
|
|
324
|
+
```ply
|
|
325
|
+
~ "Data/Maybe" (Just, Nothing, fromMaybe)
|
|
326
|
+
~ "@algosail/example/Num" (add)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`private` is visible in this file, is written into `.d.ply`, and is not exported.
|
|
330
|
+
|
|
331
|
+
A comment runs from `.` to the end of the line, when the dot is not inside `()`. `..` in column 0, directly above `:`, `;`, `-`, or `&`, is the text of that declaration. One space after `..` is not part of the text. A type has one text for all of its constructors. A class has one text for its name and for all of its methods. A `..` line above `#` does not attach to the declaration.
|
|
332
|
+
|
|
333
|
+
A declaration stands in column 0 and starts with `:`, `;`, `-`, `&`, `#`, or `~`.
|
|
334
|
+
|
|
335
|
+
A package is described by `package.json`.
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
{
|
|
339
|
+
"name": "@algosail/example",
|
|
340
|
+
"type": "module",
|
|
341
|
+
"ply": { "source": "src", "foreign": "foreign", "out": "dist", "language": 1 },
|
|
342
|
+
"exports": {
|
|
343
|
+
"./Data/Maybe": {
|
|
344
|
+
"ply": "./dist/Data/Maybe.d.ply",
|
|
345
|
+
"import": "./dist/Data/Maybe.js"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
A `foreign` word has no body. Its implementation lives in the `ply.foreign` directory, and the number of JavaScript parameters matches the input length. It combines with `private`.
|
|
352
|
+
|
|
353
|
+
```ply
|
|
354
|
+
: foreign private length ( (Array a) -- Num )
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
`ply.foreign` is required when the package has such a word. The directory lies inside the package and is distinct from `source` and `out`. The generated `.js` imports the implementation from there. Those files are not copied into `dist`.
|
|
358
|
+
|
|
359
|
+
```text
|
|
360
|
+
plylang check [dir]
|
|
361
|
+
plylang build [dir] [--exports]
|
|
362
|
+
plylang docs [dir] [--out <file>]
|
|
363
|
+
plylang check --parse <file>
|
|
364
|
+
plylang check --js <file>
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
`plylang check` checks a package the same way `build` does: every file under `ply.source`, the imports, and `foreign`. It writes no files. The default directory is the current one. After the same check, `plylang build` writes a `.js` file and a `.d.ply` interface into `ply.out`. `plylang build --exports` replaces the `exports` field of `package.json` with entries for the built modules. A key is `./` followed by the module path. `ply` points at the `.d.ply`, and `import` points at the `.js`. The other fields of the file stay as they were. With this flag an import of the package's own module succeeds even when that entry was not there yet. Without the flag the entry must already be present in `exports`.
|
|
368
|
+
|
|
369
|
+
`plylang docs` writes `DOCS.md` at the package root. `--out` names another file. A module heading is the string used to import it: the package name and the path, or only the path when there is no name. The prose comes from `..`. Inside a module the sections are `Types`, `Classes`, `Instances`, and `Words`. An empty section is left out. The name of a type, a class, or a word is a heading under its section. `Instances` is a list of `#` heads, with no comment and no method bindings.
|
|
370
|
+
|
|
371
|
+
`plylang check --parse` prints how one file was parsed, and it does not check types. `plylang check --js` prints the JavaScript that `build` would write for that file. Outside a package that is the JavaScript of the one file, and imports are not resolved.
|
|
372
|
+
|
|
373
|
+
The exit code is 0 for success, 1 for a diagnostic, and 2 for bad arguments. `--help` prints the same lines. Messages are in English: `file:line:column: error: PLxxxx: message`, and the next line is a link to the matching section of `docs/Errors.md`.
|
|
374
|
+
|
|
375
|
+
What enters a word's function is its own prefix, from the bottom upward. One result is returned by itself. Several results come back as a JavaScript array: that is the calling convention, and it is not an `(Array a)` value. The words from `dup` through `over2` do not become calls. `call`, `dip`, `keep`, `bi`, `if`, and their neighbors inline the body of the quotation.
|
|
376
|
+
|
|
377
|
+
## Building
|
|
378
|
+
|
|
379
|
+
From this repository:
|
|
380
|
+
|
|
381
|
+
```text
|
|
382
|
+
cargo build --release
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`target/release/plylang` and `target/release/plylang-ls` are the compiler and the server. The server speaks LSP over stdio. In a package with a `ply` field it resolves imports the same way `plylang build` does.
|
|
386
|
+
|
|
387
|
+
The npm package is named `@plylang/plylang` and installs the same two commands. Version `1.x` of that package is language `1`. A minor or a patch of the compiler does not change the language number. `PLYLANG_BIN` and `PLYLANG_LS_BIN` supply a binary of their own.
|
|
388
|
+
|
|
389
|
+
Highlighting is the [tree-sitter-ply](https://github.com/algosail/tree-sitter-ply) grammar. The Zed extension is the neighboring repository `zed-ply`: install it as a dev extension after `cargo build -p plylang-ls`.
|
|
390
|
+
|
|
391
|
+
The license is MIT.
|
package/bin/native.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// The npm commands `plylang` and `plylang-ls` start here and run the Rust
|
|
2
|
+
// binary. An installed optional package `@plylang/plylang-<os>-<arch>` wins.
|
|
3
|
+
// A checkout of this repository uses `target/release`, then `target/debug`.
|
|
4
|
+
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
|
|
10
|
+
const root = join(import.meta.dirname, "..");
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
|
|
13
|
+
export const PLATFORMS = new Set([
|
|
14
|
+
"darwin-arm64",
|
|
15
|
+
"darwin-x64",
|
|
16
|
+
"linux-arm64",
|
|
17
|
+
"linux-x64",
|
|
18
|
+
"win32-arm64",
|
|
19
|
+
"win32-x64",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export function platformId(platform = process.platform, arch = process.arch) {
|
|
23
|
+
const id = `${platform}-${arch}`;
|
|
24
|
+
return PLATFORMS.has(id) ? id : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveBinary(command, options = {}) {
|
|
28
|
+
if (command !== "plylang" && command !== "plylang-ls") {
|
|
29
|
+
throw new Error(`unknown command ${command}`);
|
|
30
|
+
}
|
|
31
|
+
const env = options.env ?? process.env;
|
|
32
|
+
const platform = options.platform ?? process.platform;
|
|
33
|
+
const arch = options.arch ?? process.arch;
|
|
34
|
+
const packageRoot = options.packageRoot ?? root;
|
|
35
|
+
const resolvePackage = options.resolvePackage ?? defaultResolvePackage;
|
|
36
|
+
const overrideName = command === "plylang" ? "PLYLANG_BIN" : "PLYLANG_LS_BIN";
|
|
37
|
+
const override = env[overrideName];
|
|
38
|
+
if (override) {
|
|
39
|
+
if (!isExecutable(override)) {
|
|
40
|
+
throw new Error(`${overrideName} is not an executable file: ${override}`);
|
|
41
|
+
}
|
|
42
|
+
return override;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const id = platformId(platform, arch);
|
|
46
|
+
if (id) {
|
|
47
|
+
const pkgName = `@plylang/plylang-${id}`;
|
|
48
|
+
const pkgJson = resolvePackage(pkgName);
|
|
49
|
+
if (pkgJson) {
|
|
50
|
+
const name = fileName(command, platform);
|
|
51
|
+
const file = join(dirname(pkgJson), "bin", name);
|
|
52
|
+
if (!isExecutable(file)) {
|
|
53
|
+
throw new Error(`${pkgName} does not contain an executable bin/${name}`);
|
|
54
|
+
}
|
|
55
|
+
return file;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const built = checkoutBinary(command, packageRoot, platform);
|
|
60
|
+
if (built) {
|
|
61
|
+
return built;
|
|
62
|
+
}
|
|
63
|
+
if (!id) {
|
|
64
|
+
throw new Error(`${command} does not ship a binary for ${platform}-${arch}.`);
|
|
65
|
+
}
|
|
66
|
+
return missing(command, id);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function missing(command, id) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`${command} was not found for ${id}.\n` +
|
|
72
|
+
"From a checkout, run `cargo build --release -p plylang -p plylang-ls`.\n" +
|
|
73
|
+
`From npm, install the optional package @plylang/plylang-${id}.`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function checkoutBinary(command, packageRoot, platform) {
|
|
78
|
+
const name = fileName(command, platform);
|
|
79
|
+
for (const profile of ["release", "debug"]) {
|
|
80
|
+
const file = join(packageRoot, "target", profile, name);
|
|
81
|
+
if (isExecutable(file)) {
|
|
82
|
+
return file;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function fileName(command, platform) {
|
|
89
|
+
return platform === "win32" ? `${command}.exe` : command;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function defaultResolvePackage(name) {
|
|
93
|
+
try {
|
|
94
|
+
return require.resolve(`${name}/package.json`);
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function run(command) {
|
|
101
|
+
let bin;
|
|
102
|
+
try {
|
|
103
|
+
bin = resolveBinary(command);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(error.message);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const child = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
109
|
+
if (child.error) {
|
|
110
|
+
console.error(`${command}: ${child.error.message}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
process.exit(child.status ?? 1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isExecutable(file) {
|
|
117
|
+
try {
|
|
118
|
+
if (!statSync(file).isFile()) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (process.platform !== "win32") {
|
|
122
|
+
accessSync(file, constants.X_OK);
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
package/bin/plylang.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plylang/plylang",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Ply compiler and language server",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Algosail",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/algosail/plylang.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/algosail/plylang#readme",
|
|
13
|
+
"bugs": "https://github.com/algosail/plylang/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ply",
|
|
16
|
+
"plylang",
|
|
17
|
+
"compiler",
|
|
18
|
+
"language-server"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=24"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"plylang": "bin/plylang.js",
|
|
28
|
+
"plylang-ls": "bin/plylang-ls.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"bin/plylang.js",
|
|
32
|
+
"bin/plylang-ls.js",
|
|
33
|
+
"bin/native.js"
|
|
34
|
+
],
|
|
35
|
+
"optionalDependencies": {
|
|
36
|
+
"@plylang/plylang-darwin-arm64": "1.0.0",
|
|
37
|
+
"@plylang/plylang-darwin-x64": "1.0.0",
|
|
38
|
+
"@plylang/plylang-linux-arm64": "1.0.0",
|
|
39
|
+
"@plylang/plylang-linux-x64": "1.0.0",
|
|
40
|
+
"@plylang/plylang-win32-arm64": "1.0.0",
|
|
41
|
+
"@plylang/plylang-win32-x64": "1.0.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "cargo build --release",
|
|
45
|
+
"test": "cargo test",
|
|
46
|
+
"test:launcher": "node --test bin/native.test.js script/stage-native.test.js"
|
|
47
|
+
}
|
|
48
|
+
}
|