functionalscript 0.0.565 → 0.0.567

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.
@@ -87,6 +87,6 @@ jobs:
87
87
  name: package-lock.json
88
88
  - uses: actions/setup-dotnet@v3
89
89
  with:
90
- dotnet-version: 8
90
+ dotnet-version: 9
91
91
  - run: npm ci
92
92
  - run: npm run comtest
@@ -16,7 +16,7 @@ jobs:
16
16
  fetch-depth: 0
17
17
  - uses: actions/setup-node@v2
18
18
  with:
19
- node-version: 19
19
+ node-version: 22
20
20
  registry-url: https://registry.npmjs.org/
21
21
  # - run: npm ci
22
22
  - run: npm run version
package/doc/byte-code.md CHANGED
@@ -42,19 +42,18 @@ struct Module {
42
42
  }
43
43
  ```
44
44
 
45
- |type|any |tag| | |
46
- |----|--------------|---|-----------------------|-----------------------------|
47
- |JSON|null | 00| | |
48
- | |number | 01|u64 | |
49
- | |false | 02| | |
50
- | |true | 03| | |
51
- | |string | 04|String | |
52
- | |object | 05|Object | |
53
- | |array | 06|Array<Any> | |
54
- |DJS |bigint+ | 07|BigUInt | |
55
- | |bigint- | 08|BigUInt | |
56
- | |local_ref | 09|u32 |consts[i] |
57
- |FJS |arg_ref | 0A|u32 |args[i] |
58
- | |undefined | 0B| | |
59
- | |function | 0C|Function |the last constant is a return|
60
- | |... | | | |
45
+ |format|any |Tag| |
46
+ |------|---------------|---|-----------------------|
47
+ |JSON |null | 00| |
48
+ | |number | 01|u64 |
49
+ | |false | 02| |
50
+ | |true | 03| |
51
+ | |string | 04|String |
52
+ | |object | 05|Object |
53
+ | |array | 06|Array<Any> |
54
+ |DJS |ref | 07|u32 |
55
+ | |positive_bigint| 08|BigUInt |
56
+ | |negative_bigint| 09|BigUInt |
57
+ |FJS |function | 0A|Function |
58
+ | |arg_ref | 0B|u32 |
59
+ | |undefined | 0C| |
package/doc/fa.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # FA
2
2
 
3
+ ```
3
4
  F ::= A 'hello'
4
5
  F ::= A 'help'
6
+ ```
5
7
 
6
8
  ## Classic FA
7
9
 
10
+ ```
8
11
  S0 ::= A 'h'
9
12
  S1 ::= S0 'e'
10
13
  S2 ::= S1 'l'
@@ -15,9 +18,11 @@ X0 ::= A 'h'
15
18
  X1 ::= X0 'e'
16
19
  X2 ::= X1 'l'
17
20
  F ::= X2 'p'
21
+ ```
18
22
 
19
23
  ## DFA
20
24
 
25
+ ```
21
26
  {S0,X0} = A 'h'
22
27
  {S1,X1} ::= {S0,X0} 'e'
23
28
  {S2,X2} ::= {S1,X1} 'l'
@@ -31,24 +36,29 @@ P2 ::= P1 'l'
31
36
  S3 ::= P2 'l'
32
37
  F ::= P2 'p'
33
38
  F ::= S3 'o'
39
+ ```
34
40
 
35
41
  ## Tokenizer FA
36
42
 
43
+ ```
37
44
  T ::= I 'true' // T0, T1, T2
38
45
  F ::= I 'false' // F0, F2, F2, F3
39
46
  N ::= I 'null' // N0, N1, N2
40
47
  Id ::= I letter
41
48
  Id ::= Id letter
42
49
  Id ::= Id digit
50
+ ```
43
51
 
44
52
  ## Tokenizer DFA
45
53
 
54
+ ```
46
55
  {T0,Id} = I 't'
47
56
  {T1,Id} = {T0,Id} 'r'
48
57
  Id = {T0,Id} letter(except 'r')
49
58
  Id = {T0,Id} digit
50
59
 
51
60
  {a..b}{c..d}{e..f}
