phonet 0.9.4

A CLI tool and library to validate phonotactic patterns for constructed languages
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# Phoner

_Phonet_ is a CLI tool and library to validate phonotactic patterns for constructed languages.
It is compatible with either romanization and phonetic transcription.
Words can be randomly generated (see [Argument Syntax](#argument-syntax)).

[Syntax Highlighting Extension for VSCode](https://github.com/darccyy/phonet-syntax)

> Formerly named 'Phoner'

# Usage

This project can be used as a rust library crate, or as a binary executable.

## Binary use

[Download latest version here](https://github.com/darccyy/phonet/releases/latest)

### Argument Syntax

```
Usage: phonet.exe [OPTIONS] [TESTS]...

Arguments:
  [TESTS]...
          Custom tests (optional)

          This overrides all tests in the file

Options:
  -f, --file <FILE>
          Name and path of file to run and test

          If name ends with a period, the 'phonet' extension is implied

          Eg. `phonet -f ./myfile.phonet` or `phonet -f ./myfile.` (same result)

          [default: phonet]

  -q, --quiet
          Don't display passes and notes, only fails

  -m, --minify
          Minify file and save

  -w, --with-tests
          Include tests in minified file

  -g, --generate [<GENERATE>]
          Generate random words

          Default count 1, specify with number

      --gmin <GENERATE_MIN_LEN>
          Set minimum length (inclusive) for generated words

          Use with the `--generate` or `-g` flag

          Note: This increases generation time exponentially

      --gmax <GENERATE_MAX_LEN>
          Set maximum length (inclusive) for generated words

          Use with the `--generate` or `-g` flag

  -n, --no-color
          Display output in default color

          Use for piping standard output to a file

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version
```

### Example

```bash
# Runs ./phonet
phonet

# Runs ./phonet, with tests: 'some', 'tests' (overrides the tests in file)
phonet some tests

# Runs ./myfile.phonet
phonet -f myfile.phonet
phonet -f myfile.phonet some tests

# Runs ./phonet, only showing fails
phonet -q

# Runs ./phonet, and minifies to ./min.phonet without tests
phonet -m

# Runs ./myfile.phonet, only displaying fails, and minifies to ./myfile.min.phonet with tests
phonet -f myfile.phonet -q -mw

# Runs ./phonet, and generates 1 random word
phonet -g

# Runs ./myfile.phonet, and generates 10 random words
phonet -g10 -f myfile.phonet

# Runs ./phonet, with no color, and writes output to ./phonet.txt
phonet -n > phonet.txt

# Runs ./myfile.phonet, only displaying fails, and generates 3 random words with length 6-8, writes output to ./phonet.txt (with no color)
phonet -f myfile.phonet -qn -g 3 --gmin 6 --gmax 8 > ./phonet.txt
```

### Create Alias / Path

Replace `<path_to_file>` with the directory of the downloaded binary.

#### Bash

Add alias in `.bashrc` in user directory

```bash
# ~/.bashrc
alias phonet="<path_to_file>/phonet.exe"
```

#### Powershell

Add to `$env:PATH`

```ps1
$env:Path = "$env:Path;<path_to_file>\phonet.exe"
```

## Library use

Add `phonet = "0.9.3"` to your `Crates.toml` file

- [Docs.rs]https://docs.rs/phonet/latest/phonet
- [Crates.io]https://crates.io/crates/phonet

### Short Example

```rs
use phonet::Draft;

fn main() {
    let file = std::fs::read_to_string("phonet").unwrap();

    // Parse draft
    Draft::from(&file).unwrap()
        // Run tests
        .run()
        // Display results
        .display(Default::default(), true)
}
```

### Long Example

```rs
use std::fs;

use phonet::{
    draft::{Message::Test, TestDraft},
    get_min_filename, DisplayLevel, Draft,
};

fn main() {
    let filename = "myfile.phonet";

    // Read file
    let file = fs::read_to_string(filename).expect("Could not read phonet file");

    // Parse file
    let mut draft = Draft::from(&file).expect("Failed to parse file");

    // Add a custom test
    draft.messages.push(Test(TestDraft {
        intent: true,
        word: "taso".to_string(),
    }));

    // Minify file
    fs::write(
        get_min_filename(filename),
        draft.minify(false).expect("Failed to minify"),
    )
    .expect("Could not write minified file");

    // Run tests and display only failed tests
    draft.run().display(DisplayLevel::OnlyFails, true);

    // Create a generator for random words
    // Each with a length between 5 and 8 (inclusive)
    // Generation is done lazily, similar to an iterator
    println!("Randomly generated words:");
    let mut words = draft
        .generator(5..=8)
        .expect("Failed to create word generator");

    // Generate 10 random words
    for _ in 0..10 {
        println!(" - {}", words.next());
    }
}
```

# File syntax

A _Phonet_ file is used to define the rules, classes, and tests for the program.

The file should either be called `phonet`, or end in `.phonet`

[Syntax Highlighting Extension for VSCode](https://github.com/darccyy/phonet-syntax)

## Statements

The syntax is a statements, each separated by a semicolon `;` or a linebreak.

Use a _Ampersand_ `&` to denote a multi-line statement. This can only be ended with a semicolon `;`.

Comments will end with a linebreak or a semicolon `;`.

All whitespace is ignored, except to separate words in [_tests_](#tests).

> Note! This will replace spaces in Regex as well! Use `\s` if you need a space

Each statement must begin with an operator:

- `#` _Hashtag_: A whole line comment. A linebreak (not a semicolon) ends the comment
- `$` _Dollar_: Define a [_class_]#classes
- `+` **_Plus_** or `!` **_Bang_**: Define a [_rule_]#rule
- `*` _Star_: Create a test [_note_]#notes, and define a _reason_ if a test fails
- `?` _Question_: Create a [_test_]#tests
- `~` _Tilde_: Define the [_mode_]#mode of the file

## Classes

Classes are used as shorthand Regular Expressions, substituted into [_rules_](#rules) at runtime.

> **Note:** Angle brackets will not parse as class names directly after:
>
> - An opening round bracket and a question mark: `(?`
> - An opening round bracket, question mark, and letter 'P': `(?P`
> - A backslash and letter 'k': `\k`
>
> This is the syntax used for look-behinds and named groups

_Syntax:_

- `$` _Dollar_
- Name - Must be only characters from [a-zA-Z0-9_]
- `=` _Equals_
- Value - Regular Expression, may contain other _classes_ in angle brackets `<>` or `⟨⟩` (as with [_rules_]#rules)

The _'any'_ class, defined with `$_ = ...`, is used for random word generation.

_Example:_

```phonet
# Some consonants
$C = [ptksmn]

# Some vowels
$V = [iueoa]

# Only sibilant consonants
$C_s = [sz]
```

## Rules

Rules are Regular Expressions used to test if a word is valid.

Rules are defined with an _intent_, either `+` for _positive_, or `!` for _negative_.

- A _positive_ rule must be followed for a word to be valid
- A _negative_ rule must **not** be followed for a word to be valid

To use a [_class_](#classes), use the class name, surrounded by angle brackets `<>` or `⟨⟩`.

_Syntax:_

- `+` **_Plus_** or `!` **_Bang_** - Plus for _positive_ rule, Bang for _negative_ rule
- Pattern - Regular Expression, may contain [_classes_]#classes in angle brackets `<>` or `⟨⟩`

_Example (with predefined [*classes*](#classes)):_

```phonet
# Must be (C)V syllable structure
+ ^ (<C>? <V>)+ $

# Must not have two vowels in a row
! <V>{2}
```

## Tests

Tests are checked against all rules, and the result is displayed in the output.

Tests are ran in the order of definition.

Like [_rules_](#rules), tests must have a defined _intent_, either `+` for _positive_, or `!` for _negative_.

- A _positive_ test will pass if it is valid
- A _negative_ test will **fail** if it is valid

_Syntax:_

- `?` _Question mark_
- `+` **_Plus_** or `!` **_Bang_** - Plus for _positive_ test, Bang for _negative_ test
- Tests - A word, or multiple words separated by a space

_Example (with predefined [*rules*](#rules)):_

```phonet
# This should match, to pass
?+ taso
# This test should NOT match, to pass
?! tax
# Each word is a test, all should match to pass
?+ taso sato tasa
```

## Notes

Notes are printed to the terminal output, alongside tests.

They are used as a _reason_ for any proceeding rules, as an explanation if a test fails.

_Syntax:_

- `*` _Star_
- Text to print, and define reason as

_Example:_

```phonet
* Syllable structure
+ ^ (<C>? <V>)+ $

# This test will NOT match, however it SHOULD (due to the Plus), so it will FAIL, with the above note as the reason
?+ tasto

* Must not have two vowels in a row
! <V>{2}

?+ taso
```

## Mode

The mode of a _Phonet_ file can be one of these:

- _Romanized_: Using `<>` (not `⟨⟩`)
- _Broad transcription_: Using `//`
- _Narrow transcription_: Using `[]`

This can optionally be specified in a file, although it does not add any functionality.

_Syntax:_

- `~` _Tilde_
- `<.>`, `/./`, or `[.]` - Mode identifier, with `.` being any string, or blank

_Examples:_

```phonet
# Specify romanized mode (fish icon)
~<>
```

```phonet
# Specify broad transcription
~ / this is the mode /
```

## Examples

See the [examples](./examples/) folder for _Phonet_ file examples.

- [Good Syntax Example]./examples/example.phonet
- [Toki Pona]./examples/tokipona.phonet
<!-- - [Ivalingo](./examples/ivalingo.phonet) -->

## Recommended Syntax Patterns

These formatting tips are not required, but recommended to make the file easier to read.

1. Specify the mode at the very top of the file
2. Define all classes at the top of the file
   - Also define an [_'any'_ class]#classes first, for word generation
3. Group related rules and tests, using a note
   - Define rules first, then positive tests, then negative tests
4. Indent rules and tests under note
   - Rules should use 1 intent, tests use 2

_Example (this is from [example.phonet](./examples/example.phonet)):_

```phonet
~<> ;# Mode (optional) - This file uses romanized letters

# Class definitions
$_ = ⟨C⟩ | ⟨V⟩        ;# Any / all letters (required for generating words)
$C = [ptkmnswjl]      ;# Consonants
$V = [aeiou]          ;# Vowels

* Invalid letters     ;# Note - Prints to standard output, and used as reason if test fails
  + ^ ⟨_⟩+ $          ;# Check that every letter is in the 'any' class
    ?+ taso
    ?! tyxo

* Examples of failing tests
    ?+ tyxo           ;# This test will fail - with the reason 'Invalid Letters' (above)
    ?! taso           ;# This test will fail, as a false positive

* Syllable structure
  + ^ ⟨V⟩? ( ⟨C⟩ ⟨V⟩ )+ $  ;# Check that word is Consonant + Vowel, repeating at least once
    ?+ taso kili ano atoso
    ?! taaso an

* Some more tests
    ?+ silo tila
    ?! akka axe

* No repeated letters
  ! (.)\1             ;# This is an unnamed back-reference
  ! (?<x> .) \k<x>    ;# This is a named back-reference (NOT a class)
    ?+ taso           ;# An example of multi-line statements on next line (comments cannot be on same line)
    ?! &
      taaso
      ttaso
    ;

* 2 tests *should* have failed!
```

![Phonet Icon](./icon.png)