loma-lang 1.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Manuel Lomartire
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.
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: loma-lang
3
+ Version: 1.1.0
4
+ Summary: LOMA: a deterministic text language for music that compiles to MIDI
5
+ Author: Manuel Lomartire
6
+ License: MIT
7
+ Project-URL: Homepage, https://manuellomartire.github.io/loma/
8
+ Project-URL: Repository, https://github.com/manuellomartire/loma
9
+ Project-URL: Documentation, https://github.com/manuellomartire/loma/blob/main/SPEC.md
10
+ Project-URL: Changelog, https://github.com/manuellomartire/loma/blob/main/CHANGELOG.md
11
+ Keywords: music,midi,compiler,llm
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # LOMA
22
+
23
+ *English · [Italiano](README.it.md)*
24
+
25
+ LOMA is a text language for describing music, designed to be **written by an AI** and **compiled deterministically to a MIDI file**: the same `.loma` file gives the same `.mid`, byte for byte, every time and with any conforming compiler.
26
+
27
+ - Full specification: [SPEC.md](SPEC.md) (English), [SPEC.it.md](SPEC.it.md) (Italian)
28
+ - Formal grammar: [spec/grammar.ebnf](spec/grammar.ebnf)
29
+ - Changes by version: [CHANGELOG.md](CHANGELOG.md)
30
+ - Playground: https://manuellomartire.github.io/loma/
31
+
32
+ ## An example
33
+
34
+ ```loma
35
+ loma 1.0
36
+ title "First example"
37
+ tempo 100
38
+ meter 4/4
39
+ key Cm
40
+
41
+ track RH ch=1 program=0
42
+ track LH ch=2 program=0
43
+
44
+ pattern arp8 base=3 dur=/8 : 1 3 5 3 1 3 5 3
45
+
46
+ section verse
47
+ bar Cm
48
+ RH{v=78}: Eb5/8 D5/8 C5/4 G4/4 r/4
49
+ LH{v=60}: use arp8
50
+ bar Ab
51
+ RH{v=70>88}: C5/4 Eb5/4{o=-6} Ab4/2{a=leg}
52
+ LH{v=60}: use arp8
53
+ end
54
+
55
+ play verse x2
56
+ ```
57
+
58
+ The AI writes intentions (chord `Cm`, the left hand plays the arpeggio, the melody grows from 70 to 88); the compiler expands them into notes on integer ticks. `/8` is an eighth note, `r` a rest, `{o=-6}` plays 6 ticks early, `x2` repeats the section.
59
+
60
+ ## Playground
61
+
62
+ Try it online: **https://manuellomartire.github.io/loma/**
63
+
64
+ `docs/index.html` is a web page that runs entirely in the browser: paste or edit LOMA, press Compile (or Ctrl/⌘+Enter), read the errors next to their lines (and copy them for the AI that wrote the file), play the result on a piano roll, download the MIDI file, open a `.mid` file to turn it into LOMA, or copy a link that contains the piece. It embeds the JavaScript compiler, so there is no server. It is published with GitHub Pages: in a fork, choose the `docs` folder of the main branch in the repository settings.
65
+
66
+ The sound comes from a small synthesizer inside the page, not from real instruments. After changing `js/loma.js`, the examples or `docs/playground.src.html`, rebuild the page with `python3 tools/build_web.py`.
67
+
68
+ ## Usage
69
+
70
+ Python 3.9 or later, no dependencies. The tests run on Linux, Windows and macOS; the second implementation needs Node.js 18 or later.
71
+
72
+ ```
73
+ python3 -m loma compile song.loma -o song.mid # compile
74
+ python3 -m loma check song.loma --json # validate only, errors as JSON
75
+ python3 -m loma expand song.loma --json # events in ticks, canonical JSON
76
+ python3 -m loma hash song.loma # SHA-256 of the MIDI file
77
+ python3 -m loma test-determinism song.loma # compile 100 times and compare
78
+ python3 -m loma decompile song.mid -o song.loma # from MIDI to LOMA
79
+ ```
80
+
81
+ After `pip install .` the command is `loma`. The file name may be `-` to read standard input. Exit status: 0 success, 1 errors in the file, 2 usage or I/O problem.
82
+
83
+ An error says where and what to fix, and a mistyped name proposes the right one. Syntax errors and bar errors are reported in the same round, so that an AI can fix them all at once:
84
+
85
+ ```
86
+ E005 line 9: unknown chord 'Cmaj'. Did you mean 'C'?
87
+ E003 line 14 bar 2 (Ab) track RH: bar length mismatch. Expected 1920 ticks, got 1680. Missing 240 ticks (= /8).
88
+ ```
89
+
90
+ ## What is in the repository
91
+
92
+ | Folder | Content |
93
+ |---|---|
94
+ | `loma/` | Reference compiler in Python: parser, expansion, events, MIDI writer, decompiler, command line |
95
+ | `js/` | Second implementation in JavaScript (Node.js and browser), used to check conformance |
96
+ | `docs/` | The playground web page: `index.html` is self-contained and can be served with GitHub Pages |
97
+ | `conformance/` | Conformance suite: 95 valid files with the expected bytes, 116 files with the expected errors, and the program that compares them |
98
+ | `spec/` | EBNF grammar |
99
+ | `tests/` | Automated tests |
100
+ | `tools/` | Regeneration of the suite, comparison of the two implementations on random files, build of the web page |
101
+ | `examples/` | Example pieces, including `showcase.loma`, which uses every construct |
102
+ | `experiment/` | Material for the experiment with several AIs: assignment, collection of the files, measurements |
103
+ | `editors/vscode/` | Syntax highlighting for Visual Studio Code |
104
+
105
+ ## Checks
106
+
107
+ ```
108
+ python3 -m unittest discover -s tests # all tests
109
+ python3 conformance/run.py --cmd "python3 -m loma" # suite on the Python compiler
110
+ python3 conformance/run.py --cmd "node js/loma.js" # suite on the JavaScript compiler
111
+ python3 tools/fuzz_compare.py --count 5000 # random files: Python against JavaScript
112
+ ```
113
+
114
+ What is verified:
115
+
116
+ - **Same bytes from two implementations.** The Python and the JavaScript compilers produce the same events and the same bytes on all 211 cases of the suite and on thousands of randomly generated files, valid and invalid.
117
+ - **Bytes worked out by hand.** The minimal file in [SPEC.md §16.1](SPEC.md#161-example-with-the-expected-bytes) was computed by hand from the specification and matches the output.
118
+ - **Round trip.** Every MIDI file of the suite, decompiled and compiled again, comes back identical.
119
+
120
+ What is **not** verified:
121
+
122
+ - Both implementations have the same author. A misreading of the specification may be present in both: the decisive proof is still a compiler written by someone else from `SPEC.md` alone.
123
+ - Nobody has yet listened critically to the example pieces: LOMA guarantees that the MIDI is always the same, not that it sounds good.
124
+ - The experiment with several AIs (`experiment/`) so far contains only the three pieces written by the author of the specification.
125
+
126
+ ## Writing LOMA with an AI
127
+
128
+ [experiment/prompt.md](experiment/prompt.md) (English) and [experiment/prompt.it.md](experiment/prompt.it.md) (Italian) contain a compact reference of the language and the assignments for the three test pieces, ready to paste. The intended loop: the AI writes the file, `loma check --json` returns the errors, the AI fixes only the lines indicated.
129
+
130
+ ## Roadmap status
131
+
132
+ | Phase | Status |
133
+ |---|---|
134
+ | 0.1 — deterministic core | Done |
135
+ | 0.1 — EBNF grammar and second implementation | Done (same author) |
136
+ | 0.1 — experiment with AIs | Tools ready; pieces written by other models are missing |
137
+ | 0.2 — expression and variety | Done |
138
+ | 0.3 — advanced harmony | Done |
139
+ | 1.0 — stable specification, documentation, public suite | Done |
140
+ | MIDI to LOMA, lyrics, syntax highlighting | Done |
141
+ | Compiler 1.1 — errors in one round, suggestions, playground links | Done |
142
+
143
+ ## Contributing
144
+
145
+ A change must keep the bytes: run the tests before opening a pull request. Regenerating the expected files of the suite (`tools/make_conformance.py`) is a change of the specification and must be deliberate.
146
+
147
+ ## License
148
+
149
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,129 @@
1
+ # LOMA
2
+
3
+ *English · [Italiano](README.it.md)*
4
+
5
+ LOMA is a text language for describing music, designed to be **written by an AI** and **compiled deterministically to a MIDI file**: the same `.loma` file gives the same `.mid`, byte for byte, every time and with any conforming compiler.
6
+
7
+ - Full specification: [SPEC.md](SPEC.md) (English), [SPEC.it.md](SPEC.it.md) (Italian)
8
+ - Formal grammar: [spec/grammar.ebnf](spec/grammar.ebnf)
9
+ - Changes by version: [CHANGELOG.md](CHANGELOG.md)
10
+ - Playground: https://manuellomartire.github.io/loma/
11
+
12
+ ## An example
13
+
14
+ ```loma
15
+ loma 1.0
16
+ title "First example"
17
+ tempo 100
18
+ meter 4/4
19
+ key Cm
20
+
21
+ track RH ch=1 program=0
22
+ track LH ch=2 program=0
23
+
24
+ pattern arp8 base=3 dur=/8 : 1 3 5 3 1 3 5 3
25
+
26
+ section verse
27
+ bar Cm
28
+ RH{v=78}: Eb5/8 D5/8 C5/4 G4/4 r/4
29
+ LH{v=60}: use arp8
30
+ bar Ab
31
+ RH{v=70>88}: C5/4 Eb5/4{o=-6} Ab4/2{a=leg}
32
+ LH{v=60}: use arp8
33
+ end
34
+
35
+ play verse x2
36
+ ```
37
+
38
+ The AI writes intentions (chord `Cm`, the left hand plays the arpeggio, the melody grows from 70 to 88); the compiler expands them into notes on integer ticks. `/8` is an eighth note, `r` a rest, `{o=-6}` plays 6 ticks early, `x2` repeats the section.
39
+
40
+ ## Playground
41
+
42
+ Try it online: **https://manuellomartire.github.io/loma/**
43
+
44
+ `docs/index.html` is a web page that runs entirely in the browser: paste or edit LOMA, press Compile (or Ctrl/⌘+Enter), read the errors next to their lines (and copy them for the AI that wrote the file), play the result on a piano roll, download the MIDI file, open a `.mid` file to turn it into LOMA, or copy a link that contains the piece. It embeds the JavaScript compiler, so there is no server. It is published with GitHub Pages: in a fork, choose the `docs` folder of the main branch in the repository settings.
45
+
46
+ The sound comes from a small synthesizer inside the page, not from real instruments. After changing `js/loma.js`, the examples or `docs/playground.src.html`, rebuild the page with `python3 tools/build_web.py`.
47
+
48
+ ## Usage
49
+
50
+ Python 3.9 or later, no dependencies. The tests run on Linux, Windows and macOS; the second implementation needs Node.js 18 or later.
51
+
52
+ ```
53
+ python3 -m loma compile song.loma -o song.mid # compile
54
+ python3 -m loma check song.loma --json # validate only, errors as JSON
55
+ python3 -m loma expand song.loma --json # events in ticks, canonical JSON
56
+ python3 -m loma hash song.loma # SHA-256 of the MIDI file
57
+ python3 -m loma test-determinism song.loma # compile 100 times and compare
58
+ python3 -m loma decompile song.mid -o song.loma # from MIDI to LOMA
59
+ ```
60
+
61
+ After `pip install .` the command is `loma`. The file name may be `-` to read standard input. Exit status: 0 success, 1 errors in the file, 2 usage or I/O problem.
62
+
63
+ An error says where and what to fix, and a mistyped name proposes the right one. Syntax errors and bar errors are reported in the same round, so that an AI can fix them all at once:
64
+
65
+ ```
66
+ E005 line 9: unknown chord 'Cmaj'. Did you mean 'C'?
67
+ E003 line 14 bar 2 (Ab) track RH: bar length mismatch. Expected 1920 ticks, got 1680. Missing 240 ticks (= /8).
68
+ ```
69
+
70
+ ## What is in the repository
71
+
72
+ | Folder | Content |
73
+ |---|---|
74
+ | `loma/` | Reference compiler in Python: parser, expansion, events, MIDI writer, decompiler, command line |
75
+ | `js/` | Second implementation in JavaScript (Node.js and browser), used to check conformance |
76
+ | `docs/` | The playground web page: `index.html` is self-contained and can be served with GitHub Pages |
77
+ | `conformance/` | Conformance suite: 95 valid files with the expected bytes, 116 files with the expected errors, and the program that compares them |
78
+ | `spec/` | EBNF grammar |
79
+ | `tests/` | Automated tests |
80
+ | `tools/` | Regeneration of the suite, comparison of the two implementations on random files, build of the web page |
81
+ | `examples/` | Example pieces, including `showcase.loma`, which uses every construct |
82
+ | `experiment/` | Material for the experiment with several AIs: assignment, collection of the files, measurements |
83
+ | `editors/vscode/` | Syntax highlighting for Visual Studio Code |
84
+
85
+ ## Checks
86
+
87
+ ```
88
+ python3 -m unittest discover -s tests # all tests
89
+ python3 conformance/run.py --cmd "python3 -m loma" # suite on the Python compiler
90
+ python3 conformance/run.py --cmd "node js/loma.js" # suite on the JavaScript compiler
91
+ python3 tools/fuzz_compare.py --count 5000 # random files: Python against JavaScript
92
+ ```
93
+
94
+ What is verified:
95
+
96
+ - **Same bytes from two implementations.** The Python and the JavaScript compilers produce the same events and the same bytes on all 211 cases of the suite and on thousands of randomly generated files, valid and invalid.
97
+ - **Bytes worked out by hand.** The minimal file in [SPEC.md §16.1](SPEC.md#161-example-with-the-expected-bytes) was computed by hand from the specification and matches the output.
98
+ - **Round trip.** Every MIDI file of the suite, decompiled and compiled again, comes back identical.
99
+
100
+ What is **not** verified:
101
+
102
+ - Both implementations have the same author. A misreading of the specification may be present in both: the decisive proof is still a compiler written by someone else from `SPEC.md` alone.
103
+ - Nobody has yet listened critically to the example pieces: LOMA guarantees that the MIDI is always the same, not that it sounds good.
104
+ - The experiment with several AIs (`experiment/`) so far contains only the three pieces written by the author of the specification.
105
+
106
+ ## Writing LOMA with an AI
107
+
108
+ [experiment/prompt.md](experiment/prompt.md) (English) and [experiment/prompt.it.md](experiment/prompt.it.md) (Italian) contain a compact reference of the language and the assignments for the three test pieces, ready to paste. The intended loop: the AI writes the file, `loma check --json` returns the errors, the AI fixes only the lines indicated.
109
+
110
+ ## Roadmap status
111
+
112
+ | Phase | Status |
113
+ |---|---|
114
+ | 0.1 — deterministic core | Done |
115
+ | 0.1 — EBNF grammar and second implementation | Done (same author) |
116
+ | 0.1 — experiment with AIs | Tools ready; pieces written by other models are missing |
117
+ | 0.2 — expression and variety | Done |
118
+ | 0.3 — advanced harmony | Done |
119
+ | 1.0 — stable specification, documentation, public suite | Done |
120
+ | MIDI to LOMA, lyrics, syntax highlighting | Done |
121
+ | Compiler 1.1 — errors in one round, suggestions, playground links | Done |
122
+
123
+ ## Contributing
124
+
125
+ A change must keep the bytes: run the tests before opening a pull request. Regenerating the expected files of the suite (`tools/make_conformance.py`) is a change of the specification and must be deliberate.
126
+
127
+ ## License
128
+
129
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,3 @@
1
+ """LOMA 1.0 reference compiler (Python 3, standard library only, integer arithmetic only)."""
2
+ __version__ = "1.1.0"
3
+ SPEC_VERSION = "1.0"
@@ -0,0 +1,12 @@
1
+ import os
2
+ import sys
3
+
4
+ from .cli import main
5
+
6
+ try:
7
+ code = main()
8
+ sys.stdout.flush()
9
+ except BrokenPipeError: # e.g. `loma expand song.loma | head`
10
+ os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
11
+ code = 0
12
+ sys.exit(code)
@@ -0,0 +1,34 @@
1
+ """Public API of the reference compiler."""
2
+ import hashlib
3
+
4
+ from .decompiler import decompile
5
+ from .events import canonical_json
6
+ from .errors import LomaErrors
7
+ from .expander import check_bars, compile_song
8
+ from .midi_writer import write_midi
9
+ from .parser import parse_partial
10
+
11
+ __all__ = ["compile_text", "expand_json", "sha256", "decompile"]
12
+
13
+
14
+ def _compile(text):
15
+ song, errors, checkable = parse_partial(text)
16
+ if errors:
17
+ raise LomaErrors(errors + (check_bars(song) if checkable else []))
18
+ return compile_song(song)
19
+
20
+
21
+ def compile_text(text):
22
+ """LOMA source -> (compiled, midi_bytes). Raises LomaErrors."""
23
+ compiled = _compile(text)
24
+ return compiled, write_midi(compiled)
25
+
26
+
27
+ def expand_json(text):
28
+ """LOMA source -> canonical JSON of the expanded events. Raises LomaErrors."""
29
+ compiled = _compile(text)
30
+ return canonical_json(compiled.events, compiled.track_names)
31
+
32
+
33
+ def sha256(data):
34
+ return hashlib.sha256(data).hexdigest()
@@ -0,0 +1,129 @@
1
+ """Command line interface. Exit status: 0 success, 1 errors in the input, 2 usage or I/O problem."""
2
+ import argparse
3
+ import json
4
+ import sys
5
+
6
+ from . import __version__, SPEC_VERSION
7
+ from .api import compile_text, expand_json, sha256
8
+ from .decompiler import decompile, DecompileError
9
+ from .errors import LomaError, LomaErrors
10
+ from .events import canonical_json
11
+ from .midi_reader import MidiError
12
+
13
+
14
+ def _read_source(path):
15
+ if path == "-":
16
+ data = sys.stdin.buffer.read()
17
+ else:
18
+ with open(path, "rb") as f:
19
+ data = f.read()
20
+ try:
21
+ return data.decode("utf-8")
22
+ except UnicodeDecodeError:
23
+ raise LomaErrors([LomaError("E001", "the file is not valid UTF-8", 1)])
24
+
25
+
26
+ def _report(errors, as_json):
27
+ if as_json:
28
+ print(json.dumps([e.to_dict() for e in errors], sort_keys=True, ensure_ascii=False))
29
+ else:
30
+ for e in errors:
31
+ print(e.format(), file=sys.stderr)
32
+ print("%d error(s)" % len(errors), file=sys.stderr)
33
+
34
+
35
+ def _parser():
36
+ ap = argparse.ArgumentParser(prog="loma", description="LOMA %s reference compiler" % SPEC_VERSION)
37
+ ap.add_argument("--version", action="version", version="loma %s (language %s)" % (__version__, SPEC_VERSION))
38
+ sub = ap.add_subparsers(dest="cmd", required=True)
39
+ p = sub.add_parser("compile", help="compile a .loma file to MIDI")
40
+ p.add_argument("file", help="source file, or - for standard input")
41
+ p.add_argument("-o", "--output", required=True)
42
+ p.add_argument("--json", action="store_true", help="errors as JSON on standard output")
43
+ p = sub.add_parser("check", help="validate only")
44
+ p.add_argument("file")
45
+ p.add_argument("--json", action="store_true")
46
+ p = sub.add_parser("expand", help="print the expanded events")
47
+ p.add_argument("file")
48
+ p.add_argument("--json", action="store_true", help="canonical JSON")
49
+ p = sub.add_parser("hash", help="SHA-256 of the MIDI output")
50
+ p.add_argument("file")
51
+ p = sub.add_parser("test-determinism", help="compile N times and compare the hashes")
52
+ p.add_argument("file")
53
+ p.add_argument("--runs", type=int, default=100)
54
+ p = sub.add_parser("decompile", help="convert a MIDI file to LOMA")
55
+ p.add_argument("file")
56
+ p.add_argument("-o", "--output", help="output file (default: standard output)")
57
+ return ap
58
+
59
+
60
+ def _run(args):
61
+ if args.cmd == "decompile":
62
+ with open(args.file, "rb") as f:
63
+ source, warnings = decompile(f.read())
64
+ for w in warnings:
65
+ print("warning: " + w, file=sys.stderr)
66
+ if args.output:
67
+ with open(args.output, "w", encoding="utf-8", newline="\n") as f:
68
+ f.write(source)
69
+ else:
70
+ sys.stdout.write(source)
71
+ return 0
72
+
73
+ text = _read_source(args.file)
74
+ if args.cmd == "check":
75
+ compile_text(text)
76
+ print("[]" if args.json else "OK")
77
+ elif args.cmd == "compile":
78
+ _, midi = compile_text(text)
79
+ with open(args.output, "wb") as f:
80
+ f.write(midi)
81
+ if not args.json:
82
+ print("wrote %s (%d bytes, sha256 %s)" % (args.output, len(midi), sha256(midi)))
83
+ elif args.cmd == "expand":
84
+ if args.json:
85
+ sys.stdout.write(expand_json(text))
86
+ else:
87
+ compiled, _ = compile_text(text)
88
+ names = compiled.track_names
89
+ for e in compiled.events:
90
+ d = e.to_dict(names)
91
+ rest = " ".join("%s=%s" % (k, d[k]) for k in sorted(d) if k not in ("tick", "type"))
92
+ print("%9d %-8s %s" % (e.tick, e.kind, rest))
93
+ elif args.cmd == "hash":
94
+ print(sha256(compile_text(text)[1]))
95
+ elif args.cmd == "test-determinism":
96
+ compiled, midi = compile_text(text)
97
+ first = (sha256(midi), canonical_json(compiled.events, compiled.track_names))
98
+ for _ in range(max(args.runs, 1) - 1):
99
+ compiled, midi = compile_text(text)
100
+ if (sha256(midi), canonical_json(compiled.events, compiled.track_names)) != first:
101
+ print("FAIL: outputs differ between runs", file=sys.stderr)
102
+ return 1
103
+ print("OK: %d runs, identical output, sha256 %s" % (max(args.runs, 1), first[0]))
104
+ return 0
105
+
106
+
107
+ def _portable_output():
108
+ """UTF-8 and '\\n' on every system: the output must not depend on the platform (on Windows the
109
+ default would be the local code page and '\\r\\n')."""
110
+ for stream, errors in ((sys.stdout, "strict"), (sys.stderr, "backslashreplace")):
111
+ if hasattr(stream, "reconfigure"):
112
+ stream.reconfigure(encoding="utf-8", errors=errors, newline="\n")
113
+
114
+
115
+ def main(argv=None):
116
+ _portable_output()
117
+ args = _parser().parse_args(argv)
118
+ try:
119
+ return _run(args)
120
+ except LomaErrors as e:
121
+ _report(e.errors, getattr(args, "json", False))
122
+ return 1
123
+ except (OSError, MidiError, DecompileError) as e:
124
+ print("loma: %s" % e, file=sys.stderr)
125
+ return 2
126
+
127
+
128
+ if __name__ == "__main__":
129
+ sys.exit(main())