61
+ ```
52
62
 
53
63
  ```js
54
64
  const t0 = [[init, one('t')]]
@@ -0,0 +1,21 @@
1
+ # ECMAScripts Proposals
2
+
3
+ ECMAScript proposals that may affect FunctionalScript
4
+
5
+ - Type Annotations: https://github.com/tc39/proposal-type-annotations
6
+ ```js
7
+ const add = (a: bigint) => (b: bigint) => a + b
8
+ ```
9
+ - Deeply Immutable Record and Tuples: https://github.com/tc39/proposal-record-tuple
10
+ ```js
11
+ const r = #{ x: 4, y: "s" }
12
+ const t = #[5, "hello"]
13
+ ```
14
+ Because these types are deeply immutable and the equality operator '===' works as deep equality, it's a good candidate for content-addressable type system.
15
+ - Pipeline operator https://github.com/tc39/proposal-pipeline-operator
16
+ ```js
17
+ const double = a => a + a
18
+ const munis1 = a => a - 1
19
+ const f = a => a |> double(%) |> minus1(%)
20
+ ```
21
+ I, Sergey, still prefer F# pipeline operator and strongly believe that it's possible to have both syntax in the language.
@@ -0,0 +1,24 @@
1
+ # VM
2
+
3
+ Two options:
4
+ - using instances
5
+ - using types. In this case, if we need multiple VMs in the same process, we need multiple types.
6
+
7
+ ## Rust Interface
8
+
9
+ ```rust
10
+ trait Any {
11
+ }
12
+
13
+ trait String {
14
+ }
15
+
16
+ trait Bigint {
17
+ }
18
+
19
+ trait Object {
20
+ }
21
+
22
+ trait Array {
23
+ }
24
+ ```
@@ -0,0 +1,129 @@
1
+ # NaNVM Re-Architecture
2
+
3
+ About a year ago (Nov 2023), the FunctionalScript team started a new project called [NaNVM](https://github.com/functionalscript/nanvm). We have limited resources (⌛💰) for the projects, so progress has been slow. Since then, we’ve implemented several components from scratch in Rust:
4
+ - An interface and multiple implementations for [Memory Management](https://github.com/functionalscript/nanvm/tree/main/nanvm-lib/src/mem), such as a [global](https://github.com/functionalscript/nanvm/blob/main/nanvm-lib/src/mem/global.rs) memory manager using standard `alloc/dealloc`, a [local](https://github.com/functionalscript/nanvm/blob/main/nanvm-lib/src/mem/local.rs) manager with a reference counter, and a simple [arena](https://github.com/functionalscript/nanvm/blob/main/nanvm-lib/src/mem/arena.rs) implementation.
5
+ - [All FS data types](https://github.com/functionalscript/nanvm/tree/main/nanvm-lib/src/js) in the VM, such as [string](https://github.com/functionalscript/nanvm/blob/main/nanvm-lib/src/js/js_string.rs), [bigint](https://github.com/functionalscript/nanvm/blob/main/nanvm-lib/src/js/js_bigint.rs), [array](https://github.com/functionalscript/nanvm/blob/main/nanvm-lib/src/js/js_array.rs), [object](https://github.com/functionalscript/nanvm/blob/main/nanvm-lib/src/js/js_object.rs), and [any](https://github.com/functionalscript/nanvm/blob/main/nanvm-lib/src/js/any.rs).
6
+ - And, of course, we've implemented a parser for JSON and DJS in Rust, which works well. See [this article about DJS](https://medium.com/@sasha.gil/bridging-the-gap-from-json-to-javascript-without-dsls-fee273573f1b) for more information.
7
+
8
+ All of this code is written in Rust. Rust excels at runtime performance and system-level programming. However, developers often struggle when working with high-level and business logic. Its verbosity and various memory management models make developing components like parsers in Rust slow and complex. It’s great for implementing a memory manager, a VM, or a big integer but less suited for tasks like implementing a parser efficiently (and yes, we are aware of third-party parser generators). That’s why we want to use a high-level language for this purpose. Of course, we don't need to search for one because we already have FunctionalScript and JavaScript as glue. Additionally, because of our limited resources, we aim to use only a few repositories. As a result, we plan to merge the NaNVM code into the [FunctionalScript repo](https://github.com/functionalscript/functionalscript).
9
+
10
+ ## Stage 0: Current State
11
+
12
+ Before we start rearchitecting the project, we should understand our current architecture. Here's the current module dependency graph for [nanvm_lib](https://github.com/functionalscript/nanvm/tree/main/nanvm-lib/src):
13
+
14
+ ```mermaid
15
+ flowchart TB
16
+ app[Application] --> parser[Parser] --> js[VM] --> mem[Memory Manager]
17
+ ```
18
+
19
+ The parser is written in Rust using VM types, such as `JsString` and `JsBigInt`. The application can parse JSON and DJS files and output in any of these formats as a standalone module.
20
+
21
+ ## Stage 1: Using a Third-Party JS Engine for Parsing
22
+
23
+ Because FunctionalScript is a subset of JavaScript, we can use third-party JavaScript engines to bootstrap our parser, written on FunctionalScript, without circular dependencies. In Rust, we only need to implement a generic byte code deserializer that reads byte code and invokes VM API functions. We selected [Deno](https://deno.com/) and its [deno_core](https://crates.io/crates/deno_core/) package as a third-party JS engine because it's also written in Rust, has a crate and it's easy to integrate with our project.
24
+
25
+ ```mermaid
26
+ flowchart TB
27
+ app[Application] --> deno(deno_core)
28
+ app --> bcd[Byte Code Deserializer] --> js[VM] --> mem[Memory Manager]
29
+ ```
30
+
31
+ ### Requirements
32
+ 1. To restore previous functionality, we still need a parser that can convert FunctionalScript or DJS into byte code,
33
+ 2. We have to design byte code for FunctionalScript and implement its deserializer in Rust.
34
+
35
+ ### Build Process
36
+
37
+ To satisfy the first requirement, we need a parser written in FunctionalScript. The build process should then take the parser source code and embed it into the application. See [include_str](https://doc.rust-lang.org/std/macro.include_str.html) for more details.
38
+
39
+ ```mermaid
40
+ flowchart TB
41
+ app[Application] --> deno(deno_core)
42
+ app --> bcd[Byte Code Deserializer] --> js[VM] --> mem[Memory Manager]
43
+ app --> fsp[(Functional Script Parser Source Code)]
44
+ ```
45
+
46
+ ### Run-Time Process
47
+
48
+ 1. The application loads the parser source code from memory into the Deno engine.
49
+ 2. The application executes the parser in the JS engine with the command-line parameters provided by the user.
50
+ 3. After the parser generates the byte code, the application sends this byte code to the VM.
51
+
52
+ This stage provides an interim solution to parse FunctionalScript using Deno while we develop the self-hosted parser.
53
+
54
+ ## Stage 2: Moving the Deno into `dev-dependencies`
55
+
56
+ Once our parser can parse itself and convert it into byte code, we can move the `deno_core` to development dependencies. This means that we need it only for build time. This transition eliminates the runtime dependency on Deno, streamlining deployment and reducing overhead in production environments.
57
+
58
+ ### Build Process
59
+
60
+ 1. Run the parser on itself using Deno and generate byte code for the parser.
61
+ 2. Embed the generated byte code into our application ([include_bytes](https://doc.rust-lang.org/std/macro.include_bytes.html)).
62
+
63
+ ### Run-Time Process
64
+
65
+ ```mermaid
66
+ flowchart TB
67
+ app[Application] --> bcd[Byte Code Deserializer] --> js[VM] --> mem[Memory Manager]
68
+ app --> fspbc[(Functional Script Parser Byte Code)]
69
+ ```
70
+
71
+ The application can do two things:
72
+ 1. Convert FunctionalScript code into byte code using the parser source code and the VM.
73
+ 2. Execute byte code using the VM.
74
+
75
+ ## Byte code
76
+
77
+ The byte code format is designed for fast and straightforward deserialization and doesn't depend on a particular VM implementation. This design ensures that the byte code remains portable and does not rely on a specific VM implementation, allowing flexibility in integrating with other environments, including content-addressable implementation of FunctionalScript.
78
+
79
+ ### Requirements
80
+
81
+ - **Simple deserialization**: `string` is UTF16, `number` and `bigint` in a binary format, `usize` is 32 bits.
82
+ - **No imports**: byte code describes a standalone module without dependencies. A parser should resolve all imports.
83
+ - **No syntax sugar**: a parser should convert all syntax sugar operations into basic commands.
84
+ - **One unit is one byte**: Byte code can be serialized into a byte array or a file.
85
+ - **Least significant byte first**: Our `bigint` implementation is LSB first, and most current CPU architectures are.
86
+
87
+ ### Pseudo-Code for the byte code binary format
88
+
89
+ We use syntax that looks like Rust to describe the binary format.
90
+
91
+ ```rust
92
+ struct Array<T> {
93
+ len: u32,
94
+ array: [T; self.len],
95
+ }
96
+
97
+ type String = Array<u16>;
98
+
99
+ // LSB first.
100
+ type BigUInt = Array<u64>;
101
+
102
+ type Object = Array<(String, Any)>;
103
+
104
+ type Tag = u8
105
+
106
+ // This is the main structure for serialization.
107
+ type Code = Array<u8>;
108
+ ```
109
+
110
+ ### Tags/commands for JSON and DJS.
111
+
112
+ |format|any |Tag| |
113
+ |------|--------------|---|-----------------------|
114
+ |JSON |null | 00| |
115
+ | |number | 01|u64 |
116
+ | |false | 02| |
117
+ | |true | 03| |
118
+ | |string | 04|String |
119
+ | |object | 05|Object |
120
+ | |array | 06|Array<Any> |
121
+ |DJS |reference | 07|u32 |
122
+ | |bigint+ | 08|BigUInt |
123
+ | |bigint- | 09|BigUInt |
124
+
125
+ We will add new tags for FunctionalScript byte code in the future as they are not needed for Stage 1.
126
+
127
+ ## Summary
128
+
129
+ If you find this architecture promising and want to support the development of a standalone FunctionalScript VM, please consider [sponsoring the project ❤️](https://opencollective.com/functionalscript). Additionally, we welcome contributions and feedback from the open-source community to accelerate this initiative.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.0.565",
3
+ "version": "0.0.567",
4
4
  "description": "FunctionalScript is a functional subset of JavaScript",
5
5
  "main": "module.f.cjs",
6
6
  "scripts": {
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "homepage": "https://github.com/functionalscript/functionalscript#readme",
32
32
  "devDependencies": {
33
- "@types/node": "^22.9.0",
33
+ "@types/node": "^22.9.1",
34
34
  "typescript": "^5.6.3"
35
35
  }
36
36
  }
@@ -34,15 +34,28 @@ const fromCodePointList = flatMap(codePointToUtf8)
34
34
 
35
35
  /** @type {(state: Utf8NonEmptyState) => i32}*/
36
36
  const utf8StateToError = state => {
37
+ let x
37
38
  switch (state.length) {
38
- case 1:
39
- return state[0] | errorMask
40
- case 2:
41
- if (state[0] < 0b1111_0000) return (((state[0] & 0b0000_1111) << 6) + (state[1] & 0b0011_1111) + 0b0000_0100_0000_0000) | errorMask
42
- return (((state[0] & 0b0000_0111) << 6) + (state[1] & 0b0011_1111) + 0b0000_0010_0000_0000) | errorMask
43
- case 3:
44
- return (((state[0] & 0b0000_0111) << 12) + ((state[1] & 0b0011_1111) << 6) + (state[2] & 0b0011_1111) + 0b1000_0000_0000_0000) | errorMask
39
+ case 1: {
40
+ [x] = state
41
+ break
42
+ }
43
+ case 2: {
44
+ const [s0, s1] = state
45
+ x = s0 < 0b1111_0000
46
+ ? ((s0 & 0b0000_1111) << 6) + (s1 & 0b0011_1111) + 0b0000_0100_0000_0000
47
+ : ((s0 & 0b0000_0111) << 6) + (s1 & 0b0011_1111) + 0b0000_0010_0000_0000
48
+ break
49
+ }
50
+ case 3: {
51
+ const [s0, s1, s2] = state
52
+ x = ((s0 & 0b0000_0111) << 12) + ((s1 & 0b0011_1111) << 6) + (s2 & 0b0011_1111) + 0b1000_0000_0000_0000
53
+ break
54
+ }
55
+ default:
56
+ throw 'invalid state'
45
57
  }
58
+ return x | errorMask
46
59
  }
47
60
 
48
61
  /** @type {operator.StateScan<number, Utf8State, list.List<i32>>} */
@@ -57,16 +70,22 @@ const utf8ByteToCodePointOp = state => byte => {
57
70
  }
58
71
  if (byte >= 0b1000_0000 && byte < 0b1100_0000) {
59
72
  switch (state.length) {
60
- case 1:
61
- if (state[0] < 0b1110_0000) { return [[((state[0] & 0b0001_1111) << 6) + (byte & 0b0011_1111)], null] }
62
- if (state[0] < 0b1111_1000) { return [[], [state[0], byte]] }
73
+ case 1: {
74
+ const [s0] = state
75
+ if (s0 < 0b1110_0000) { return [[((s0 & 0b0001_1111) << 6) + (byte & 0b0011_1111)], null] }
76
+ if (s0 < 0b1111_1000) { return [[], [s0, byte]] }
63
77
  break
64
- case 2:
65
- if (state[0] < 0b1111_0000) { return [[((state[0] & 0b0000_1111) << 12) + ((state[1] & 0b0011_1111) << 6) + (byte & 0b0011_1111)], null] }
66
- if (state[0] < 0b1111_1000) { return [[], [state[0], state[1], byte]] }
78
+ }
79
+ case 2: {
80
+ const [s0, s1] = state
81
+ if (s0 < 0b1111_0000) { return [[((s0 & 0b0000_1111) << 12) + ((s1 & 0b0011_1111) << 6) + (byte & 0b0011_1111)], null] }
82
+ if (s0 < 0b1111_1000) { return [[], [s0, s1, byte]] }
67
83
  break
68
- case 3:
69
- return [[((state[0] & 0b0000_0111) << 18) + ((state[1] & 0b0011_1111) << 12) + ((state[2] & 0b0011_1111) << 6) + (byte & 0b0011_1111)], null]
84
+ }
85
+ case 3: {
86
+ const [s0, s1, s2] = state
87
+ return [[((s0 & 0b0000_0111) << 18) + ((s1 & 0b0011_1111) << 12) + ((s2 & 0b0011_1111) << 6) + (byte & 0b0011_1111)], null]
88
+ }
70
89
  }
71
90
  }
72
91
  const error = utf8StateToError(state)
@@ -76,10 +95,8 @@ const utf8ByteToCodePointOp = state => byte => {
76
95
  }
77
96
 
78
97
  /** @type {(state: Utf8State) => readonly[list.List<i32>, Utf8State]} */
79
- const utf8EofToCodePointOp = state => {
80
- if (state === null) { return [null, null] }
81
- return [[utf8StateToError(state)], null]
82
- }
98
+ const utf8EofToCodePointOp = state =>
99
+ [state === null ? null : [utf8StateToError(state)], null]
83
100
 
84
101
  /** @type {operator.StateScan<ByteOrEof, Utf8State, list.List<i32>>} */
85
102
  const utf8ByteOrEofToCodePointOp = state => input => input === null ? utf8EofToCodePointOp(state) : utf8ByteToCodePointOp(state)(input)
@@ -7,7 +7,7 @@ const { list } = require('../module.f.cjs')
7
7
  const operator = require("../function/operator/module.f.cjs")
8
8
 
9
9
  /** @type {(a: readonly json.Unknown[]) => string} */
10
- const stringify = a => json.stringify(sort)(a)
10
+ const stringify = json.stringify(sort)
11
11
 
12
12
  /** @type {_.Operators<sortedSet.SortedSet<string>>} */
13
13
  const op = { union: sortedSet.union(unsafeCmp), equal: list.equal(operator.strictEqual) }