harmonyc 0.22.0 β 0.23.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/README.md
CHANGED
|
@@ -1,142 +1,271 @@
|
|
|
1
1
|
# Harmony Code
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Write unit tests that speak human language**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Harmony Code transforms how you write and maintain tests. Instead of wrestling with complex test code, you describe _what_ you want to test in plain English (or whichever language), and Harmony generates the Vitest test code for you.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## β¨ Why Harmony Code?
|
|
8
|
+
|
|
9
|
+
**π Think in scenarios, not syntax**
|
|
10
|
+
Focus on business logic and user flows instead of test framework boilerplate.
|
|
11
|
+
|
|
12
|
+
**π Tests as living documentation**
|
|
13
|
+
Your `.harmony` files become readable specifications that anyone can understand.
|
|
14
|
+
|
|
15
|
+
**ποΈ Separation of concerns**
|
|
16
|
+
Test design (`.harmony`) stays separate from test implementation (`.phrases.ts`).
|
|
17
|
+
|
|
18
|
+
**β‘ Vitest integration**
|
|
19
|
+
Get all the benefits of Vitest's speed and developer experience.
|
|
20
|
+
|
|
21
|
+
## π Quick Start
|
|
22
|
+
|
|
23
|
+
### 1. Install Harmony Code
|
|
8
24
|
|
|
9
25
|
```bash
|
|
10
26
|
npm install harmonyc
|
|
11
27
|
```
|
|
12
28
|
|
|
13
|
-
|
|
29
|
+
### 2. Configure Vitest
|
|
30
|
+
|
|
31
|
+
Add Harmony plugin to your `vitest.config.js` or `vite.config.js` and include `.harmony` files in tests:
|
|
14
32
|
|
|
15
33
|
```js
|
|
16
34
|
import harmony from 'harmonyc/vitest'
|
|
17
35
|
|
|
18
36
|
export default {
|
|
19
37
|
plugins: [harmony()],
|
|
20
|
-
|
|
38
|
+
test: {
|
|
39
|
+
include: ['**/*.{test,spec}.{js,ts}', '**/*.harmony'],
|
|
40
|
+
},
|
|
21
41
|
}
|
|
22
42
|
```
|
|
23
43
|
|
|
24
|
-
|
|
44
|
+
### 3. Write your first test
|
|
25
45
|
|
|
26
|
-
|
|
46
|
+
Create `formatCurrency.harmony`:
|
|
27
47
|
|
|
28
|
-
|
|
48
|
+
```harmony
|
|
49
|
+
# Currency Formatting
|
|
29
50
|
|
|
30
|
-
|
|
51
|
+
+ Dollar formatting:
|
|
52
|
+
- format amount `123.45` => "$123.45"
|
|
53
|
+
- format amount `100` => "$100.00"
|
|
31
54
|
|
|
32
|
-
|
|
55
|
+
+ Euro formatting:
|
|
56
|
+
- format amount `67.98` with "EUR" => "β¬67.98"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 4. Implement test steps
|
|
60
|
+
|
|
61
|
+
Harmony automatically generates `formatCurrency.phrases.ts` for you, adding new method stubs and keeping your existing implementations. It intelligently sorts methods alphabetically within `When_` and `Then_` categories. Fill them in like this:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { expect } from 'vitest'
|
|
65
|
+
import { formatCurrency } from '../src/currency'
|
|
33
66
|
|
|
34
|
-
|
|
67
|
+
export default class FormatCurrencyPhrases {
|
|
68
|
+
async When_format_amount_X(amount: number) {
|
|
69
|
+
return formatCurrency(amount)
|
|
70
|
+
}
|
|
35
71
|
|
|
72
|
+
async When_format_amount_X_with_Y(amount: number, currency: string) {
|
|
73
|
+
return formatCurrency(amount, currency)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async Then_X(expected: string, actual: string) {
|
|
77
|
+
expect(actual).toBe(expected)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
36
80
|
```
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- authenticate with "admin" => product count `0`
|
|
43
|
-
- create product
|
|
44
|
-
=> product created
|
|
45
|
-
=> product count `1`
|
|
46
|
-
- Delete:
|
|
47
|
-
- delete product => product deleted => product count `0`
|
|
81
|
+
|
|
82
|
+
### 5. Run your tests
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx vitest
|
|
48
86
|
```
|
|
49
87
|
|
|
50
|
-
|
|
88
|
+
That's it! Your tests run just like regular Vitest tests, but with the clarity of human language.
|
|
51
89
|
|
|
52
|
-
|
|
90
|
+
## π IDE Support
|
|
53
91
|
|
|
54
|
-
|
|
92
|
+
**VS Code Extension**: Get syntax highlighting and IntelliSense for `.harmony` files.
|
|
55
93
|
|
|
56
|
-
|
|
94
|
+
Install: [Harmony Code Extension](https://marketplace.visualstudio.com/items?itemName=harmony-ac.harmony-code)
|
|
57
95
|
|
|
58
|
-
|
|
96
|
+
**Vitest Integration**: Run and debug tests directly from VS Code using the official Vitest extension.
|
|
59
97
|
|
|
60
|
-
|
|
98
|
+
## π Harmony Code Syntax Guide
|
|
61
99
|
|
|
62
|
-
|
|
100
|
+
### Test Structure
|
|
63
101
|
|
|
64
|
-
|
|
102
|
+
Harmony Code uses indentation and symbols to create test scenarios:
|
|
65
103
|
|
|
66
|
-
|
|
104
|
+
- **`+ `** creates test **forks** - each becomes a separate test case
|
|
105
|
+
- **`- `** creates **sequential steps** within the same test case
|
|
106
|
+
- **Lines ending with `:`** are sections for organizing tests
|
|
67
107
|
|
|
68
|
-
|
|
108
|
+
### Actions and Expectations
|
|
109
|
+
|
|
110
|
+
Each step can have an action and expected outcomes:
|
|
69
111
|
|
|
70
112
|
```harmony
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
+ code fragment:
|
|
74
|
-
+ greet `3` times
|
|
113
|
+
- action => expected result
|
|
114
|
+
- action => !! "expected error message"
|
|
75
115
|
```
|
|
76
116
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```javascript
|
|
80
|
-
test('T1 - strings', async () => {
|
|
81
|
-
const P = new Phrases()
|
|
82
|
-
await P.When_hello_('John')
|
|
83
|
-
})
|
|
84
|
-
test('T2 - code fragment', async () => {
|
|
85
|
-
const P = new Phrases()
|
|
86
|
-
await P.When_greet__times(3)
|
|
87
|
-
})
|
|
88
|
-
```
|
|
117
|
+
**Actions** become `When_*` methods, **expectations** become `Then_*` methods.
|
|
89
118
|
|
|
90
|
-
###
|
|
119
|
+
### Parameters
|
|
91
120
|
|
|
92
|
-
|
|
93
|
-
|
|
121
|
+
Use quotes for strings and backticks for other values:
|
|
122
|
+
|
|
123
|
+
```harmony
|
|
124
|
+
- login with "john@example.com" remember `true` => user logged in
|
|
125
|
+
```
|
|
94
126
|
|
|
95
|
-
|
|
127
|
+
Becomes:
|
|
96
128
|
|
|
97
|
-
|
|
129
|
+
```typescript
|
|
130
|
+
async When_login_with_X_remember_Y(email: string, rememberMe: boolean) {
|
|
131
|
+
// your implementation
|
|
132
|
+
}
|
|
133
|
+
```
|
|
98
134
|
|
|
99
|
-
|
|
135
|
+
Parameters are mapped to `X`, `Y`, `Z`, `A`, `B`, `C`... (alphabetically) in method names.
|
|
100
136
|
|
|
101
|
-
|
|
137
|
+
### Example: User Authentication
|
|
102
138
|
|
|
103
139
|
```harmony
|
|
104
|
-
|
|
140
|
+
# User Authentication
|
|
141
|
+
|
|
142
|
+
+ successful login:
|
|
143
|
+
- enter email "user@test.com"
|
|
144
|
+
- enter password "password123"
|
|
145
|
+
- click login button => redirected to dashboard
|
|
146
|
+
|
|
147
|
+
+ failed login:
|
|
148
|
+
- enter email "wrong@test.com"
|
|
149
|
+
- enter password "wrongpass"
|
|
150
|
+
- click login button => !! "Invalid credentials"
|
|
151
|
+
|
|
152
|
+
+ password requirements:
|
|
153
|
+
+ too short:
|
|
154
|
+
- set password "123" => !! "Password must be at least 8 characters"
|
|
155
|
+
+ missing special character:
|
|
156
|
+
- set password "password123" => !! "Password must contain a special character"
|
|
105
157
|
```
|
|
106
158
|
|
|
107
|
-
|
|
159
|
+
## π How It Works
|
|
160
|
+
|
|
161
|
+
1. **Write scenarios** in `.harmony` files using human-readable language
|
|
162
|
+
2. **Harmony generates** the corresponding Vitest test structure
|
|
163
|
+
3. **Harmony updates** your `.phrases.ts` files, adding new method stubs while preserving existing implementations
|
|
164
|
+
4. **You implement** the step methods (only the new ones need your attention)
|
|
165
|
+
5. **Run tests** with Vitest watch mode automatically, or with VSCode Vitest extension
|
|
166
|
+
|
|
167
|
+
The beauty is in the separation: your test scenarios remain clean and business-focused, while implementation details live in separate files.
|
|
108
168
|
|
|
109
|
-
|
|
169
|
+
### Smart Code Generation
|
|
170
|
+
|
|
171
|
+
- **Preserves your work**: Existing method implementations are never overwritten
|
|
172
|
+
- **Adds only what's needed**: New method stubs are generated for missing steps
|
|
173
|
+
- **Removes unused methods**: Methods no longer referenced in `.harmony` files are cleaned up
|
|
174
|
+
- **Keeps things organized**: Methods are sorted alphabetically within `When_` and `Then_` categories
|
|
175
|
+
|
|
176
|
+
## π― Advanced Features
|
|
110
177
|
|
|
111
178
|
### Variables
|
|
112
179
|
|
|
113
|
-
|
|
180
|
+
Store and reuse values across test steps:
|
|
181
|
+
|
|
182
|
+
```harmony
|
|
183
|
+
+ user workflow:
|
|
184
|
+
- create user => ${userId}
|
|
185
|
+
- login with user id "${userId}" => success
|
|
186
|
+
- delete user "${userId}" => user removed
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Test Switches
|
|
190
|
+
|
|
191
|
+
Generate multiple test cases with variations:
|
|
114
192
|
|
|
193
|
+
```harmony
|
|
194
|
+
+ password validation:
|
|
195
|
+
- password { "123" / "abc" / "" } => !! "Invalid password"
|
|
115
196
|
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
197
|
+
|
|
198
|
+
This creates three separate test cases, one for each password value.
|
|
199
|
+
|
|
200
|
+
### Error Testing
|
|
201
|
+
|
|
202
|
+
Use `!!` to test error conditions. The error message is optional - if provided, it checks that the thrown error contains that substring:
|
|
203
|
+
|
|
204
|
+
```harmony
|
|
205
|
+
+ division by zero:
|
|
206
|
+
- divide `10` by `0` => !! "Cannot divide by zero"
|
|
207
|
+
- divide `5` by `0` => !! # just checks that an error is thrown
|
|
122
208
|
```
|
|
123
209
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
210
|
+
**No implementation needed**: Error steps don't require corresponding methods in your phrases file.
|
|
211
|
+
|
|
212
|
+
### Complex Scenarios
|
|
213
|
+
|
|
214
|
+
Build sophisticated test flows:
|
|
215
|
+
|
|
216
|
+
```harmony
|
|
217
|
+
# E-commerce Checkout
|
|
218
|
+
|
|
219
|
+
+ guest checkout:
|
|
220
|
+
- add product "T-shirt" to cart
|
|
221
|
+
- go to checkout
|
|
222
|
+
- enter shipping address:
|
|
223
|
+
- name "John Doe"
|
|
224
|
+
- address "123 Main St"
|
|
225
|
+
- select payment method "credit card"
|
|
226
|
+
- complete purchase => order confirmation
|
|
227
|
+
|
|
228
|
+
+ member checkout:
|
|
229
|
+
- login as "member@test.com"
|
|
230
|
+
- add product "Laptop" to cart
|
|
231
|
+
- use saved address => address populated
|
|
232
|
+
- complete purchase => order confirmation
|
|
233
|
+
- check order history => order appears
|
|
138
234
|
```
|
|
139
235
|
|
|
140
|
-
##
|
|
236
|
+
## π§Ή Best Practices
|
|
237
|
+
|
|
238
|
+
**Use natural language**: Write steps as if explaining to a manual tester. Write in the language you use for discussing requirements.
|
|
239
|
+
**Keep scenarios focused**: Each test should verify one main behavior
|
|
240
|
+
**Use descriptive names**: Make test intentions clear from the scenario text
|
|
241
|
+
**Use parameter-only names** if there is an obivous single-parameter input or output (e.g., `` => `true` ``, which will be `Then_X(x)` )
|
|
242
|
+
**Group related tests**: Use sections (lines ending with `:`) to organize
|
|
243
|
+
**Test edge cases**: Include error conditions and boundary values
|
|
244
|
+
**Maintain phrase files**: Keep step implementations simple and focused, add JSDoc if step documentation is needed
|
|
245
|
+
|
|
246
|
+
## β¨οΈ CLI Usage
|
|
247
|
+
|
|
248
|
+
Compile `.harmony` files manually:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
npx harmonyc "src/**/*.harmony"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Watch for changes:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
npx harmonyc "src/**/*.harmony" --watch
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## π¦ Package Structure
|
|
261
|
+
|
|
262
|
+
- **`harmonyc`** - Core compiler and Vitest plugin
|
|
263
|
+
- **VS Code Extension** - Syntax highlighting and language support
|
|
264
|
+
|
|
265
|
+
## π€ Contributing
|
|
266
|
+
|
|
267
|
+
Harmony Code is open source and welcomes contributions. Check out our [GitHub repository](https://github.com/harmony-ac/code) for issues, feature requests, and development setup.
|
|
268
|
+
|
|
269
|
+
## π License
|
|
141
270
|
|
|
142
|
-
MIT
|
|
271
|
+
MIT - see [LICENSE](LICENSE) for details.
|
|
@@ -22,7 +22,7 @@ export class VitestGenerator {
|
|
|
22
22
|
this.extraArgs = [];
|
|
23
23
|
}
|
|
24
24
|
feature(feature) {
|
|
25
|
-
const phrasesModule = './' + basename(this.sourceFileName.replace(/\.harmony$/, '.phrases
|
|
25
|
+
const phrasesModule = './' + basename(this.sourceFileName.replace(/\.harmony$/, '.phrases'));
|
|
26
26
|
const fn = (this.featureClassName =
|
|
27
27
|
this.currentFeatureName =
|
|
28
28
|
pascalCase(feature.name) + 'Phrases');
|
package/dist/model/model.d.ts
CHANGED
|
@@ -121,12 +121,6 @@ export declare class Switch extends Part {
|
|
|
121
121
|
toString(): string;
|
|
122
122
|
toSingleLineString(): string;
|
|
123
123
|
}
|
|
124
|
-
export declare class Router extends Part {
|
|
125
|
-
choices: Repeater[];
|
|
126
|
-
constructor(choices: Repeater[]);
|
|
127
|
-
toString(): string;
|
|
128
|
-
toSingleLineString(): string;
|
|
129
|
-
}
|
|
130
124
|
export declare abstract class Arg extends Part {
|
|
131
125
|
abstract toCode(cg: CodeGenerator): string;
|
|
132
126
|
abstract toDeclaration(cg: CodeGenerator, index: number): string;
|
package/dist/model/model.js
CHANGED
|
@@ -210,12 +210,12 @@ export class Repeater extends Part {
|
|
|
210
210
|
this.choices = choices;
|
|
211
211
|
}
|
|
212
212
|
toString() {
|
|
213
|
-
return `{${this.choices.map((ps) => ps.join(' ')).join('
|
|
213
|
+
return `{${this.choices.map((ps) => ps.join(' ')).join(' && ')}}`;
|
|
214
214
|
}
|
|
215
215
|
toSingleLineString() {
|
|
216
216
|
return `{${this.choices
|
|
217
217
|
.map((ps) => ps.map((p) => p.toSingleLineString()).join(' '))
|
|
218
|
-
.join('
|
|
218
|
+
.join(' && ')}}`;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
export class Switch extends Part {
|
|
@@ -224,22 +224,10 @@ export class Switch extends Part {
|
|
|
224
224
|
this.choices = choices;
|
|
225
225
|
}
|
|
226
226
|
toString() {
|
|
227
|
-
return `{ ${this.choices.join('
|
|
227
|
+
return `{ ${this.choices.join('; ')} }`;
|
|
228
228
|
}
|
|
229
229
|
toSingleLineString() {
|
|
230
|
-
return `{ ${this.choices.map((c) => c.toSingleLineString()).join('
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
export class Router extends Part {
|
|
234
|
-
constructor(choices) {
|
|
235
|
-
super();
|
|
236
|
-
this.choices = choices;
|
|
237
|
-
}
|
|
238
|
-
toString() {
|
|
239
|
-
return `{ ${this.choices.join(' ; ')} }`;
|
|
240
|
-
}
|
|
241
|
-
toSingleLineString() {
|
|
242
|
-
return `{ ${this.choices.map((c) => c.toSingleLineString()).join(' ; ')} }`;
|
|
230
|
+
return `{ ${this.choices.map((c) => c.toSingleLineString()).join('; ')} }`;
|
|
243
231
|
}
|
|
244
232
|
}
|
|
245
233
|
export class Arg extends Part {
|
|
@@ -13,8 +13,7 @@ export declare enum T {
|
|
|
13
13
|
ClosingBracket = "]",
|
|
14
14
|
OpeningBrace = "{",
|
|
15
15
|
ClosingBrace = "}",
|
|
16
|
-
And = "
|
|
17
|
-
Slash = "/",
|
|
16
|
+
And = "&&",
|
|
18
17
|
Semicolon = ";",
|
|
19
18
|
DoubleQuoteString = "double-quote string",
|
|
20
19
|
UnclosedDoubleQuoteString = "unclosed double-quote string",
|
|
@@ -14,8 +14,7 @@ export var T;
|
|
|
14
14
|
T["ClosingBracket"] = "]";
|
|
15
15
|
T["OpeningBrace"] = "{";
|
|
16
16
|
T["ClosingBrace"] = "}";
|
|
17
|
-
T["And"] = "
|
|
18
|
-
T["Slash"] = "/";
|
|
17
|
+
T["And"] = "&&";
|
|
19
18
|
T["Semicolon"] = ";";
|
|
20
19
|
T["DoubleQuoteString"] = "double-quote string";
|
|
21
20
|
T["UnclosedDoubleQuoteString"] = "unclosed double-quote string";
|
|
@@ -46,8 +45,7 @@ const rules = [
|
|
|
46
45
|
[true, /\]/y, T.ClosingBracket],
|
|
47
46
|
[true, /\{/y, T.OpeningBrace],
|
|
48
47
|
[true, /\}/y, T.ClosingBrace],
|
|
49
|
-
[true,
|
|
50
|
-
[true, /&/y, T.And],
|
|
48
|
+
[true, /&&/y, T.And],
|
|
51
49
|
[true, /;/y, T.Semicolon],
|
|
52
50
|
[true, /!!/y, T.ErrorMark],
|
|
53
51
|
[true, /=>/y, T.ResponseArrow],
|
|
@@ -69,6 +67,6 @@ const rules = [
|
|
|
69
67
|
[true, /\$\{[^}\n]*/y, T.UnclosedVariable],
|
|
70
68
|
[true, /\|(?: .*|(?=\n|$))/y, T.MultilineString],
|
|
71
69
|
[true, /\|[^ \n]/y, T.InvalidMultilineStringMark],
|
|
72
|
-
[true, /.+?(?=[\[\]"`|#{}
|
|
70
|
+
[true, /.+?(?=[\[\]"`|#{};]|\$\{|$|=>|!!|&&|\/\/|:\s*$)/my, T.Words],
|
|
73
71
|
];
|
|
74
72
|
export default rules;
|
package/dist/parser/parser.js
CHANGED
|
@@ -31,9 +31,7 @@ export const NEWLINES = list_sc(tok(T.Newline), nil()), WORDS = apply(tok(T.Word
|
|
|
31
31
|
//list_sc(rep_sc(SIMPLE_PART), tok(T.And)),
|
|
32
32
|
//(cs) => new Repeater(cs)
|
|
33
33
|
//),
|
|
34
|
-
SWITCH = apply(list_sc(SIMPLE_PART, tok(T.
|
|
35
|
-
//ROUTER = apply(list_sc(REPEATER, tok(T.Semicolon)), (cs) => new Router(cs)),
|
|
36
|
-
BRACES = kmid(tok(T.OpeningBrace), SWITCH, tok(T.ClosingBrace)), PART = alt_sc(SIMPLE_PART, BRACES), PHRASE = rep_sc(PART), ARG = alt_sc(DOUBLE_QUOTE_STRING, BACKTICK_STRING, DOCSTRING), SET_VARIABLE = apply(seq(VARIABLE, ARG), ([variable, value]) => new SetVariable(variable, value)), ACTION = alt_sc(SET_VARIABLE, apply(PHRASE, (parts, range) => new Action(parts).at(range))), RESPONSE = apply(seq(PHRASE, opt_sc(VARIABLE)), ([parts, variable], range) => new Response(parts, variable !== undefined ? new SaveToVariable(variable) : undefined).at(range)), ERROR_RESPONSE = apply(seq(ERROR_MARK, opt_sc(alt_sc(DOUBLE_QUOTE_STRING, DOCSTRING))), ([, parts]) => new ErrorResponse(parts)), SAVE_TO_VARIABLE = apply(VARIABLE, (variable) => new Response([], new SaveToVariable(variable))), ARROW = kright(opt_sc(NEWLINES), tok(T.ResponseArrow)), RESPONSE_ITEM = kright(ARROW, alt_sc(SAVE_TO_VARIABLE, ERROR_RESPONSE, RESPONSE)), STEP = apply(seq(ACTION, rep_sc(RESPONSE_ITEM)), ([action, responses]) => new Step(action, responses).setFork(true)), LABEL = apply(kleft(list_sc(PART, nil()), tok(T.Colon)), (words, range) => new Label(words.map((w) => w.toString()).join(' ')).at(range)), SECTION = apply(LABEL, (text) => new Section(text)), BRANCH = apply(alt_sc(SECTION, STEP), (branch, range) => branch.at(range)), // section first, to make sure there is no colon after step
|
|
34
|
+
SWITCH = apply(list_sc(SIMPLE_PART, tok(T.Semicolon)), (cs) => new Switch(cs)), BRACES = kmid(tok(T.OpeningBrace), SWITCH, tok(T.ClosingBrace)), PART = alt_sc(SIMPLE_PART, BRACES), PHRASE = rep_sc(PART), ARG = alt_sc(DOUBLE_QUOTE_STRING, BACKTICK_STRING, DOCSTRING), SET_VARIABLE = apply(seq(VARIABLE, ARG), ([variable, value]) => new SetVariable(variable, value)), ACTION = alt_sc(SET_VARIABLE, apply(PHRASE, (parts, range) => new Action(parts).at(range))), RESPONSE = apply(seq(PHRASE, opt_sc(VARIABLE)), ([parts, variable], range) => new Response(parts, variable !== undefined ? new SaveToVariable(variable) : undefined).at(range)), ERROR_RESPONSE = apply(seq(ERROR_MARK, opt_sc(alt_sc(DOUBLE_QUOTE_STRING, DOCSTRING))), ([, parts]) => new ErrorResponse(parts)), SAVE_TO_VARIABLE = apply(VARIABLE, (variable) => new Response([], new SaveToVariable(variable))), ARROW = kright(opt_sc(NEWLINES), tok(T.ResponseArrow)), RESPONSE_ITEM = kright(ARROW, alt_sc(SAVE_TO_VARIABLE, ERROR_RESPONSE, RESPONSE)), STEP = apply(seq(ACTION, rep_sc(RESPONSE_ITEM)), ([action, responses]) => new Step(action, responses).setFork(true)), LABEL = apply(kleft(list_sc(PART, nil()), tok(T.Colon)), (words, range) => new Label(words.map((w) => w.toString()).join(' ')).at(range)), SECTION = apply(LABEL, (text) => new Section(text)), BRANCH = apply(alt_sc(SECTION, STEP), (branch, range) => branch.at(range)), // section first, to make sure there is no colon after step
|
|
37
35
|
DENTS = apply(alt_sc(tok(T.Plus), tok(T.Minus)), (seqOrFork) => {
|
|
38
36
|
return {
|
|
39
37
|
dent: (seqOrFork.text.length - 2) / 2,
|
package/package.json
CHANGED