Compare commits
No commits in common. "main" and "hard-rewrite" have entirely different histories.
main
...
hard-rewri
152 changed files with 529 additions and 10010 deletions
1
.envrc
1
.envrc
|
@ -1,2 +1 @@
|
||||||
use flake . --impure
|
use flake . --impure
|
||||||
export TYPST_ROOT="$(pwd)/docs"
|
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -2,7 +2,3 @@
|
||||||
.direnv/
|
.direnv/
|
||||||
/target
|
/target
|
||||||
.pre-commit-config.yaml
|
.pre-commit-config.yaml
|
||||||
*.pdf
|
|
||||||
/docs/*.png
|
|
||||||
/testfiles/gen/*
|
|
||||||
!/testfiles/gen/.gitkeep
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
See https://www.rust-lang.org/policies/code-of-conduct.
|
|
||||||
The current maintainers, that is,
|
|
||||||
|
|
||||||
- [@Schrottkatze](https://forge.katzen.cafe/schrottkatze)
|
|
||||||
- [@multisamplednight](https://forge.katzen.cafe/multisamplednight)
|
|
||||||
|
|
||||||
are the entities to email, message or talk to if you feel like any interaction in the context of
|
|
||||||
iOwO is not okay. We'll try to answer as soon as we can.
|
|
||||||
|
|
||||||
Please do **not** open an issue. Notify the maintainers privately instead.
|
|
120
CONTRIBUTING.md
120
CONTRIBUTING.md
|
@ -1,120 +0,0 @@
|
||||||
# Contributing to iOwO
|
|
||||||
|
|
||||||
Before we get started, thank you for thinking about doing so!
|
|
||||||
|
|
||||||
## Through an issue
|
|
||||||
|
|
||||||
- Be excellent to each other. Adhere to the [code of conduct].
|
|
||||||
- About the title: If you had 5 seconds to tell someone the essence of the issue, what would it be?
|
|
||||||
|
|
||||||
### Bugs
|
|
||||||
|
|
||||||
- Write out in detail which steps in which order are necessary to reproduce the bug.
|
|
||||||
- Include environmental information as well, in specific:
|
|
||||||
- How did you install iOwO?
|
|
||||||
- What version of iOwO are you running?
|
|
||||||
- What operating system are you running?
|
|
||||||
In the case of a Linux distro, mention the specific distro and when you last updated as well.
|
|
||||||
- If the bug causes a crash, try to get a backtrace or in worse cases, a coredump.
|
|
||||||
|
|
||||||
### Feature requests
|
|
||||||
|
|
||||||
- Be sure to include a motivation in which case your intended feature would be used
|
|
||||||
even if it seems obvious to you.
|
|
||||||
- Estimate what would be needed to implement the feature:
|
|
||||||
- Is it an addition to the language itself?
|
|
||||||
- Is it just a new command?
|
|
||||||
- Does it ground-breakingly change how iOwO works?
|
|
||||||
|
|
||||||
## Through a PR
|
|
||||||
|
|
||||||
1. Clone the repo.
|
|
||||||
2. Switch to a new appropiately named branch for what you want to do, using `git switch -c`.
|
|
||||||
3. Implement your code changes with your favorite code editor.
|
|
||||||
4. Try them with `cargo run`.
|
|
||||||
5. If there are errors or warnings, go to step 3. Commit occasionally.
|
|
||||||
6. Otherwise,
|
|
||||||
- if you have an account at https://forge.katzen.cafe,
|
|
||||||
1. fork the repo
|
|
||||||
2. set it up as a remote using `git remote add`
|
|
||||||
3. push using `git push @ -u`
|
|
||||||
- if you don't,
|
|
||||||
1. combine your patches using `git diff --patch` and throw them in a file
|
|
||||||
2. send that file to one of the maintainers per email
|
|
||||||
- alongside with a description of what it does
|
|
||||||
3. also mention in the mail that we should consider GitHub and GitLab mirrors,
|
|
||||||
referring to this line
|
|
||||||
|
|
||||||
### Tech stack
|
|
||||||
|
|
||||||
The techstack we operate on is
|
|
||||||
|
|
||||||
- [typst] for documents and concrete proposals
|
|
||||||
- [Rust] for the actual code
|
|
||||||
|
|
||||||
So if you want to contribute functionality, take a look at [The Rust Programming Language book]!
|
|
||||||
If you want to contribute thoughts and technical designs, then consider taking a ride through
|
|
||||||
[typst's excellent tutorial]!
|
|
||||||
|
|
||||||
For creative things, we suggest using whatever **you** are comfortable with.
|
|
||||||
Otherwise, we suggest these:
|
|
||||||
|
|
||||||
- [Inkscape], [GIMP] and [Blender] for promotional material like logos and posters
|
|
||||||
- [Penpot] for layouting prototypes and the like
|
|
||||||
|
|
||||||
[typst]: https://typst.app
|
|
||||||
[Rust]: https://www.rust-lang.org
|
|
||||||
[The Rust Programming Language book]: https://doc.rust-lang.org/book/
|
|
||||||
[typst's excellent tutorial]: https://typst.app/docs/tutorial
|
|
||||||
|
|
||||||
[Inkscape]: https://inkscape.org/
|
|
||||||
[GIMP]: https://www.gimp.org/
|
|
||||||
[Blender]: https://www.blender.org/
|
|
||||||
[Penpot]: https://penpot.app/
|
|
||||||
|
|
||||||
## Politics
|
|
||||||
|
|
||||||
- Current maintainers are defined as the entities listed in the [code of conduct]
|
|
||||||
|
|
||||||
### PRs
|
|
||||||
|
|
||||||
- Every PR requires an approving review from a maintainer (that is not the author) before merge
|
|
||||||
- Maintainers can merge their own PRs
|
|
||||||
- But only after approval
|
|
||||||
|
|
||||||
### Major decisions
|
|
||||||
|
|
||||||
- All current maintainers have to agree **unanimously**.
|
|
||||||
- Agreement must be based on [informed consent].
|
|
||||||
- In effect, a maintainer has to understand what they agree to.
|
|
||||||
|
|
||||||
# Interacting with PRs
|
|
||||||
|
|
||||||
Remember, be respectful.
|
|
||||||
Entities invest their free time and motivation into making these changes,
|
|
||||||
treat them appropiately.
|
|
||||||
|
|
||||||
- Since we mostly work based on forks, [git's remotes] work fairly good.
|
|
||||||
- Replace things in pointy brackets (`<>`) respectively (and remove the pointy brackets).
|
|
||||||
|
|
||||||
## Initial steps for a new contributor or new local checkout
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git remote add <contributor-name> https://forge.katzen.cafe/<contributor-account>/iowo.git
|
|
||||||
git remote update <contributor-name>
|
|
||||||
```
|
|
||||||
|
|
||||||
## After setting up the remote
|
|
||||||
|
|
||||||
- You can repeat this step anytime you want to switch branches or update your local checkout.
|
|
||||||
- The PR branch is visible just below the PR title on Forgejo, after the colon (`:`).
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git switch <pr-branch>
|
|
||||||
git pull
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
[code of conduct]: ./CODE_OF_CONDUCT.md
|
|
||||||
[informed consent]: https://en.wikipedia.org/wiki/Informed_consent
|
|
||||||
[git's remotes]: https://git-scm.com/docs/git-remote
|
|
1729
Cargo.lock
generated
1729
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
24
Cargo.toml
24
Cargo.toml
|
@ -1,31 +1,19 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/app",
|
"crates/executor",
|
||||||
"crates/eval",
|
"crates/pl-cli",
|
||||||
"crates/ir",
|
"crates/rpl"
|
||||||
"crates/lang",
|
|
||||||
"crates/svg-filters",
|
|
||||||
"crates/prowocessing",
|
|
||||||
"crates/executor-poc",
|
|
||||||
"crates/pawarser",
|
|
||||||
"crates/json-pawarser",
|
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = [ "derive" ] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
petgraph = "0.6.4"
|
|
||||||
|
|
||||||
# to enable all the lints below, this must be present in a workspace member's Cargo.toml:
|
[lints.rust]
|
||||||
# [lints]
|
|
||||||
# workspace = true
|
|
||||||
|
|
||||||
[workspace.lints.rust]
|
|
||||||
unsafe_code = "deny"
|
unsafe_code = "deny"
|
||||||
variant_size_differences = "warn"
|
variant_size_differences = "warn"
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[lints.clippy]
|
||||||
branches_sharing_code = "warn"
|
branches_sharing_code = "warn"
|
||||||
clone_on_ref_ptr = "warn"
|
clone_on_ref_ptr = "warn"
|
||||||
cognitive_complexity = "warn"
|
cognitive_complexity = "warn"
|
||||||
|
|
676
LICENSE
676
LICENSE
|
@ -1,676 +0,0 @@
|
||||||
The main program and its libraries are licensed under the AGPL 3.0
|
|
||||||
as listed below the separator line.
|
|
||||||
|
|
||||||
Media samples in the testfiles folder are licensed under CC BY-SA 4.0.
|
|
||||||
To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/
|
|
||||||
The appropiate authors are given below, in the format of
|
|
||||||
`- file/path: author (link to author)`
|
|
||||||
|
|
||||||
- testfiles/rails.png: Schrottkatze (https://forge.katzen.cafe/schrottkatze)
|
|
||||||
|
|
||||||
===============================================================================
|
|
||||||
|
|
||||||
iOwO: media pipeline toolset
|
|
||||||
Copyright (C) 2024 Schrottkatze, MultisampledNight and iOwO contributors
|
|
||||||
|
|
||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 19 November 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The GNU Affero General Public License is a free, copyleft license for
|
|
||||||
software and other kinds of works, specifically designed to ensure
|
|
||||||
cooperation with the community in the case of network server software.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
|
||||||
our General Public Licenses are intended to guarantee your freedom to
|
|
||||||
share and change all versions of a program--to make sure it remains free
|
|
||||||
software for all its users.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
them if you wish), that you receive source code or can get it if you
|
|
||||||
want it, that you can change the software or use pieces of it in new
|
|
||||||
free programs, and that you know you can do these things.
|
|
||||||
|
|
||||||
Developers that use our General Public Licenses protect your rights
|
|
||||||
with two steps: (1) assert copyright on the software, and (2) offer
|
|
||||||
you this License which gives you legal permission to copy, distribute
|
|
||||||
and/or modify the software.
|
|
||||||
|
|
||||||
A secondary benefit of defending all users' freedom is that
|
|
||||||
improvements made in alternate versions of the program, if they
|
|
||||||
receive widespread use, become available for other developers to
|
|
||||||
incorporate. Many developers of free software are heartened and
|
|
||||||
encouraged by the resulting cooperation. However, in the case of
|
|
||||||
software used on network servers, this result may fail to come about.
|
|
||||||
The GNU General Public License permits making a modified version and
|
|
||||||
letting the public access it on a server without ever releasing its
|
|
||||||
source code to the public.
|
|
||||||
|
|
||||||
The GNU Affero General Public License is designed specifically to
|
|
||||||
ensure that, in such cases, the modified source code becomes available
|
|
||||||
to the community. It requires the operator of a network server to
|
|
||||||
provide the source code of the modified version running there to the
|
|
||||||
users of that server. Therefore, public use of a modified version, on
|
|
||||||
a publicly accessible server, gives the public access to the source
|
|
||||||
code of the modified version.
|
|
||||||
|
|
||||||
An older license, called the Affero General Public License and
|
|
||||||
published by Affero, was designed to accomplish similar goals. This is
|
|
||||||
a different license, not a version of the Affero GPL, but Affero has
|
|
||||||
released a new version of the Affero GPL which permits relicensing under
|
|
||||||
this license.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
0. Definitions.
|
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
|
||||||
works, such as semiconductor masks.
|
|
||||||
|
|
||||||
"The Program" refers to any copyrightable work licensed under this
|
|
||||||
License. Each licensee is addressed as "you". "Licensees" and
|
|
||||||
"recipients" may be individuals or organizations.
|
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work
|
|
||||||
in a fashion requiring copyright permission, other than the making of an
|
|
||||||
exact copy. The resulting work is called a "modified version" of the
|
|
||||||
earlier work or a work "based on" the earlier work.
|
|
||||||
|
|
||||||
A "covered work" means either the unmodified Program or a work based
|
|
||||||
on the Program.
|
|
||||||
|
|
||||||
To "propagate" a work means to do anything with it that, without
|
|
||||||
permission, would make you directly or secondarily liable for
|
|
||||||
infringement under applicable copyright law, except executing it on a
|
|
||||||
computer or modifying a private copy. Propagation includes copying,
|
|
||||||
distribution (with or without modification), making available to the
|
|
||||||
public, and in some countries other activities as well.
|
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other
|
|
||||||
parties to make or receive copies. Mere interaction with a user through
|
|
||||||
a computer network, with no transfer of a copy, is not conveying.
|
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
|
||||||
to the extent that it includes a convenient and prominently visible
|
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
|
||||||
tells the user that there is no warranty for the work (except to the
|
|
||||||
extent that warranties are provided), that licensees may convey the
|
|
||||||
work under this License, and how to view a copy of this License. If
|
|
||||||
the interface presents a list of user commands or options, such as a
|
|
||||||
menu, a prominent item in the list meets this criterion.
|
|
||||||
|
|
||||||
1. Source Code.
|
|
||||||
|
|
||||||
The "source code" for a work means the preferred form of the work
|
|
||||||
for making modifications to it. "Object code" means any non-source
|
|
||||||
form of a work.
|
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official
|
|
||||||
standard defined by a recognized standards body, or, in the case of
|
|
||||||
interfaces specified for a particular programming language, one that
|
|
||||||
is widely used among developers working in that language.
|
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other
|
|
||||||
than the work as a whole, that (a) is included in the normal form of
|
|
||||||
packaging a Major Component, but which is not part of that Major
|
|
||||||
Component, and (b) serves only to enable use of the work with that
|
|
||||||
Major Component, or to implement a Standard Interface for which an
|
|
||||||
implementation is available to the public in source code form. A
|
|
||||||
"Major Component", in this context, means a major essential component
|
|
||||||
(kernel, window system, and so on) of the specific operating system
|
|
||||||
(if any) on which the executable work runs, or a compiler used to
|
|
||||||
produce the work, or an object code interpreter used to run it.
|
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all
|
|
||||||
the source code needed to generate, install, and (for an executable
|
|
||||||
work) run the object code and to modify the work, including scripts to
|
|
||||||
control those activities. However, it does not include the work's
|
|
||||||
System Libraries, or general-purpose tools or generally available free
|
|
||||||
programs which are used unmodified in performing those activities but
|
|
||||||
which are not part of the work. For example, Corresponding Source
|
|
||||||
includes interface definition files associated with source files for
|
|
||||||
the work, and the source code for shared libraries and dynamically
|
|
||||||
linked subprograms that the work is specifically designed to require,
|
|
||||||
such as by intimate data communication or control flow between those
|
|
||||||
subprograms and other parts of the work.
|
|
||||||
|
|
||||||
The Corresponding Source need not include anything that users
|
|
||||||
can regenerate automatically from other parts of the Corresponding
|
|
||||||
Source.
|
|
||||||
|
|
||||||
The Corresponding Source for a work in source code form is that
|
|
||||||
same work.
|
|
||||||
|
|
||||||
2. Basic Permissions.
|
|
||||||
|
|
||||||
All rights granted under this License are granted for the term of
|
|
||||||
copyright on the Program, and are irrevocable provided the stated
|
|
||||||
conditions are met. This License explicitly affirms your unlimited
|
|
||||||
permission to run the unmodified Program. The output from running a
|
|
||||||
covered work is covered by this License only if the output, given its
|
|
||||||
content, constitutes a covered work. This License acknowledges your
|
|
||||||
rights of fair use or other equivalent, as provided by copyright law.
|
|
||||||
|
|
||||||
You may make, run and propagate covered works that you do not
|
|
||||||
convey, without conditions so long as your license otherwise remains
|
|
||||||
in force. You may convey covered works to others for the sole purpose
|
|
||||||
of having them make modifications exclusively for you, or provide you
|
|
||||||
with facilities for running those works, provided that you comply with
|
|
||||||
the terms of this License in conveying all material for which you do
|
|
||||||
not control copyright. Those thus making or running the covered works
|
|
||||||
for you must do so exclusively on your behalf, under your direction
|
|
||||||
and control, on terms that prohibit them from making any copies of
|
|
||||||
your copyrighted material outside their relationship with you.
|
|
||||||
|
|
||||||
Conveying under any other circumstances is permitted solely under
|
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10
|
|
||||||
makes it unnecessary.
|
|
||||||
|
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
|
||||||
|
|
||||||
No covered work shall be deemed part of an effective technological
|
|
||||||
measure under any applicable law fulfilling obligations under article
|
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
|
||||||
similar laws prohibiting or restricting circumvention of such
|
|
||||||
measures.
|
|
||||||
|
|
||||||
When you convey a covered work, you waive any legal power to forbid
|
|
||||||
circumvention of technological measures to the extent such circumvention
|
|
||||||
is effected by exercising rights under this License with respect to
|
|
||||||
the covered work, and you disclaim any intention to limit operation or
|
|
||||||
modification of the work as a means of enforcing, against the work's
|
|
||||||
users, your or third parties' legal rights to forbid circumvention of
|
|
||||||
technological measures.
|
|
||||||
|
|
||||||
4. Conveying Verbatim Copies.
|
|
||||||
|
|
||||||
You may convey verbatim copies of the Program's source code as you
|
|
||||||
receive it, in any medium, provided that you conspicuously and
|
|
||||||
appropriately publish on each copy an appropriate copyright notice;
|
|
||||||
keep intact all notices stating that this License and any
|
|
||||||
non-permissive terms added in accord with section 7 apply to the code;
|
|
||||||
keep intact all notices of the absence of any warranty; and give all
|
|
||||||
recipients a copy of this License along with the Program.
|
|
||||||
|
|
||||||
You may charge any price or no price for each copy that you convey,
|
|
||||||
and you may offer support or warranty protection for a fee.
|
|
||||||
|
|
||||||
5. Conveying Modified Source Versions.
|
|
||||||
|
|
||||||
You may convey a work based on the Program, or the modifications to
|
|
||||||
produce it from the Program, in the form of source code under the
|
|
||||||
terms of section 4, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The work must carry prominent notices stating that you modified
|
|
||||||
it, and giving a relevant date.
|
|
||||||
|
|
||||||
b) The work must carry prominent notices stating that it is
|
|
||||||
released under this License and any conditions added under section
|
|
||||||
7. This requirement modifies the requirement in section 4 to
|
|
||||||
"keep intact all notices".
|
|
||||||
|
|
||||||
c) You must license the entire work, as a whole, under this
|
|
||||||
License to anyone who comes into possession of a copy. This
|
|
||||||
License will therefore apply, along with any applicable section 7
|
|
||||||
additional terms, to the whole of the work, and all its parts,
|
|
||||||
regardless of how they are packaged. This License gives no
|
|
||||||
permission to license the work in any other way, but it does not
|
|
||||||
invalidate such permission if you have separately received it.
|
|
||||||
|
|
||||||
d) If the work has interactive user interfaces, each must display
|
|
||||||
Appropriate Legal Notices; however, if the Program has interactive
|
|
||||||
interfaces that do not display Appropriate Legal Notices, your
|
|
||||||
work need not make them do so.
|
|
||||||
|
|
||||||
A compilation of a covered work with other separate and independent
|
|
||||||
works, which are not by their nature extensions of the covered work,
|
|
||||||
and which are not combined with it such as to form a larger program,
|
|
||||||
in or on a volume of a storage or distribution medium, is called an
|
|
||||||
"aggregate" if the compilation and its resulting copyright are not
|
|
||||||
used to limit the access or legal rights of the compilation's users
|
|
||||||
beyond what the individual works permit. Inclusion of a covered work
|
|
||||||
in an aggregate does not cause this License to apply to the other
|
|
||||||
parts of the aggregate.
|
|
||||||
|
|
||||||
6. Conveying Non-Source Forms.
|
|
||||||
|
|
||||||
You may convey a covered work in object code form under the terms
|
|
||||||
of sections 4 and 5, provided that you also convey the
|
|
||||||
machine-readable Corresponding Source under the terms of this License,
|
|
||||||
in one of these ways:
|
|
||||||
|
|
||||||
a) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by the
|
|
||||||
Corresponding Source fixed on a durable physical medium
|
|
||||||
customarily used for software interchange.
|
|
||||||
|
|
||||||
b) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by a
|
|
||||||
written offer, valid for at least three years and valid for as
|
|
||||||
long as you offer spare parts or customer support for that product
|
|
||||||
model, to give anyone who possesses the object code either (1) a
|
|
||||||
copy of the Corresponding Source for all the software in the
|
|
||||||
product that is covered by this License, on a durable physical
|
|
||||||
medium customarily used for software interchange, for a price no
|
|
||||||
more than your reasonable cost of physically performing this
|
|
||||||
conveying of source, or (2) access to copy the
|
|
||||||
Corresponding Source from a network server at no charge.
|
|
||||||
|
|
||||||
c) Convey individual copies of the object code with a copy of the
|
|
||||||
written offer to provide the Corresponding Source. This
|
|
||||||
alternative is allowed only occasionally and noncommercially, and
|
|
||||||
only if you received the object code with such an offer, in accord
|
|
||||||
with subsection 6b.
|
|
||||||
|
|
||||||
d) Convey the object code by offering access from a designated
|
|
||||||
place (gratis or for a charge), and offer equivalent access to the
|
|
||||||
Corresponding Source in the same way through the same place at no
|
|
||||||
further charge. You need not require recipients to copy the
|
|
||||||
Corresponding Source along with the object code. If the place to
|
|
||||||
copy the object code is a network server, the Corresponding Source
|
|
||||||
may be on a different server (operated by you or a third party)
|
|
||||||
that supports equivalent copying facilities, provided you maintain
|
|
||||||
clear directions next to the object code saying where to find the
|
|
||||||
Corresponding Source. Regardless of what server hosts the
|
|
||||||
Corresponding Source, you remain obligated to ensure that it is
|
|
||||||
available for as long as needed to satisfy these requirements.
|
|
||||||
|
|
||||||
e) Convey the object code using peer-to-peer transmission, provided
|
|
||||||
you inform other peers where the object code and Corresponding
|
|
||||||
Source of the work are being offered to the general public at no
|
|
||||||
charge under subsection 6d.
|
|
||||||
|
|
||||||
A separable portion of the object code, whose source code is excluded
|
|
||||||
from the Corresponding Source as a System Library, need not be
|
|
||||||
included in conveying the object code work.
|
|
||||||
|
|
||||||
A "User Product" is either (1) a "consumer product", which means any
|
|
||||||
tangible personal property which is normally used for personal, family,
|
|
||||||
or household purposes, or (2) anything designed or sold for incorporation
|
|
||||||
into a dwelling. In determining whether a product is a consumer product,
|
|
||||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
|
||||||
product received by a particular user, "normally used" refers to a
|
|
||||||
typical or common use of that class of product, regardless of the status
|
|
||||||
of the particular user or of the way in which the particular user
|
|
||||||
actually uses, or expects or is expected to use, the product. A product
|
|
||||||
is a consumer product regardless of whether the product has substantial
|
|
||||||
commercial, industrial or non-consumer uses, unless such uses represent
|
|
||||||
the only significant mode of use of the product.
|
|
||||||
|
|
||||||
"Installation Information" for a User Product means any methods,
|
|
||||||
procedures, authorization keys, or other information required to install
|
|
||||||
and execute modified versions of a covered work in that User Product from
|
|
||||||
a modified version of its Corresponding Source. The information must
|
|
||||||
suffice to ensure that the continued functioning of the modified object
|
|
||||||
code is in no case prevented or interfered with solely because
|
|
||||||
modification has been made.
|
|
||||||
|
|
||||||
If you convey an object code work under this section in, or with, or
|
|
||||||
specifically for use in, a User Product, and the conveying occurs as
|
|
||||||
part of a transaction in which the right of possession and use of the
|
|
||||||
User Product is transferred to the recipient in perpetuity or for a
|
|
||||||
fixed term (regardless of how the transaction is characterized), the
|
|
||||||
Corresponding Source conveyed under this section must be accompanied
|
|
||||||
by the Installation Information. But this requirement does not apply
|
|
||||||
if neither you nor any third party retains the ability to install
|
|
||||||
modified object code on the User Product (for example, the work has
|
|
||||||
been installed in ROM).
|
|
||||||
|
|
||||||
The requirement to provide Installation Information does not include a
|
|
||||||
requirement to continue to provide support service, warranty, or updates
|
|
||||||
for a work that has been modified or installed by the recipient, or for
|
|
||||||
the User Product in which it has been modified or installed. Access to a
|
|
||||||
network may be denied when the modification itself materially and
|
|
||||||
adversely affects the operation of the network or violates the rules and
|
|
||||||
protocols for communication across the network.
|
|
||||||
|
|
||||||
Corresponding Source conveyed, and Installation Information provided,
|
|
||||||
in accord with this section must be in a format that is publicly
|
|
||||||
documented (and with an implementation available to the public in
|
|
||||||
source code form), and must require no special password or key for
|
|
||||||
unpacking, reading or copying.
|
|
||||||
|
|
||||||
7. Additional Terms.
|
|
||||||
|
|
||||||
"Additional permissions" are terms that supplement the terms of this
|
|
||||||
License by making exceptions from one or more of its conditions.
|
|
||||||
Additional permissions that are applicable to the entire Program shall
|
|
||||||
be treated as though they were included in this License, to the extent
|
|
||||||
that they are valid under applicable law. If additional permissions
|
|
||||||
apply only to part of the Program, that part may be used separately
|
|
||||||
under those permissions, but the entire Program remains governed by
|
|
||||||
this License without regard to the additional permissions.
|
|
||||||
|
|
||||||
When you convey a copy of a covered work, you may at your option
|
|
||||||
remove any additional permissions from that copy, or from any part of
|
|
||||||
it. (Additional permissions may be written to require their own
|
|
||||||
removal in certain cases when you modify the work.) You may place
|
|
||||||
additional permissions on material, added by you to a covered work,
|
|
||||||
for which you have or can give appropriate copyright permission.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, for material you
|
|
||||||
add to a covered work, you may (if authorized by the copyright holders of
|
|
||||||
that material) supplement the terms of this License with terms:
|
|
||||||
|
|
||||||
a) Disclaiming warranty or limiting liability differently from the
|
|
||||||
terms of sections 15 and 16 of this License; or
|
|
||||||
|
|
||||||
b) Requiring preservation of specified reasonable legal notices or
|
|
||||||
author attributions in that material or in the Appropriate Legal
|
|
||||||
Notices displayed by works containing it; or
|
|
||||||
|
|
||||||
c) Prohibiting misrepresentation of the origin of that material, or
|
|
||||||
requiring that modified versions of such material be marked in
|
|
||||||
reasonable ways as different from the original version; or
|
|
||||||
|
|
||||||
d) Limiting the use for publicity purposes of names of licensors or
|
|
||||||
authors of the material; or
|
|
||||||
|
|
||||||
e) Declining to grant rights under trademark law for use of some
|
|
||||||
trade names, trademarks, or service marks; or
|
|
||||||
|
|
||||||
f) Requiring indemnification of licensors and authors of that
|
|
||||||
material by anyone who conveys the material (or modified versions of
|
|
||||||
it) with contractual assumptions of liability to the recipient, for
|
|
||||||
any liability that these contractual assumptions directly impose on
|
|
||||||
those licensors and authors.
|
|
||||||
|
|
||||||
All other non-permissive additional terms are considered "further
|
|
||||||
restrictions" within the meaning of section 10. If the Program as you
|
|
||||||
received it, or any part of it, contains a notice stating that it is
|
|
||||||
governed by this License along with a term that is a further
|
|
||||||
restriction, you may remove that term. If a license document contains
|
|
||||||
a further restriction but permits relicensing or conveying under this
|
|
||||||
License, you may add to a covered work material governed by the terms
|
|
||||||
of that license document, provided that the further restriction does
|
|
||||||
not survive such relicensing or conveying.
|
|
||||||
|
|
||||||
If you add terms to a covered work in accord with this section, you
|
|
||||||
must place, in the relevant source files, a statement of the
|
|
||||||
additional terms that apply to those files, or a notice indicating
|
|
||||||
where to find the applicable terms.
|
|
||||||
|
|
||||||
Additional terms, permissive or non-permissive, may be stated in the
|
|
||||||
form of a separately written license, or stated as exceptions;
|
|
||||||
the above requirements apply either way.
|
|
||||||
|
|
||||||
8. Termination.
|
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly
|
|
||||||
provided under this License. Any attempt otherwise to propagate or
|
|
||||||
modify it is void, and will automatically terminate your rights under
|
|
||||||
this License (including any patent licenses granted under the third
|
|
||||||
paragraph of section 11).
|
|
||||||
|
|
||||||
However, if you cease all violation of this License, then your
|
|
||||||
license from a particular copyright holder is reinstated (a)
|
|
||||||
provisionally, unless and until the copyright holder explicitly and
|
|
||||||
finally terminates your license, and (b) permanently, if the copyright
|
|
||||||
holder fails to notify you of the violation by some reasonable means
|
|
||||||
prior to 60 days after the cessation.
|
|
||||||
|
|
||||||
Moreover, your license from a particular copyright holder is
|
|
||||||
reinstated permanently if the copyright holder notifies you of the
|
|
||||||
violation by some reasonable means, this is the first time you have
|
|
||||||
received notice of violation of this License (for any work) from that
|
|
||||||
copyright holder, and you cure the violation prior to 30 days after
|
|
||||||
your receipt of the notice.
|
|
||||||
|
|
||||||
Termination of your rights under this section does not terminate the
|
|
||||||
licenses of parties who have received copies or rights from you under
|
|
||||||
this License. If your rights have been terminated and not permanently
|
|
||||||
reinstated, you do not qualify to receive new licenses for the same
|
|
||||||
material under section 10.
|
|
||||||
|
|
||||||
9. Acceptance Not Required for Having Copies.
|
|
||||||
|
|
||||||
You are not required to accept this License in order to receive or
|
|
||||||
run a copy of the Program. Ancillary propagation of a covered work
|
|
||||||
occurring solely as a consequence of using peer-to-peer transmission
|
|
||||||
to receive a copy likewise does not require acceptance. However,
|
|
||||||
nothing other than this License grants you permission to propagate or
|
|
||||||
modify any covered work. These actions infringe copyright if you do
|
|
||||||
not accept this License. Therefore, by modifying or propagating a
|
|
||||||
covered work, you indicate your acceptance of this License to do so.
|
|
||||||
|
|
||||||
10. Automatic Licensing of Downstream Recipients.
|
|
||||||
|
|
||||||
Each time you convey a covered work, the recipient automatically
|
|
||||||
receives a license from the original licensors, to run, modify and
|
|
||||||
propagate that work, subject to this License. You are not responsible
|
|
||||||
for enforcing compliance by third parties with this License.
|
|
||||||
|
|
||||||
An "entity transaction" is a transaction transferring control of an
|
|
||||||
organization, or substantially all assets of one, or subdividing an
|
|
||||||
organization, or merging organizations. If propagation of a covered
|
|
||||||
work results from an entity transaction, each party to that
|
|
||||||
transaction who receives a copy of the work also receives whatever
|
|
||||||
licenses to the work the party's predecessor in interest had or could
|
|
||||||
give under the previous paragraph, plus a right to possession of the
|
|
||||||
Corresponding Source of the work from the predecessor in interest, if
|
|
||||||
the predecessor has it or can get it with reasonable efforts.
|
|
||||||
|
|
||||||
You may not impose any further restrictions on the exercise of the
|
|
||||||
rights granted or affirmed under this License. For example, you may
|
|
||||||
not impose a license fee, royalty, or other charge for exercise of
|
|
||||||
rights granted under this License, and you may not initiate litigation
|
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
|
||||||
any patent claim is infringed by making, using, selling, offering for
|
|
||||||
sale, or importing the Program or any portion of it.
|
|
||||||
|
|
||||||
11. Patents.
|
|
||||||
|
|
||||||
A "contributor" is a copyright holder who authorizes use under this
|
|
||||||
License of the Program or a work on which the Program is based. The
|
|
||||||
work thus licensed is called the contributor's "contributor version".
|
|
||||||
|
|
||||||
A contributor's "essential patent claims" are all patent claims
|
|
||||||
owned or controlled by the contributor, whether already acquired or
|
|
||||||
hereafter acquired, that would be infringed by some manner, permitted
|
|
||||||
by this License, of making, using, or selling its contributor version,
|
|
||||||
but do not include claims that would be infringed only as a
|
|
||||||
consequence of further modification of the contributor version. For
|
|
||||||
purposes of this definition, "control" includes the right to grant
|
|
||||||
patent sublicenses in a manner consistent with the requirements of
|
|
||||||
this License.
|
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
|
||||||
patent license under the contributor's essential patent claims, to
|
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
|
||||||
propagate the contents of its contributor version.
|
|
||||||
|
|
||||||
In the following three paragraphs, a "patent license" is any express
|
|
||||||
agreement or commitment, however denominated, not to enforce a patent
|
|
||||||
(such as an express permission to practice a patent or covenant not to
|
|
||||||
sue for patent infringement). To "grant" such a patent license to a
|
|
||||||
party means to make such an agreement or commitment not to enforce a
|
|
||||||
patent against the party.
|
|
||||||
|
|
||||||
If you convey a covered work, knowingly relying on a patent license,
|
|
||||||
and the Corresponding Source of the work is not available for anyone
|
|
||||||
to copy, free of charge and under the terms of this License, through a
|
|
||||||
publicly available network server or other readily accessible means,
|
|
||||||
then you must either (1) cause the Corresponding Source to be so
|
|
||||||
available, or (2) arrange to deprive yourself of the benefit of the
|
|
||||||
patent license for this particular work, or (3) arrange, in a manner
|
|
||||||
consistent with the requirements of this License, to extend the patent
|
|
||||||
license to downstream recipients. "Knowingly relying" means you have
|
|
||||||
actual knowledge that, but for the patent license, your conveying the
|
|
||||||
covered work in a country, or your recipient's use of the covered work
|
|
||||||
in a country, would infringe one or more identifiable patents in that
|
|
||||||
country that you have reason to believe are valid.
|
|
||||||
|
|
||||||
If, pursuant to or in connection with a single transaction or
|
|
||||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
|
||||||
covered work, and grant a patent license to some of the parties
|
|
||||||
receiving the covered work authorizing them to use, propagate, modify
|
|
||||||
or convey a specific copy of the covered work, then the patent license
|
|
||||||
you grant is automatically extended to all recipients of the covered
|
|
||||||
work and works based on it.
|
|
||||||
|
|
||||||
A patent license is "discriminatory" if it does not include within
|
|
||||||
the scope of its coverage, prohibits the exercise of, or is
|
|
||||||
conditioned on the non-exercise of one or more of the rights that are
|
|
||||||
specifically granted under this License. You may not convey a covered
|
|
||||||
work if you are a party to an arrangement with a third party that is
|
|
||||||
in the business of distributing software, under which you make payment
|
|
||||||
to the third party based on the extent of your activity of conveying
|
|
||||||
the work, and under which the third party grants, to any of the
|
|
||||||
parties who would receive the covered work from you, a discriminatory
|
|
||||||
patent license (a) in connection with copies of the covered work
|
|
||||||
conveyed by you (or copies made from those copies), or (b) primarily
|
|
||||||
for and in connection with specific products or compilations that
|
|
||||||
contain the covered work, unless you entered into that arrangement,
|
|
||||||
or that patent license was granted, prior to 28 March 2007.
|
|
||||||
|
|
||||||
Nothing in this License shall be construed as excluding or limiting
|
|
||||||
any implied license or other defenses to infringement that may
|
|
||||||
otherwise be available to you under applicable patent law.
|
|
||||||
|
|
||||||
12. No Surrender of Others' Freedom.
|
|
||||||
|
|
||||||
If conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot convey a
|
|
||||||
covered work so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you may
|
|
||||||
not convey it at all. For example, if you agree to terms that obligate you
|
|
||||||
to collect a royalty for further conveying from those to whom you convey
|
|
||||||
the Program, the only way you could satisfy both those terms and this
|
|
||||||
License would be to refrain entirely from conveying the Program.
|
|
||||||
|
|
||||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, if you modify the
|
|
||||||
Program, your modified version must prominently offer all users
|
|
||||||
interacting with it remotely through a computer network (if your version
|
|
||||||
supports such interaction) an opportunity to receive the Corresponding
|
|
||||||
Source of your version by providing access to the Corresponding Source
|
|
||||||
from a network server at no charge, through some standard or customary
|
|
||||||
means of facilitating copying of software. This Corresponding Source
|
|
||||||
shall include the Corresponding Source for any work covered by version 3
|
|
||||||
of the GNU General Public License that is incorporated pursuant to the
|
|
||||||
following paragraph.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
|
||||||
permission to link or combine any covered work with a work licensed
|
|
||||||
under version 3 of the GNU General Public License into a single
|
|
||||||
combined work, and to convey the resulting work. The terms of this
|
|
||||||
License will continue to apply to the part which is the covered work,
|
|
||||||
but the work with which it is combined will remain governed by version
|
|
||||||
3 of the GNU General Public License.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the GNU Affero General Public License from time to time. Such new versions
|
|
||||||
will be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Program specifies that a certain numbered version of the GNU Affero General
|
|
||||||
Public License "or any later version" applies to it, you have the
|
|
||||||
option of following the terms and conditions either of that numbered
|
|
||||||
version or of any later version published by the Free Software
|
|
||||||
Foundation. If the Program does not specify a version number of the
|
|
||||||
GNU Affero General Public License, you may choose any version ever published
|
|
||||||
by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
|
||||||
versions of the GNU Affero General Public License can be used, that proxy's
|
|
||||||
public statement of acceptance of a version permanently authorizes you
|
|
||||||
to choose that version for the Program.
|
|
||||||
|
|
||||||
Later license versions may give you additional or different
|
|
||||||
permissions. However, no additional obligations are imposed on any
|
|
||||||
author or copyright holder as a result of your choosing to follow a
|
|
||||||
later version.
|
|
||||||
|
|
||||||
15. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. Limitation of Liability.
|
|
||||||
|
|
||||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
|
||||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
|
||||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
|
||||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
|
||||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
|
||||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
|
||||||
SUCH DAMAGES.
|
|
||||||
|
|
||||||
17. Interpretation of Sections 15 and 16.
|
|
||||||
|
|
||||||
If the disclaimer of warranty and limitation of liability provided
|
|
||||||
above cannot be given local legal effect according to their terms,
|
|
||||||
reviewing courts shall apply local law that most closely approximates
|
|
||||||
an absolute waiver of all civil liability in connection with the
|
|
||||||
Program, unless a warranty or assumption of liability accompanies a
|
|
||||||
copy of the Program in return for a fee.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
state the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If your software can interact with users remotely through a computer
|
|
||||||
network, you should also make sure that it provides a way for users to
|
|
||||||
get its source. For example, if your program is a web application, its
|
|
||||||
interface could display a "Source" link that leads users to an archive
|
|
||||||
of the code. There are many ways you could offer source, and different
|
|
||||||
solutions will be better for different programs; see section 13 for the
|
|
||||||
specific requirements.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
|
||||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
|
||||||
<https://www.gnu.org/licenses/>.
|
|
16
README.md
16
README.md
|
@ -1,16 +0,0 @@
|
||||||
# iOwO
|
|
||||||
|
|
||||||
Tired of remembering if the argument order matters on this ImageMagick command?
|
|
||||||
Tired of having shell parsing interfere with what you actually want to do?
|
|
||||||
|
|
||||||
Fear no more, with iOwO (pronounced iowo (
|
|
||||||
we believe at least (we don't know what we're doing [please help]!)
|
|
||||||
)) you can just write a plain pipeline with [nushell]-ish syntax!
|
|
||||||
Or that's what we want it to be at least.
|
|
||||||
|
|
||||||
So, uh, grab a seat and a beverage, sit down, and maybe take a look around.
|
|
||||||
Although there's not much here — _yet_.
|
|
||||||
|
|
||||||
[please help]: ./CONTRIBUTING.md
|
|
||||||
[nushell]: https://www.nushell.sh/
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "app"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
default-run = "app"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
ariadne = "0.4"
|
|
||||||
clap = { workspace = true, features = [ "derive", "env" ] }
|
|
||||||
dirs = "5"
|
|
||||||
eval = { path = "../eval" }
|
|
||||||
ir = { path = "../ir" }
|
|
||||||
prowocessing = { path = "../prowocessing"}
|
|
||||||
owo-colors = "4"
|
|
||||||
ron = "0.8"
|
|
||||||
serde = { workspace = true, features = [ "derive" ] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
time = { version = "0.3", features = [ "local-offset" ] }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
|
@ -1,93 +0,0 @@
|
||||||
use self::config_file::{find_config_file, Configs};
|
|
||||||
pub(crate) use cli::CliConfigs;
|
|
||||||
|
|
||||||
mod cli;
|
|
||||||
mod config_file;
|
|
||||||
|
|
||||||
/// this struct may hold all configuration
|
|
||||||
pub struct Config {
|
|
||||||
pub evaluator: eval::Available,
|
|
||||||
|
|
||||||
pub startup_msg: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
/// Get the configs from all possible places (args, file, env...)
|
|
||||||
pub fn read(args: &CliConfigs) -> Self {
|
|
||||||
// let config = if let Some(config) = &args.config_path {
|
|
||||||
// Ok(config.clone())
|
|
||||||
// } else {
|
|
||||||
// find_config_file()
|
|
||||||
// };
|
|
||||||
let config = args
|
|
||||||
.config_path
|
|
||||||
.clone()
|
|
||||||
.ok_or(())
|
|
||||||
.or_else(|()| find_config_file());
|
|
||||||
|
|
||||||
// try to read a maybe existing config file
|
|
||||||
let config = config.ok().and_then(|path| {
|
|
||||||
Configs::read(path).map_or_else(
|
|
||||||
|e| {
|
|
||||||
eprintln!("Config error: {e:?}");
|
|
||||||
eprintln!("Proceeding with defaults or cli args...");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
Some,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(file) = config {
|
|
||||||
Self {
|
|
||||||
evaluator: args.evaluator.and(file.evaluator).unwrap_or_default(),
|
|
||||||
// this is negated because to an outward api, the negative is more intuitive,
|
|
||||||
// while in the source the other way around is more intuitive
|
|
||||||
startup_msg: !(args.no_startup_message || file.no_startup_message),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self {
|
|
||||||
startup_msg: !args.no_startup_message,
|
|
||||||
evaluator: args.evaluator.unwrap_or_default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod error {
|
|
||||||
/// Errors that can occur when reading configs
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Config {
|
|
||||||
/// The config dir doesn't exist
|
|
||||||
NoConfigDir,
|
|
||||||
/// We didn't find a config file in the config dir
|
|
||||||
NoConfigFileFound,
|
|
||||||
/// An io error happened while reading/opening it!
|
|
||||||
IoError(std::io::Error),
|
|
||||||
/// The given extension (via an argument) isn't known.
|
|
||||||
///
|
|
||||||
/// Occurs if the extension is neither `.json` nor `.ron` (including if there is no extension, in which case this will be `None`).
|
|
||||||
UnknownExtension(Option<String>),
|
|
||||||
/// Wrapper around an `Error` from `serde_json`
|
|
||||||
SerdeJsonError(serde_json::Error),
|
|
||||||
/// Wrapper around a `SpannedError` from `ron`
|
|
||||||
SerdeRonError(ron::error::SpannedError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for Config {
|
|
||||||
fn from(value: std::io::Error) -> Self {
|
|
||||||
Self::IoError(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<serde_json::Error> for Config {
|
|
||||||
fn from(value: serde_json::Error) -> Self {
|
|
||||||
Self::SerdeJsonError(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ron::error::SpannedError> for Config {
|
|
||||||
fn from(value: ron::error::SpannedError) -> Self {
|
|
||||||
Self::SerdeRonError(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use clap::{builder::BoolishValueParser, ArgAction, Args};
|
|
||||||
|
|
||||||
#[derive(Args)]
|
|
||||||
pub(crate) struct CliConfigs {
|
|
||||||
/// How to actually run the pipeline.
|
|
||||||
/// Overrides the config file. Defaults to the debug evaluator.
|
|
||||||
#[arg(short, long)]
|
|
||||||
pub evaluator: Option<eval::Available>,
|
|
||||||
|
|
||||||
/// Read this config file.
|
|
||||||
#[arg(short, long)]
|
|
||||||
pub config_path: Option<PathBuf>,
|
|
||||||
/// Turn off the startup message.
|
|
||||||
///
|
|
||||||
/// The startup message is not constant and depends on the time.
|
|
||||||
#[arg(long, env = "NO_STARTUP_MESSAGE", action = ArgAction::SetTrue, value_parser = BoolishValueParser::new())]
|
|
||||||
pub no_startup_message: bool,
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
use std::{
|
|
||||||
fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::error_reporting::{report_serde_json_err, report_serde_ron_err};
|
|
||||||
|
|
||||||
use super::error::{self, Config};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct Configs {
|
|
||||||
#[serde(default = "default_example_value")]
|
|
||||||
pub example_value: i32,
|
|
||||||
#[serde(default)]
|
|
||||||
pub no_startup_message: bool,
|
|
||||||
pub evaluator: Option<eval::Available>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// what the fuck serde why do i need this
|
|
||||||
fn default_example_value() -> i32 {
|
|
||||||
43
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the location of a config file and check if there is, in fact, a file
|
|
||||||
pub(super) fn find_config_file() -> Result<PathBuf, Config> {
|
|
||||||
let Some(config_path) = dirs::config_dir() else {
|
|
||||||
return Err(Config::NoConfigDir);
|
|
||||||
};
|
|
||||||
|
|
||||||
let ron_path = config_path.with_file_name("config.ron");
|
|
||||||
let json_path = config_path.with_file_name("config.json");
|
|
||||||
|
|
||||||
if Path::new(&ron_path).exists() {
|
|
||||||
Ok(ron_path)
|
|
||||||
} else if Path::new(&json_path).exists() {
|
|
||||||
Ok(json_path)
|
|
||||||
} else {
|
|
||||||
Err(Config::NoConfigFileFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Configs {
|
|
||||||
pub fn read(p: PathBuf) -> Result<Self, error::Config> {
|
|
||||||
match p
|
|
||||||
.extension()
|
|
||||||
.map(|v| v.to_str().expect("config path to be UTF-8"))
|
|
||||||
{
|
|
||||||
Some("ron") => {
|
|
||||||
let f = fs::read_to_string(p)?;
|
|
||||||
ron::from_str(&f).or_else(|e| report_serde_ron_err(&f, &e))
|
|
||||||
}
|
|
||||||
Some("json") => {
|
|
||||||
let f = fs::read_to_string(p)?;
|
|
||||||
serde_json::from_str(&f).or_else(|e| report_serde_json_err(&f, &e))
|
|
||||||
}
|
|
||||||
e => Err(error::Config::UnknownExtension(e.map(str::to_owned))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
use std::process;
|
|
||||||
|
|
||||||
use ron::error::Position;
|
|
||||||
|
|
||||||
/// Report an `Error` from the `serde_json` crate
|
|
||||||
pub fn report_serde_json_err(src: &str, err: &serde_json::Error) -> ! {
|
|
||||||
report_serde_err(src, err.line(), err.column(), err.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Report a `SpannedError` from the `ron` crate
|
|
||||||
pub fn report_serde_ron_err(src: &str, err: &ron::error::SpannedError) -> ! {
|
|
||||||
let Position { line, col } = err.position;
|
|
||||||
report_serde_err(src, line, col, err.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Basic function for reporting serde type of errors
|
|
||||||
fn report_serde_err(src: &str, line: usize, col: usize, msg: String) -> ! {
|
|
||||||
use ariadne::{Label, Report, Source};
|
|
||||||
let offset = try_reconstruct_loc(src, line, col);
|
|
||||||
|
|
||||||
Report::build(ariadne::ReportKind::Error, "test", offset)
|
|
||||||
.with_label(Label::new(("test", offset..offset)).with_message("Something went wrong here!"))
|
|
||||||
.with_message(msg)
|
|
||||||
.with_note("We'd like to give better errors, but serde errors are horrible to work with...")
|
|
||||||
.finish()
|
|
||||||
.eprint(("test", Source::from(src)))
|
|
||||||
.expect("writing error to stderr failed");
|
|
||||||
process::exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reconstruct a byte offset from the line + column numbers typical from serde crates
|
|
||||||
fn try_reconstruct_loc(src: &str, line_nr: usize, col_nr: usize) -> usize {
|
|
||||||
let (line_nr, col_nr) = (line_nr - 1, col_nr - 1);
|
|
||||||
|
|
||||||
src.lines()
|
|
||||||
.enumerate()
|
|
||||||
.fold(0, |acc, (i, line)| match i.cmp(&line_nr) {
|
|
||||||
std::cmp::Ordering::Less => acc + line.len() + 1,
|
|
||||||
std::cmp::Ordering::Equal => acc + col_nr,
|
|
||||||
std::cmp::Ordering::Greater => acc,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
use std::{fs, path::PathBuf};
|
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
|
||||||
use config::{CliConfigs, Config};
|
|
||||||
use dev::DevCommands;
|
|
||||||
use welcome_msg::print_startup_msg;
|
|
||||||
|
|
||||||
mod config;
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
mod error_reporting;
|
|
||||||
mod welcome_msg;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
struct Args {
|
|
||||||
#[command(flatten)]
|
|
||||||
configs: CliConfigs,
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum Commands {
|
|
||||||
Run {
|
|
||||||
/// What file contains the pipeline to evaluate.
|
|
||||||
source: PathBuf,
|
|
||||||
},
|
|
||||||
Dev {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: DevCommands,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// TODO: proper error handling across the whole function
|
|
||||||
// don't forget to also look inside `Config`
|
|
||||||
let args = Args::parse();
|
|
||||||
let cfg = Config::read(&args.configs);
|
|
||||||
|
|
||||||
if cfg.startup_msg {
|
|
||||||
print_startup_msg();
|
|
||||||
}
|
|
||||||
|
|
||||||
match args.command {
|
|
||||||
Commands::Run { source } => {
|
|
||||||
let source = fs::read_to_string(source).expect("can't find source file");
|
|
||||||
let ir = ir::from_ron(&source).expect("failed to parse source to graph ir");
|
|
||||||
|
|
||||||
let mut machine = cfg.evaluator.pick();
|
|
||||||
machine.feed(ir);
|
|
||||||
machine.eval_full();
|
|
||||||
}
|
|
||||||
Commands::Dev {
|
|
||||||
command: dev_command,
|
|
||||||
} => dev_command.run(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod dev {
|
|
||||||
use clap::Subcommand;
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub(crate) enum DevCommands {}
|
|
||||||
|
|
||||||
impl DevCommands {
|
|
||||||
pub fn run(self) {
|
|
||||||
println!("There are currently no dev commands.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
use time::{Month, OffsetDateTime};
|
|
||||||
|
|
||||||
/// Print the startup message which changes depending on system time.
|
|
||||||
pub fn print_startup_msg() {
|
|
||||||
// now or fallback to utc
|
|
||||||
let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
|
|
||||||
|
|
||||||
if now.month() == Month::June {
|
|
||||||
// pride startup message
|
|
||||||
println!("Hello, thanks for using iOwO and happy pride month!");
|
|
||||||
// TODO: proper link with explaination
|
|
||||||
println!("Why pride month is important in {}", now.year());
|
|
||||||
} else {
|
|
||||||
println!("Hello, thanks for using iOwO! :3");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
pub mod read {
|
|
||||||
use image::{io::Reader as ImageReader, DynamicImage};
|
|
||||||
use ir::instruction::read::{Read, SourceType};
|
|
||||||
|
|
||||||
pub fn read(Read { source }: Read) -> DynamicImage {
|
|
||||||
// TODO: actual error handling
|
|
||||||
let img = ImageReader::open(match source {
|
|
||||||
SourceType::File(path) => path,
|
|
||||||
})
|
|
||||||
.expect("something went wrong :(((");
|
|
||||||
|
|
||||||
img.decode().expect("couldn't decode image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod write {
|
|
||||||
use image::{DynamicImage, ImageFormat};
|
|
||||||
use ir::instruction::write::{TargetFormat, TargetType, Write};
|
|
||||||
|
|
||||||
pub fn write(Write { target, format }: Write, input_data: &DynamicImage) {
|
|
||||||
// TODO: actual error handling
|
|
||||||
input_data
|
|
||||||
.save_with_format(
|
|
||||||
match target {
|
|
||||||
TargetType::File(path) => path,
|
|
||||||
},
|
|
||||||
match format {
|
|
||||||
TargetFormat::Jpeg => ImageFormat::Jpeg,
|
|
||||||
TargetFormat::Png => ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.expect("couldn't save file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod filters {
|
|
||||||
pub mod invert {
|
|
||||||
use image::DynamicImage;
|
|
||||||
|
|
||||||
pub fn invert(mut input_data: DynamicImage) -> DynamicImage {
|
|
||||||
input_data.invert();
|
|
||||||
input_data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
use ir::{
|
|
||||||
id,
|
|
||||||
instruction::{Filter, Kind},
|
|
||||||
GraphIr, Instruction, Map,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::value::Variant;
|
|
||||||
mod instr;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct Evaluator {
|
|
||||||
ir: GraphIr,
|
|
||||||
|
|
||||||
/// What the output of each individual streamer, and as result its output sockets, is.
|
|
||||||
/// Grows larger as evaluation continues,
|
|
||||||
/// as there's no mechanism for purging never-to-be-used-anymore instructions yet.
|
|
||||||
evaluated: Map<id::Output, Variant>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::Evaluator for Evaluator {
|
|
||||||
fn feed(&mut self, ir: GraphIr) {
|
|
||||||
self.ir = ir;
|
|
||||||
self.evaluated.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_full(&mut self) {
|
|
||||||
// GraphIr::topological_sort returns InstructionRefs, which are mostly cool
|
|
||||||
// but we'd like to have them owned, so we can call Self::step without lifetime hassle
|
|
||||||
let queue: Vec<Instruction> = self
|
|
||||||
.ir
|
|
||||||
.topological_sort()
|
|
||||||
.into_iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for instr in queue {
|
|
||||||
self.step(instr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Evaluator {
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
fn step(&mut self, instr: Instruction) {
|
|
||||||
// what inputs does this instr need? fetch them
|
|
||||||
let inputs: Vec<_> = instr
|
|
||||||
.input_sources()
|
|
||||||
.iter()
|
|
||||||
.map(|source| {
|
|
||||||
let source_socket = source
|
|
||||||
.as_ref()
|
|
||||||
.expect("all inputs to be connected when an instruction is ran");
|
|
||||||
self.evaluated
|
|
||||||
.get(source_socket)
|
|
||||||
.expect("toposort to yield later instrs only after previous ones")
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// then actually do whatever the instruction should do
|
|
||||||
// NOTE: makes heavy use of index slicing,
|
|
||||||
// on the basis that ir::instruction::Kind::socket_count is correct
|
|
||||||
// TODO: make this a more flexible dispatch-ish arch
|
|
||||||
let output = match instr.kind {
|
|
||||||
Kind::Read(details) => Some(Variant::Image(instr::read::read(details))),
|
|
||||||
Kind::Write(details) => {
|
|
||||||
#[allow(irrefutable_let_patterns)] // will necessarily change
|
|
||||||
let Variant::Image(input) = inputs[0] else {
|
|
||||||
panic!("cannot only write images, but received: `{:?}`", inputs[0]);
|
|
||||||
};
|
|
||||||
instr::write::write(details, input);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Kind::Math(_) => todo!(),
|
|
||||||
Kind::Blend(_) => todo!(),
|
|
||||||
Kind::Noise(_) => todo!(),
|
|
||||||
Kind::Filter(filter_instruction) => match filter_instruction {
|
|
||||||
Filter::Invert => {
|
|
||||||
#[allow(irrefutable_let_patterns)]
|
|
||||||
let Variant::Image(input) = inputs[0] else {
|
|
||||||
panic!(
|
|
||||||
"cannot only filter invert images, but received: `{:?}`",
|
|
||||||
inputs[0]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(Variant::Image(instr::filters::invert::invert(
|
|
||||||
input.clone(),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(output) = output {
|
|
||||||
// TODO: very inaccurate, actually respect individual instructions.
|
|
||||||
// should be implied by a different arch
|
|
||||||
// TODO: all of those should not be public, offer some methods to get this on
|
|
||||||
// `Instruction` instead (can infer short-term based on Kind::socket_count)
|
|
||||||
let socket = id::Output(id::Socket {
|
|
||||||
belongs_to: instr.id,
|
|
||||||
idx: id::SocketIdx(0),
|
|
||||||
});
|
|
||||||
self.evaluated.insert(socket, output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
pub mod debug;
|
|
|
@ -1,43 +0,0 @@
|
||||||
use ir::GraphIr;
|
|
||||||
|
|
||||||
mod kind;
|
|
||||||
mod value;
|
|
||||||
|
|
||||||
/// Can collapse a [`GraphIr`] in meaningful ways and do interesting work on it.
|
|
||||||
///
|
|
||||||
/// It's surprisingly difficult to find a fitting description for this.
|
|
||||||
pub trait Evaluator {
|
|
||||||
/// Take some [`GraphIr`] which will then be processed later.
|
|
||||||
/// May be called multiple times, in which the [`GraphIr`]s should add up.
|
|
||||||
// TODO: atm they definitely don't add up -- add some functionality to GraphIr to
|
|
||||||
// make it combine two graphs into one
|
|
||||||
fn feed(&mut self, ir: GraphIr);
|
|
||||||
|
|
||||||
/// Walk through the _whole_ [`GraphIr`] and run through each instruction.
|
|
||||||
fn eval_full(&mut self);
|
|
||||||
|
|
||||||
// TODO: for an LSP or the like, eval_single which starts at a given instr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The available [`Evaluator`]s.
|
|
||||||
///
|
|
||||||
/// Checklist for adding new ones:
|
|
||||||
///
|
|
||||||
/// 1. Create a new module under the [`kind`] module.
|
|
||||||
/// 2. Add a struct and implement [`Evaluator`] for it.
|
|
||||||
#[derive(Clone, Copy, Debug, Default, clap::ValueEnum, serde::Deserialize, serde::Serialize)]
|
|
||||||
pub enum Available {
|
|
||||||
/// Runs fully on the CPU. Single-threaded, debug-friendly and quick to implement.
|
|
||||||
#[default]
|
|
||||||
Debug,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Available {
|
|
||||||
/// Selects the [`Evaluator`] corresponding to this label.
|
|
||||||
#[must_use]
|
|
||||||
pub fn pick(&self) -> Box<dyn Evaluator> {
|
|
||||||
match self {
|
|
||||||
Self::Debug => Box::<kind::debug::Evaluator>::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
use image::DynamicImage;
|
|
||||||
|
|
||||||
/// Any runtime value that an instruction can input or output.
|
|
||||||
///
|
|
||||||
/// The name is taken from [Godot's `Variant` type],
|
|
||||||
/// which is very similar to this one.
|
|
||||||
///
|
|
||||||
/// [Godot's `Variant` type]: https://docs.godotengine.org/en/stable/classes/class_variant.html
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Variant {
|
|
||||||
Image(DynamicImage),
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "executor-poc"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
image = "0.25.1"
|
|
||||||
indexmap = "2.2.6"
|
|
||||||
nalgebra = "0.33.0"
|
|
||||||
petgraph.workspace = true
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
|
@ -1,128 +0,0 @@
|
||||||
use indexmap::IndexMap;
|
|
||||||
use instructions::Instruction;
|
|
||||||
use petgraph::graph::DiGraph;
|
|
||||||
use types::Type;
|
|
||||||
|
|
||||||
trait Node {
|
|
||||||
fn inputs() -> IndexMap<String, Type>;
|
|
||||||
fn outputs() -> IndexMap<String, Type>;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NodeGraph {
|
|
||||||
graph: DiGraph<Instruction, TypedEdge>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TypedEdge {
|
|
||||||
from: String,
|
|
||||||
to: String,
|
|
||||||
typ: Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
mod instructions {
|
|
||||||
//! This is the lowest level of the IR, the one the executor will use.
|
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use indexmap::{indexmap, IndexMap};
|
|
||||||
pub enum Instruction {
|
|
||||||
// File handling
|
|
||||||
LoadFile,
|
|
||||||
SaveFile,
|
|
||||||
|
|
||||||
ColorMatrix,
|
|
||||||
PosMatrix,
|
|
||||||
|
|
||||||
Blend,
|
|
||||||
SplitChannels,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Instruction {
|
|
||||||
fn inputs(&self) -> IndexMap<String, Type> {
|
|
||||||
match self {
|
|
||||||
Instruction::LoadFile => indexmap! {
|
|
||||||
"path" => Type::Path
|
|
||||||
},
|
|
||||||
Instruction::SaveFile => indexmap! {
|
|
||||||
"path" => Type::Path
|
|
||||||
},
|
|
||||||
|
|
||||||
Instruction::ColorMatrix => indexmap! {
|
|
||||||
"image" => Type::ImageData,
|
|
||||||
"matrix" => Type::Mat(4,5)
|
|
||||||
},
|
|
||||||
Instruction::PosMatrix => indexmap! {
|
|
||||||
"image" => Type::ImageData,
|
|
||||||
"matrix" => Type::Mat(2, 3),
|
|
||||||
},
|
|
||||||
|
|
||||||
Instruction::Blend => todo!(),
|
|
||||||
Instruction::SplitChannels => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn outputs(&self) -> IndexMap<String, Type> {
|
|
||||||
match self {
|
|
||||||
Instruction::LoadFile => indexmap! {
|
|
||||||
"image" => Type::ImageData
|
|
||||||
},
|
|
||||||
Instruction::SaveFile => indexmap! {},
|
|
||||||
|
|
||||||
Instruction::ColorMatrix => indexmap! {
|
|
||||||
"resut" => Type::ImageData
|
|
||||||
},
|
|
||||||
Instruction::PosMatrix => todo!(),
|
|
||||||
|
|
||||||
Instruction::Blend => todo!(),
|
|
||||||
Instruction::SplitChannels => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod types {
|
|
||||||
pub enum Type {
|
|
||||||
// TODO: later do lower level type system for this stuff?
|
|
||||||
// Image(Size, PixelType),
|
|
||||||
// // image data for processing.
|
|
||||||
// // always PixelType::Rgba32F
|
|
||||||
// ImageData(Size),
|
|
||||||
// // stuff that's still to be generated, not sized and no pixeltype
|
|
||||||
// ProceduralImage,
|
|
||||||
ImageData,
|
|
||||||
Text,
|
|
||||||
Integer,
|
|
||||||
Float,
|
|
||||||
Double,
|
|
||||||
Path,
|
|
||||||
Bool,
|
|
||||||
Vec(
|
|
||||||
// length,
|
|
||||||
u8,
|
|
||||||
),
|
|
||||||
Mat(
|
|
||||||
// Rows
|
|
||||||
u8,
|
|
||||||
// Columns
|
|
||||||
u8,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub struct Size {
|
|
||||||
// width: u16,
|
|
||||||
// height: u16,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Pixel types. Taken from variants [here](https://docs.rs/image/latest/image/pub enum.DynamicImage.html).
|
|
||||||
// pub enum PixelType {
|
|
||||||
// Luma8,
|
|
||||||
// LumaA8,
|
|
||||||
// Rgb8,
|
|
||||||
// Rgba8,
|
|
||||||
// Luma16,
|
|
||||||
// LumaA16,
|
|
||||||
// Rgb16,
|
|
||||||
// Rgba16,
|
|
||||||
// Rgb32F,
|
|
||||||
// #[default]
|
|
||||||
// Rgba32F,
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "eval"
|
name = "executor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
@ -8,8 +8,4 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace = true, features = [ "derive" ] }
|
clap = { workspace = true, features = [ "derive" ] }
|
||||||
image = "0.24"
|
image = "0.24"
|
||||||
ir = { path = "../ir" }
|
rpl = { path = "../rpl" }
|
||||||
serde = { workspace = true }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
1
crates/executor/src/cpu/mod.rs
Normal file
1
crates/executor/src/cpu/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub(crate) struct CpuExecutor;
|
41
crates/executor/src/debug/instructions/mod.rs
Normal file
41
crates/executor/src/debug/instructions/mod.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
pub mod read {
|
||||||
|
use image::{io::Reader as ImageReader, DynamicImage};
|
||||||
|
use rpl::instructions::read::{Read, SourceType};
|
||||||
|
|
||||||
|
pub fn read(Read { source, format }: Read) -> DynamicImage {
|
||||||
|
let mut img = ImageReader::open(match source {
|
||||||
|
SourceType::File(path) => path,
|
||||||
|
})
|
||||||
|
.expect("something went wrong :(((");
|
||||||
|
|
||||||
|
img.decode().expect("couldn't decode image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod write {
|
||||||
|
use image::{io::Reader as ImageReader, DynamicImage, ImageFormat};
|
||||||
|
use rpl::instructions::write::{TargetFormat, TargetType, Write};
|
||||||
|
|
||||||
|
pub fn write(Write { target, format }: Write, input_data: DynamicImage) {
|
||||||
|
input_data.save_with_format(
|
||||||
|
match target {
|
||||||
|
TargetType::File(path) => path,
|
||||||
|
},
|
||||||
|
match format {
|
||||||
|
TargetFormat::Jpeg => ImageFormat::Jpeg,
|
||||||
|
TargetFormat::Png => ImageFormat::Png,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod filters {
|
||||||
|
pub mod invert {
|
||||||
|
use image::DynamicImage;
|
||||||
|
|
||||||
|
pub fn invert(mut input_data: DynamicImage) -> DynamicImage {
|
||||||
|
input_data.invert();
|
||||||
|
input_data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
crates/executor/src/debug/mod.rs
Normal file
37
crates/executor/src/debug/mod.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use rpl::instructions::{FilterInstruction, Instruction};
|
||||||
|
|
||||||
|
use crate::{value::DynamicValue, Executor};
|
||||||
|
mod instructions;
|
||||||
|
|
||||||
|
pub struct DebugExecutor;
|
||||||
|
|
||||||
|
impl Executor for DebugExecutor {
|
||||||
|
fn execute(instruction: Instruction, input: Option<DynamicValue>) -> Option<DynamicValue> {
|
||||||
|
match instruction {
|
||||||
|
Instruction::Read(read_instruction) => Some(DynamicValue::Image(
|
||||||
|
instructions::read::read(read_instruction),
|
||||||
|
)),
|
||||||
|
Instruction::Write(write_instruction) => {
|
||||||
|
instructions::write::write(
|
||||||
|
write_instruction,
|
||||||
|
match input {
|
||||||
|
Some(DynamicValue::Image(img)) => img,
|
||||||
|
_ => panic!("awawwawwa"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Instruction::Math(_) => todo!(),
|
||||||
|
Instruction::Blend(_) => todo!(),
|
||||||
|
Instruction::Noise(_) => todo!(),
|
||||||
|
Instruction::Filter(filter_instruction) => match filter_instruction {
|
||||||
|
FilterInstruction::Invert => Some(DynamicValue::Image(
|
||||||
|
instructions::filters::invert::invert(match input {
|
||||||
|
Some(DynamicValue::Image(img)) => img,
|
||||||
|
_ => panic!("invalid value type for invert"),
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
crates/executor/src/lib.rs
Normal file
38
crates/executor/src/lib.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use rpl::instructions::Instruction;
|
||||||
|
use value::DynamicValue;
|
||||||
|
|
||||||
|
mod debug;
|
||||||
|
mod value;
|
||||||
|
|
||||||
|
/// The available executors
|
||||||
|
/// unused in early dev.
|
||||||
|
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||||
|
pub enum Executors {
|
||||||
|
/// the debug executor is single threaded and really, *really* slow. And unstable. Don't use. Unless you're a dev working on this.
|
||||||
|
Debug,
|
||||||
|
/// the CPU executor primarily uses the CPU. Most likely most feature complete, and the fallback.
|
||||||
|
Cpu,
|
||||||
|
/// the Vulkan executor (obviously) uses vulkan. there's a good chance this isn't implemented yet as you're reading this.
|
||||||
|
Vulkan,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Executor {
|
||||||
|
fn execute(instruction: Instruction, input: Option<DynamicValue>) -> Option<DynamicValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute_all(instructions: Vec<Instruction>) {
|
||||||
|
let mut tmp = None;
|
||||||
|
|
||||||
|
for instruction in instructions {
|
||||||
|
tmp = debug::DebugExecutor::execute(instruction, tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scratchpad lol:
|
||||||
|
// execution structure:
|
||||||
|
// 1. take in rpl
|
||||||
|
// 2. analyse/validate structure against allowed executors
|
||||||
|
// 3. assign executors to instructions
|
||||||
|
// 4. optimize
|
||||||
|
// 5. prepare memory management patterns
|
||||||
|
// 6. run
|
5
crates/executor/src/value/mod.rs
Normal file
5
crates/executor/src/value/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
use image::DynamicImage;
|
||||||
|
|
||||||
|
pub enum DynamicValue {
|
||||||
|
Image(DynamicImage),
|
||||||
|
}
|
|
@ -1,84 +0,0 @@
|
||||||
//! Instance identification for instructions and their glue.
|
|
||||||
//!
|
|
||||||
//! Instructions as defined in [`crate::instruction::Kind`] and descendants are very useful,
|
|
||||||
//! but they cannot be directly used as vertices in the graph IR,
|
|
||||||
//! as there may easily be multiple instructions of the same kind in the same program.
|
|
||||||
//!
|
|
||||||
//! Instead, this module offers an alternative way to refer to specific instances:
|
|
||||||
//!
|
|
||||||
//! - [`Instruction`]s are effectively just a number floating in space,
|
|
||||||
//! incremented each time a new instruction is referred to.
|
|
||||||
//! - [`Socket`]s contain
|
|
||||||
//! - what [`Instruction`] they belong to
|
|
||||||
//! - which index they occupy on it
|
|
||||||
//!
|
|
||||||
//! The distinction between [`Input`] and [`Output`] is implemented
|
|
||||||
//! as them being different types.
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// One specific instruction.
|
|
||||||
///
|
|
||||||
/// It does **not** contain what kind of instruction this is.
|
|
||||||
/// Refer to [`crate::instruction::Kind`] for this instead.
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct Instruction(pub(super) u64);
|
|
||||||
|
|
||||||
impl fmt::Debug for Instruction {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "InstrId {}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// On an **instruction**, accepts incoming data.
|
|
||||||
///
|
|
||||||
/// An **instruction** cannot run if any of these are not connected.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct Input(pub(super) Socket);
|
|
||||||
|
|
||||||
impl Input {
|
|
||||||
#[must_use]
|
|
||||||
pub fn socket(&self) -> &Socket {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// On an **instruction**, returns outgoing data to be fed to [`Input`]s.
|
|
||||||
///
|
|
||||||
/// In contrast to [`Input`]s, [`Output`]s may be used or unused.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct Output(pub Socket); // TODO: Restrict publicness to super
|
|
||||||
|
|
||||||
impl Output {
|
|
||||||
#[must_use]
|
|
||||||
pub fn socket(&self) -> &Socket {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An unspecified socket on a specific **instruction**,
|
|
||||||
/// and where it is on that **instruction**.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct Socket {
|
|
||||||
pub belongs_to: Instruction,
|
|
||||||
pub idx: SocketIdx,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Where a [`Socket`] is on one **instruction**.
|
|
||||||
///
|
|
||||||
/// Note that this does **not** identify a [`Socket`] globally.
|
|
||||||
/// There may be multiple [`Socket`]s sharing the same [`SocketIdx`],
|
|
||||||
/// but on different [`Instruction`]s.
|
|
||||||
///
|
|
||||||
/// This really only serves for denoting where a socket is,
|
|
||||||
/// when it's already clear which instruction is referred to.
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct SocketIdx(pub u16); // TODO: Restrict publicness to super
|
|
||||||
|
|
||||||
impl fmt::Debug for SocketIdx {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
pub mod read;
|
|
||||||
pub mod write;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub enum Kind {
|
|
||||||
// TODO: `read::Read` and `write::Write` hold real values atm -- they should actually
|
|
||||||
// point to `Const` instructions instead (which are... yet to be done...)
|
|
||||||
Read(read::Read),
|
|
||||||
Write(write::Write),
|
|
||||||
Math(Math),
|
|
||||||
Blend(Blend),
|
|
||||||
Noise(Noise),
|
|
||||||
Filter(Filter),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub enum Math {
|
|
||||||
Add,
|
|
||||||
Subtract,
|
|
||||||
Multiply,
|
|
||||||
Divide,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub enum Blend {
|
|
||||||
Normal,
|
|
||||||
Multiply,
|
|
||||||
Additive,
|
|
||||||
Overlay,
|
|
||||||
Screen,
|
|
||||||
Subtractive,
|
|
||||||
Difference,
|
|
||||||
Darken,
|
|
||||||
Lighten,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub enum Noise {
|
|
||||||
Perlin,
|
|
||||||
Simplex,
|
|
||||||
Voronoi,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub enum Filter {
|
|
||||||
Invert,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: given that this basically matches on all instructions, we may need to use
|
|
||||||
// the visitor pattern in future here, or at least get them behind traits
|
|
||||||
// which should allow far more nuanced description
|
|
||||||
impl Kind {
|
|
||||||
/// Returns how many sockets this kind of instruction has.
|
|
||||||
#[must_use]
|
|
||||||
pub fn socket_count(&self) -> SocketCount {
|
|
||||||
match self {
|
|
||||||
Self::Read(_) => (0, 1),
|
|
||||||
Self::Write(_) => (1, 0),
|
|
||||||
Self::Math(_) | Self::Blend(_) => (2, 1),
|
|
||||||
Self::Noise(_) => {
|
|
||||||
todo!("how many arguments does noise take? how many outputs does it have?")
|
|
||||||
}
|
|
||||||
Self::Filter(Filter::Invert) => (1, 1),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How many sockets are on an instruction?
|
|
||||||
pub struct SocketCount {
|
|
||||||
pub inputs: u16,
|
|
||||||
pub outputs: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(u16, u16)> for SocketCount {
|
|
||||||
fn from((inputs, outputs): (u16, u16)) -> Self {
|
|
||||||
Self { inputs, outputs }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct Read {
|
|
||||||
pub source: SourceType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub enum SourceType {
|
|
||||||
File(PathBuf),
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct Write {
|
|
||||||
pub target: TargetType,
|
|
||||||
pub format: TargetFormat,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub enum TargetType {
|
|
||||||
File(PathBuf),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub enum TargetFormat {
|
|
||||||
Jpeg,
|
|
||||||
Png,
|
|
||||||
}
|
|
|
@ -1,353 +0,0 @@
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
|
|
||||||
use instruction::SocketCount;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
pub mod id;
|
|
||||||
pub mod instruction;
|
|
||||||
pub mod semi_human;
|
|
||||||
|
|
||||||
pub type Map<K, V> = std::collections::BTreeMap<K, V>;
|
|
||||||
pub type Set<T> = std::collections::BTreeSet<T>;
|
|
||||||
|
|
||||||
/// Gives you a super well typed graph IR for a given human-readable repr.
|
|
||||||
///
|
|
||||||
/// Look at [`semi_human::GraphIr`] and the test files in the repo at `testfiles/`
|
|
||||||
/// to see what the RON should look like.
|
|
||||||
/// No, we don't want you to write out [`GraphIr`] in full by hand.
|
|
||||||
/// That's something for the machines to do.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns an error if the parsed source is not a valid human-readable graph IR.
|
|
||||||
pub fn from_ron(source: &str) -> ron::error::SpannedResult<GraphIr> {
|
|
||||||
let human_repr: semi_human::GraphIr = ron::from_str(source)?;
|
|
||||||
Ok(human_repr.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The toplevel representation of a whole pipeline.
|
|
||||||
///
|
|
||||||
/// # DAGs
|
|
||||||
///
|
|
||||||
/// Pipelines may not be fully linear. They may branch out and recombine later on.
|
|
||||||
/// As such, the representation for them which is currently used is a
|
|
||||||
/// [**D**irected **A**cyclic **G**raph](https://en.wikipedia.org/wiki/Directed_acyclic_graph).
|
|
||||||
///
|
|
||||||
/// For those who are already familiar with graphs, a DAG is one, except that:
|
|
||||||
///
|
|
||||||
/// - It is **directed**: Edges have a direction they point to.
|
|
||||||
/// In this case, edges point from the outputs of streamers to inputs of consumers.
|
|
||||||
/// - It is **acyclic**: Those directed edges may not form loops.
|
|
||||||
/// In other words, if one follows edges only in their direction, it must be impossible
|
|
||||||
/// to come back to an already visited node.
|
|
||||||
///
|
|
||||||
/// Here, if an edge points from _A_ to _B_ (`A --> B`),
|
|
||||||
/// then _A_ is called a **dependency** or an **input source** of _B_,
|
|
||||||
/// and _B_ is called a **dependent** or an **output target** of _A_.
|
|
||||||
///
|
|
||||||
/// The DAG also enables another neat operation:
|
|
||||||
/// [Topological sorting](https://en.wikipedia.org/wiki/Topological_sorting).
|
|
||||||
/// This allows to put the entire graph into a linear list,
|
|
||||||
/// where it's guaranteed that once a vertex is visited,
|
|
||||||
/// all dependencies of it will have been visited already as well.
|
|
||||||
///
|
|
||||||
/// The representation used here in specific is a bit more complicated,
|
|
||||||
/// since **instructions** directly aren't just connected to one another,
|
|
||||||
/// but their **sockets** are instead.
|
|
||||||
///
|
|
||||||
/// So the vertices of the DAG are the **sockets**
|
|
||||||
/// (which are either [`id::Input`] or [`id::Output`] depending on the direction),
|
|
||||||
/// and each **socket** in turn belongs to an **instruction**.
|
|
||||||
///
|
|
||||||
/// # Usage
|
|
||||||
///
|
|
||||||
/// - If you want to build one from scratch,
|
|
||||||
/// add a few helper methods like
|
|
||||||
/// constructing an empty one,
|
|
||||||
/// adding instructions and
|
|
||||||
/// adding edges
|
|
||||||
/// - If you want to construct one from an existing repr,
|
|
||||||
/// maybe you want to use [`semi_human::GraphIr`].
|
|
||||||
///
|
|
||||||
/// # Storing additional data
|
|
||||||
///
|
|
||||||
/// Chances are the graph IR seems somewhat fit to put metadata in it.
|
|
||||||
/// However, most likely you're interacting in context of some other system,
|
|
||||||
/// and also want to manage and index that data on your own.
|
|
||||||
///
|
|
||||||
/// As such, consider using _secondary_ maps instead.
|
|
||||||
/// That is, store in a data structure _you_ own a mapping
|
|
||||||
/// from [`id`]s
|
|
||||||
/// to whatever data you need.
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
|
|
||||||
pub struct GraphIr {
|
|
||||||
/// "Backbone" storage of all **instruction** IDs to
|
|
||||||
/// what **kind of instruction** they are.
|
|
||||||
instructions: Map<id::Instruction, instruction::Kind>,
|
|
||||||
|
|
||||||
/// How the data flows forward. **Dependencies** map to **dependents** here.
|
|
||||||
edges: Map<id::Output, Set<id::Input>>,
|
|
||||||
/// How the data flows backward. **Dependents** map to **dependencies** here.
|
|
||||||
rev_edges: Map<id::Input, id::Output>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this impl block, but actually the whole module, screams for tests
|
|
||||||
impl GraphIr {
|
|
||||||
/// Look "backwards" in the graph,
|
|
||||||
/// and find out what instructions need to be done before this one.
|
|
||||||
/// The input slots are visited in order.
|
|
||||||
///
|
|
||||||
/// - The iterator returns individually [`Some`]`(`[`None`]`)` if the corresponding slot is
|
|
||||||
/// not connected.
|
|
||||||
///
|
|
||||||
/// The same caveats as for [`GraphIr::resolve`] apply.
|
|
||||||
#[must_use]
|
|
||||||
pub fn input_sources(
|
|
||||||
&self,
|
|
||||||
subject: &id::Instruction,
|
|
||||||
) -> Option<impl Iterator<Item = Option<&id::Output>> + '_> {
|
|
||||||
let (subject, kind) = self.instructions.get_key_value(subject)?;
|
|
||||||
let SocketCount { inputs, .. } = kind.socket_count();
|
|
||||||
|
|
||||||
Some((0..inputs).map(|idx| {
|
|
||||||
let input = id::Input(socket(subject, idx));
|
|
||||||
self.rev_edges.get(&input)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Look "forwards" in the graph to see what other instructions this instruction feeds into.
|
|
||||||
///
|
|
||||||
/// The output slots represent the top-level iterator,
|
|
||||||
/// and each one's connections are emitted one level below.
|
|
||||||
///
|
|
||||||
/// Just [`Iterator::flatten`] if you are not interested in the slots.
|
|
||||||
///
|
|
||||||
/// The same caveats as for [`GraphIr::resolve`] apply.
|
|
||||||
#[must_use]
|
|
||||||
pub fn output_targets(
|
|
||||||
&self,
|
|
||||||
subject: &id::Instruction,
|
|
||||||
) -> Option<impl Iterator<Item = Option<&Set<id::Input>>> + '_> {
|
|
||||||
let (subject, kind) = self.instructions.get_key_value(subject)?;
|
|
||||||
let SocketCount { outputs, .. } = kind.socket_count();
|
|
||||||
|
|
||||||
Some((0..outputs).map(|idx| {
|
|
||||||
let output = id::Output(socket(subject, idx));
|
|
||||||
self.edges.get(&output)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the instruction corresponding to the given ID.
|
|
||||||
/// Returns [`None`] if there is no such instruction in this graph IR.
|
|
||||||
///
|
|
||||||
/// Theoretically this could be fixed easily at the expense of some memory
|
|
||||||
/// by just incrementing and storing some global counter,
|
|
||||||
/// however, at the moment there's no compelling reason
|
|
||||||
/// to actually have multiple [`GraphIr`]s at one point in time.
|
|
||||||
/// Open an issue if that poses a problem for you.
|
|
||||||
#[must_use]
|
|
||||||
pub fn resolve<'ir>(&'ir self, subject: &id::Instruction) -> Option<InstructionRef<'ir>> {
|
|
||||||
let (id, kind) = self.instructions.get_key_value(subject)?;
|
|
||||||
|
|
||||||
let input_sources = self.input_sources(subject)?.collect();
|
|
||||||
let output_targets = self.output_targets(subject)?.collect();
|
|
||||||
|
|
||||||
Some(InstructionRef {
|
|
||||||
id,
|
|
||||||
kind,
|
|
||||||
input_sources,
|
|
||||||
output_targets,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the instruction this input belongs to.
|
|
||||||
///
|
|
||||||
/// The same caveats as for [`GraphIr::resolve`] apply.
|
|
||||||
#[must_use]
|
|
||||||
pub fn owner_of_input<'ir>(&'ir self, input: &id::Input) -> Option<InstructionRef<'ir>> {
|
|
||||||
self.resolve(&input.socket().belongs_to)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the instruction this output belongs to.
|
|
||||||
///
|
|
||||||
/// The same caveats as for [`GraphIr::resolve`] apply.
|
|
||||||
#[must_use]
|
|
||||||
pub fn owner_of_output<'ir>(&'ir self, output: &id::Output) -> Option<InstructionRef<'ir>> {
|
|
||||||
self.resolve(&output.socket().belongs_to)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the order in which the instructions could be visited
|
|
||||||
/// in order to ensure that all dependencies are resolved
|
|
||||||
/// before a vertex is visited.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if there are any cycles in the IR, as it needs to be a DAG.
|
|
||||||
#[must_use]
|
|
||||||
// yes, this function could probably return an iterator and be lazy
|
|
||||||
// no, not today
|
|
||||||
pub fn topological_sort(&self) -> Vec<InstructionRef> {
|
|
||||||
// count how many incoming edges each vertex has
|
|
||||||
let mut nonzero_input_counts: Map<_, NonZeroUsize> =
|
|
||||||
self.rev_edges
|
|
||||||
.iter()
|
|
||||||
.fold(Map::new(), |mut count, (input, _)| {
|
|
||||||
let _ = *count
|
|
||||||
.entry(input.socket().belongs_to.clone())
|
|
||||||
.and_modify(|count| *count = count.saturating_add(1))
|
|
||||||
.or_insert(NonZeroUsize::MIN);
|
|
||||||
count
|
|
||||||
});
|
|
||||||
|
|
||||||
// are there any unconnected ones we could start with?
|
|
||||||
// TODO: experiment if a VecDeque with some ordering fun is digested better by the executor
|
|
||||||
let no_inputs: Vec<_> = {
|
|
||||||
let nonzero: Set<_> = nonzero_input_counts.keys().collect();
|
|
||||||
let all: Set<_> = self.instructions.keys().collect();
|
|
||||||
all.difference(&nonzero).copied().cloned().collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
// then let's find the order!
|
|
||||||
let mut order = Vec::new();
|
|
||||||
let mut active_queue = no_inputs;
|
|
||||||
|
|
||||||
while let Some(current) = active_queue.pop() {
|
|
||||||
// now that this vertex is visited and resolved,
|
|
||||||
// make sure all dependents notice that
|
|
||||||
|
|
||||||
let dependents = self
|
|
||||||
.output_targets(¤t)
|
|
||||||
.expect("graph to be consistent")
|
|
||||||
.flatten()
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
for dependent_input in dependents {
|
|
||||||
let dependent = &dependent_input.socket().belongs_to;
|
|
||||||
|
|
||||||
// how many inputs are connected to this dependent without us?
|
|
||||||
let count = nonzero_input_counts
|
|
||||||
.get_mut(dependent)
|
|
||||||
.expect("connected output must refer to non-zero input");
|
|
||||||
|
|
||||||
let new = NonZeroUsize::new(count.get() - 1);
|
|
||||||
if let Some(new) = new {
|
|
||||||
// aww, still some
|
|
||||||
*count = new;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// none, that means this one is free now! let's throw it onto the active queue then
|
|
||||||
let (now_active, _) = nonzero_input_counts
|
|
||||||
.remove_entry(dependent)
|
|
||||||
.expect("connected output must refer to non-zero input");
|
|
||||||
active_queue.push(now_active);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check if this instruction is "well-fed", that is, has all the inputs it needs,
|
|
||||||
// and if not, panic
|
|
||||||
order.push(self.resolve(¤t).expect("graph to be consistent"));
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
nonzero_input_counts.is_empty(),
|
|
||||||
concat!(
|
|
||||||
"topological sort didn't cover all instructions\n",
|
|
||||||
"either there are unconnected inputs, or there is a cycle\n",
|
|
||||||
"unresolved instructions:\n",
|
|
||||||
"{:#?}"
|
|
||||||
),
|
|
||||||
nonzero_input_counts,
|
|
||||||
);
|
|
||||||
|
|
||||||
order
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constructs an [`id::Socket`] a bit more tersely.
|
|
||||||
fn socket(id: &id::Instruction, idx: u16) -> id::Socket {
|
|
||||||
id::Socket {
|
|
||||||
belongs_to: id.clone(),
|
|
||||||
idx: id::SocketIdx(idx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A full instruction bundeled in context, with its inputs and outputs.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct Instruction {
|
|
||||||
pub id: id::Instruction,
|
|
||||||
pub kind: instruction::Kind,
|
|
||||||
|
|
||||||
// can't have these two public since then a user might corrupt their length
|
|
||||||
input_sources: Vec<Option<id::Output>>,
|
|
||||||
output_targets: Vec<Set<id::Input>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Instruction {
|
|
||||||
/// Where this instruction gets its inputs from.
|
|
||||||
///
|
|
||||||
/// [`None`] means that this input is unfilled,
|
|
||||||
/// and must be filled before the instruction can be ran.
|
|
||||||
#[must_use]
|
|
||||||
pub fn input_sources(&self) -> &[Option<id::Output>] {
|
|
||||||
&self.input_sources
|
|
||||||
}
|
|
||||||
|
|
||||||
/// To whom outputs are sent.
|
|
||||||
#[must_use]
|
|
||||||
pub fn output_targets(&self) -> &[Set<id::Input>] {
|
|
||||||
&self.output_targets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [`Instruction`], but every single field is borrowed instead.
|
|
||||||
/// See its docs.
|
|
||||||
///
|
|
||||||
/// Use the [`From`] impl to handily convert into an [`Instruction`].
|
|
||||||
/// The other way around is unlikely to be wanted — since you already have an [`Instruction`],
|
|
||||||
/// chances are you just want to take a reference (`&`) of it.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct InstructionRef<'ir> {
|
|
||||||
pub id: &'ir id::Instruction,
|
|
||||||
pub kind: &'ir instruction::Kind,
|
|
||||||
|
|
||||||
input_sources: Vec<Option<&'ir id::Output>>,
|
|
||||||
output_targets: Vec<Option<&'ir Set<id::Input>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'ir> InstructionRef<'ir> {
|
|
||||||
/// Where this instruction gets its inputs from.
|
|
||||||
///
|
|
||||||
/// [`None`] means that this input is unfilled,
|
|
||||||
/// and must be filled before the instruction can be ran.
|
|
||||||
#[must_use]
|
|
||||||
pub fn input_sources(&self) -> &[Option<&'ir id::Output>] {
|
|
||||||
&self.input_sources
|
|
||||||
}
|
|
||||||
|
|
||||||
/// To whom outputs are sent.
|
|
||||||
#[must_use]
|
|
||||||
pub fn output_targets(&self) -> &[Option<&'ir Set<id::Input>>] {
|
|
||||||
&self.output_targets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// would love to use ToOwned but Rust has no specialization yet
|
|
||||||
// and it'd hurt a blanket impl of ToOwned otherwise
|
|
||||||
impl From<InstructionRef<'_>> for Instruction {
|
|
||||||
fn from(source: InstructionRef<'_>) -> Self {
|
|
||||||
Self {
|
|
||||||
id: source.id.clone(),
|
|
||||||
kind: source.kind.clone(),
|
|
||||||
input_sources: source
|
|
||||||
.input_sources
|
|
||||||
.into_iter()
|
|
||||||
.map(Option::<&_>::cloned)
|
|
||||||
.collect(),
|
|
||||||
output_targets: source
|
|
||||||
.output_targets
|
|
||||||
.into_iter()
|
|
||||||
.map(|outputs| outputs.map(Clone::clone).unwrap_or_default())
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
//! The midterm solution for source representation, until we've got a nice source frontend.
|
|
||||||
//!
|
|
||||||
//! Sacrifices type expressivity for the sake of typability in [RON] files.
|
|
||||||
//!
|
|
||||||
//! **If you want to construct a graph IR programmatically,
|
|
||||||
//! use [`crate::GraphIr`] directly instead,
|
|
||||||
//! as it gives you more control to specify where your instructions came from.**
|
|
||||||
//!
|
|
||||||
//! [RON]: https://docs.rs/ron/latest/ron/
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{id, instruction, Map, Set};
|
|
||||||
|
|
||||||
/// Semi-human-{read,writ}able [`crate::GraphIr`] with far less useful types.
|
|
||||||
///
|
|
||||||
/// **Do not use this if you want to programatically construct IR.**
|
|
||||||
/// Instead, directly use [`crate::GraphIr`].
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
|
||||||
pub struct GraphIr {
|
|
||||||
/// See [`crate::GraphIr::instructions`], just that a simple number is used for the ID instead
|
|
||||||
pub(crate) instructions: Map<u64, instruction::Kind>,
|
|
||||||
/// See [`crate::GraphIr::edges`], the forward edges.
|
|
||||||
/// RON wants you to type the set as if it were a list.
|
|
||||||
pub(crate) edges: Map<Socket, Set<Socket>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
|
||||||
pub struct Socket {
|
|
||||||
/// ID of the instruction this socket is on.
|
|
||||||
pub(crate) on: u64,
|
|
||||||
pub(crate) idx: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Socket> for id::Socket {
|
|
||||||
fn from(source: Socket) -> Self {
|
|
||||||
Self {
|
|
||||||
belongs_to: id::Instruction(source.on),
|
|
||||||
idx: id::SocketIdx(source.idx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<GraphIr> for crate::GraphIr {
|
|
||||||
fn from(source: GraphIr) -> Self {
|
|
||||||
let edges = source.edges.clone();
|
|
||||||
Self {
|
|
||||||
instructions: source
|
|
||||||
.instructions
|
|
||||||
.into_iter()
|
|
||||||
.map(|(id, kind)| (id::Instruction(id), kind))
|
|
||||||
.collect(),
|
|
||||||
edges: type_edges(source.edges),
|
|
||||||
rev_edges: reverse_and_type_edges(edges),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn type_edges(edges: Map<Socket, Set<Socket>>) -> Map<id::Output, Set<id::Input>> {
|
|
||||||
edges
|
|
||||||
.into_iter()
|
|
||||||
.map(|(output, inputs)| {
|
|
||||||
let output = id::Output(output.into());
|
|
||||||
let inputs = inputs.into_iter().map(Into::into).map(id::Input).collect();
|
|
||||||
(output, inputs)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reverse_and_type_edges(edges: Map<Socket, Set<Socket>>) -> Map<id::Input, id::Output> {
|
|
||||||
edges
|
|
||||||
.into_iter()
|
|
||||||
.fold(Map::new(), |mut rev_edges, (output, inputs)| {
|
|
||||||
let output = id::Output(output.into());
|
|
||||||
|
|
||||||
for input in inputs {
|
|
||||||
let input = id::Input(input.into());
|
|
||||||
let previous = rev_edges.insert(input, output.clone());
|
|
||||||
if let Some(previous) = previous {
|
|
||||||
// TODO: handle this with a TryFrom impl
|
|
||||||
panic!("two or more outputs referred to the same input {previous:#?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rev_edges
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "json-pawarser"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
logos = "0.14.2"
|
|
||||||
enumset = "1.1.3"
|
|
||||||
rowan = "0.15.15"
|
|
||||||
pawarser = { path = "../pawarser" }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
|
@ -1,78 +0,0 @@
|
||||||
use array::array;
|
|
||||||
use enumset::{enum_set, EnumSet};
|
|
||||||
use pawarser::parser::ParserBuilder;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
syntax_error::SyntaxError,
|
|
||||||
syntax_kind::{lex, SyntaxKind},
|
|
||||||
};
|
|
||||||
|
|
||||||
use self::object::object;
|
|
||||||
|
|
||||||
mod array;
|
|
||||||
mod object;
|
|
||||||
|
|
||||||
pub(crate) type Parser<'src> = pawarser::Parser<'src, SyntaxKind, SyntaxError>;
|
|
||||||
pub(crate) type CompletedMarker = pawarser::CompletedMarker<SyntaxKind, SyntaxError>;
|
|
||||||
|
|
||||||
const BASIC_VALUE_TOKENS: EnumSet<SyntaxKind> =
|
|
||||||
enum_set!(SyntaxKind::BOOL | SyntaxKind::NULL | SyntaxKind::NUMBER | SyntaxKind::STRING);
|
|
||||||
|
|
||||||
pub fn value(p: &mut Parser) -> bool {
|
|
||||||
if BASIC_VALUE_TOKENS.contains(p.current()) {
|
|
||||||
p.do_bump();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
object(p).or_else(|| array(p)).is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::{
|
|
||||||
test_utils::{check_parser, gen_checks},
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn value_lit() {
|
|
||||||
gen_checks! {value;
|
|
||||||
r#""helo world""# => r#"ROOT { STRING "\"helo world\""; }"#,
|
|
||||||
"42" => r#"ROOT { NUMBER "42"; }"#,
|
|
||||||
"null" => r#"ROOT { NULL "null"; }"#,
|
|
||||||
"true" => r#"ROOT { BOOL "true"; }"#,
|
|
||||||
"false" => r#"ROOT { BOOL "false"; }"#
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test_utils {
|
|
||||||
use pawarser::parser::ParserBuilder;
|
|
||||||
|
|
||||||
use crate::syntax_kind::{lex, SyntaxKind};
|
|
||||||
|
|
||||||
use super::Parser;
|
|
||||||
|
|
||||||
macro_rules! gen_checks {
|
|
||||||
($fn_to_test:ident; $($in:literal => $out:literal),+) => {
|
|
||||||
$(crate::grammar::test_utils::check_parser($in, |p| { $fn_to_test(p); }, $out);)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) use gen_checks;
|
|
||||||
|
|
||||||
pub(super) fn check_parser(input: &str, parser_fn: fn(&mut Parser), expected_output: &str) {
|
|
||||||
let toks = lex(input);
|
|
||||||
let mut p: Parser = ParserBuilder::new(toks)
|
|
||||||
.add_meaningless(SyntaxKind::WHITESPACE)
|
|
||||||
.add_meaningless(SyntaxKind::NEWLINE)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
parser_fn(&mut p);
|
|
||||||
|
|
||||||
let out = p.finish();
|
|
||||||
|
|
||||||
assert_eq!(format!("{out:?}").trim_end(), expected_output);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
use crate::{syntax_error::SyntaxError, syntax_kind::SyntaxKind};
|
|
||||||
|
|
||||||
use super::{value, CompletedMarker, Parser};
|
|
||||||
|
|
||||||
pub(super) fn array(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
let array_start = p.start("array");
|
|
||||||
|
|
||||||
if !p.eat(SyntaxKind::BRACKET_OPEN) {
|
|
||||||
array_start.abandon(p);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let el = p.start("arr_el");
|
|
||||||
value(p);
|
|
||||||
el.complete(p, SyntaxKind::ELEMENT);
|
|
||||||
|
|
||||||
while p.at(SyntaxKind::COMMA) {
|
|
||||||
let potential_trailing_comma = p.start("potential_trailing_comma");
|
|
||||||
|
|
||||||
p.eat(SyntaxKind::COMMA);
|
|
||||||
let maybe_el = p.start("arr_el");
|
|
||||||
if !value(p) {
|
|
||||||
maybe_el.abandon(p);
|
|
||||||
potential_trailing_comma.complete(p, SyntaxKind::TRAILING_COMMA);
|
|
||||||
} else {
|
|
||||||
maybe_el.complete(p, SyntaxKind::ELEMENT);
|
|
||||||
potential_trailing_comma.abandon(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(if !p.eat(SyntaxKind::BRACKET_CLOSE) {
|
|
||||||
array_start.error(p, SyntaxError::UnclosedArray)
|
|
||||||
} else {
|
|
||||||
array_start.complete(p, SyntaxKind::ARRAY)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
use crate::{grammar::value, syntax_error::SyntaxError, syntax_kind::SyntaxKind};
|
|
||||||
|
|
||||||
use super::{CompletedMarker, Parser, BASIC_VALUE_TOKENS};
|
|
||||||
|
|
||||||
pub(super) fn object(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
let obj_start = p.start("object");
|
|
||||||
|
|
||||||
if !p.eat(SyntaxKind::BRACE_OPEN) {
|
|
||||||
obj_start.abandon(p);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
member(p);
|
|
||||||
while p.at(SyntaxKind::COMMA) {
|
|
||||||
// not always an error, later configurable
|
|
||||||
let potential_trailing_comma = p.start("potential_trailing_comma");
|
|
||||||
p.eat(SyntaxKind::COMMA);
|
|
||||||
|
|
||||||
if member(p).is_none() {
|
|
||||||
potential_trailing_comma.complete(p, SyntaxKind::TRAILING_COMMA);
|
|
||||||
} else {
|
|
||||||
potential_trailing_comma.abandon(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(if p.eat(SyntaxKind::BRACE_CLOSE) {
|
|
||||||
obj_start.complete(p, SyntaxKind::OBJECT)
|
|
||||||
} else {
|
|
||||||
obj_start.error(p, SyntaxError::UnclosedObject)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn member(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
let member_start = p.start("member");
|
|
||||||
|
|
||||||
if p.at(SyntaxKind::BRACE_CLOSE) {
|
|
||||||
member_start.abandon(p);
|
|
||||||
return None;
|
|
||||||
} else if p.at(SyntaxKind::STRING) {
|
|
||||||
let member_name_start = p.start("member_name");
|
|
||||||
p.eat(SyntaxKind::STRING);
|
|
||||||
member_name_start.complete(p, SyntaxKind::MEMBER_NAME);
|
|
||||||
} else {
|
|
||||||
return todo!("handle other tokens: {:?}", p.current());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.eat(SyntaxKind::COLON) {
|
|
||||||
todo!("handle wrong tokens")
|
|
||||||
}
|
|
||||||
|
|
||||||
let member_value_start = p.start("member_value_start");
|
|
||||||
if value(p) {
|
|
||||||
member_value_start.complete(p, SyntaxKind::MEMBER_VALUE);
|
|
||||||
Some(member_start.complete(p, SyntaxKind::MEMBER))
|
|
||||||
} else {
|
|
||||||
member_value_start.abandon(p);
|
|
||||||
let e = member_start.error(p, SyntaxError::MemberMissingValue);
|
|
||||||
Some(
|
|
||||||
e.precede(p, "member but failed already")
|
|
||||||
.complete(p, SyntaxKind::MEMBER),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::grammar::{
|
|
||||||
object::{member, object},
|
|
||||||
test_utils::gen_checks,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn object_basic() {
|
|
||||||
gen_checks! {object;
|
|
||||||
r#"{"a": "b"}"# => r#"ROOT { OBJECT { BRACE_OPEN "{"; MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; WHITESPACE " "; MEMBER_VALUE { STRING "\"b\""; } } BRACE_CLOSE "}"; } }"#,
|
|
||||||
r#"{"a": 42}"# => r#"ROOT { OBJECT { BRACE_OPEN "{"; MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; WHITESPACE " "; MEMBER_VALUE { NUMBER "42"; } } BRACE_CLOSE "}"; } }"#,
|
|
||||||
r#"{"a": "b""# => r#"ROOT { PARSE_ERR: UnclosedObject { BRACE_OPEN "{"; MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; WHITESPACE " "; MEMBER_VALUE { STRING "\"b\""; } } } }"#,
|
|
||||||
r#"{"a": }"# => r#"ROOT { OBJECT { BRACE_OPEN "{"; MEMBER { PARSE_ERR: MemberMissingValue { MEMBER_NAME { STRING "\"a\""; } COLON ":"; } } WHITESPACE " "; BRACE_CLOSE "}"; } }"#,
|
|
||||||
r#"{"a":"# => r#"ROOT { PARSE_ERR: UnclosedObject { BRACE_OPEN "{"; MEMBER { PARSE_ERR: MemberMissingValue { MEMBER_NAME { STRING "\"a\""; } COLON ":"; } } } }"#,
|
|
||||||
r#"{"a":true,}"# => r#"ROOT { OBJECT { BRACE_OPEN "{"; MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; MEMBER_VALUE { BOOL "true"; } } TRAILING_COMMA { COMMA ","; } BRACE_CLOSE "}"; } }"#
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn member_basic() {
|
|
||||||
gen_checks! {member;
|
|
||||||
r#""a": "b""# => r#"ROOT { MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; WHITESPACE " "; MEMBER_VALUE { STRING "\"b\""; } } }"#,
|
|
||||||
r#""a": 42"# => r#"ROOT { MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; WHITESPACE " "; MEMBER_VALUE { NUMBER "42"; } } }"#,
|
|
||||||
r#""a":"# => r#"ROOT { MEMBER { PARSE_ERR: MemberMissingValue { MEMBER_NAME { STRING "\"a\""; } COLON ":"; } } }"#
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
mod grammar;
|
|
||||||
mod syntax_error;
|
|
||||||
mod syntax_kind;
|
|
|
@ -1,11 +0,0 @@
|
||||||
use crate::syntax_kind::SyntaxKind;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum SyntaxError {
|
|
||||||
UnclosedObject,
|
|
||||||
UnclosedArray,
|
|
||||||
DisallowedKeyType(SyntaxKind),
|
|
||||||
MemberMissingValue,
|
|
||||||
UnexpectedTrailingComma,
|
|
||||||
}
|
|
||||||
impl pawarser::parser::SyntaxError for SyntaxError {}
|
|
|
@ -1,117 +0,0 @@
|
||||||
use logos::Logos;
|
|
||||||
|
|
||||||
pub fn lex(src: &str) -> Vec<(SyntaxKind, &str)> {
|
|
||||||
let mut lex = SyntaxKind::lexer(src);
|
|
||||||
let mut r = Vec::new();
|
|
||||||
|
|
||||||
while let Some(tok_res) = lex.next() {
|
|
||||||
r.push((tok_res.unwrap_or(SyntaxKind::LEX_ERR), lex.slice()))
|
|
||||||
}
|
|
||||||
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(enumset::EnumSetType, Debug, Logos, PartialEq, Eq, Clone, Copy, Hash)]
|
|
||||||
#[repr(u16)]
|
|
||||||
#[enumset(no_super_impls)]
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
pub enum SyntaxKind {
|
|
||||||
OBJECT,
|
|
||||||
MEMBER,
|
|
||||||
MEMBER_NAME,
|
|
||||||
MEMBER_VALUE,
|
|
||||||
|
|
||||||
ARRAY,
|
|
||||||
ELEMENT,
|
|
||||||
|
|
||||||
// SyntaxKinds for future json5/etc support
|
|
||||||
TRAILING_COMMA,
|
|
||||||
|
|
||||||
// Tokens
|
|
||||||
// Regexes adapted from [the logos handbook](https://logos.maciej.codes/examples/json_borrowed.html)
|
|
||||||
#[token("true")]
|
|
||||||
#[token("false")]
|
|
||||||
BOOL,
|
|
||||||
#[token("{")]
|
|
||||||
BRACE_OPEN,
|
|
||||||
#[token("}")]
|
|
||||||
BRACE_CLOSE,
|
|
||||||
#[token("[")]
|
|
||||||
BRACKET_OPEN,
|
|
||||||
#[token("]")]
|
|
||||||
BRACKET_CLOSE,
|
|
||||||
#[token(":")]
|
|
||||||
COLON,
|
|
||||||
#[token(",")]
|
|
||||||
COMMA,
|
|
||||||
#[token("null")]
|
|
||||||
NULL,
|
|
||||||
#[regex(r"-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?")]
|
|
||||||
NUMBER,
|
|
||||||
#[regex(r#""([^"\\]|\\["\\bnfrt]|u[a-fA-F0-9]{4})*""#)]
|
|
||||||
STRING,
|
|
||||||
|
|
||||||
// Whitespace tokens
|
|
||||||
#[regex("[ \\t\\f]+")]
|
|
||||||
WHITESPACE,
|
|
||||||
#[token("\n")]
|
|
||||||
NEWLINE,
|
|
||||||
|
|
||||||
// Error SyntaxKinds
|
|
||||||
LEX_ERR,
|
|
||||||
PARSE_ERR,
|
|
||||||
|
|
||||||
// Meta SyntaxKinds
|
|
||||||
ROOT,
|
|
||||||
EOF,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl pawarser::parser::SyntaxElement for SyntaxKind {
|
|
||||||
const SYNTAX_EOF: Self = Self::EOF;
|
|
||||||
|
|
||||||
const SYNTAX_ERROR: Self = Self::PARSE_ERR;
|
|
||||||
const SYNTAX_ROOT: Self = Self::ROOT;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SyntaxKind> for rowan::SyntaxKind {
|
|
||||||
fn from(kind: SyntaxKind) -> Self {
|
|
||||||
Self(kind as u16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<rowan::SyntaxKind> for SyntaxKind {
|
|
||||||
fn from(raw: rowan::SyntaxKind) -> Self {
|
|
||||||
assert!(raw.0 <= SyntaxKind::EOF as u16);
|
|
||||||
#[allow(unsafe_code, reason = "The transmute is necessary here")]
|
|
||||||
unsafe {
|
|
||||||
std::mem::transmute::<u16, SyntaxKind>(raw.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::syntax_kind::{lex, SyntaxKind};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_object() {
|
|
||||||
const TEST_DATA: &str = r#"{"hello_world": "meow", "some_num":7.42}"#;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
dbg!(lex(TEST_DATA)),
|
|
||||||
vec![
|
|
||||||
(SyntaxKind::BRACE_OPEN, "{"),
|
|
||||||
(SyntaxKind::STRING, "\"hello_world\""),
|
|
||||||
(SyntaxKind::COLON, ":"),
|
|
||||||
(SyntaxKind::WHITESPACE, " "),
|
|
||||||
(SyntaxKind::STRING, "\"meow\""),
|
|
||||||
(SyntaxKind::COMMA, ","),
|
|
||||||
(SyntaxKind::WHITESPACE, " "),
|
|
||||||
(SyntaxKind::STRING, "\"some_num\""),
|
|
||||||
(SyntaxKind::COLON, ":"),
|
|
||||||
(SyntaxKind::NUMBER, "7.42"),
|
|
||||||
(SyntaxKind::BRACE_CLOSE, "}")
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "lang"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
logos = "0.14"
|
|
||||||
petgraph = { workspace = true}
|
|
||||||
indexmap = "2.2.6"
|
|
||||||
clap = { version = "4", features = ["derive"] }
|
|
||||||
ariadne = "0.4.0"
|
|
||||||
ego-tree = "0.6.2"
|
|
||||||
rowan = "0.15.15"
|
|
||||||
drop_bomb = "0.1.5"
|
|
||||||
enumset = "1.1.3"
|
|
||||||
indoc = "2"
|
|
||||||
dashmap = "5.5.3"
|
|
||||||
crossbeam = "0.8.4"
|
|
||||||
owo-colors = {version = "4", features = ["supports-colors"]}
|
|
||||||
strip-ansi-escapes = "0.2.0"
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
|
@ -1,80 +0,0 @@
|
||||||
use crate::lst_parser::syntax_kind::SyntaxKind::*;
|
|
||||||
use crate::SyntaxNode;
|
|
||||||
use rowan::Language;
|
|
||||||
|
|
||||||
// Heavily modified version of https://github.com/rust-analyzer/rowan/blob/e2d2e93e16c5104b136d0bc738a0d48346922200/examples/s_expressions.rs#L250-L266
|
|
||||||
macro_rules! ast_nodes {
|
|
||||||
($($ast:ident, $kind:ident);+) => {
|
|
||||||
$(
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct $ast(SyntaxNode);
|
|
||||||
impl rowan::ast::AstNode for $ast {
|
|
||||||
type Language = crate::Lang;
|
|
||||||
|
|
||||||
fn can_cast(kind: <Self::Language as Language>::Kind) -> bool {
|
|
||||||
kind == $kind
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast(node: SyntaxNode) -> Option<Self> {
|
|
||||||
if node.kind() == $kind {
|
|
||||||
Some(Self(node))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn syntax(&self) -> &SyntaxNode {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ast_nodes!(
|
|
||||||
Def, DEF;
|
|
||||||
DefName, DEF_NAME;
|
|
||||||
DefBody, DEF_BODY;
|
|
||||||
|
|
||||||
Mod, MODULE;
|
|
||||||
ModName, MODULE_NAME;
|
|
||||||
ModBody, MODULE_BODY;
|
|
||||||
|
|
||||||
Use, USE;
|
|
||||||
UsePat, USE_PAT;
|
|
||||||
PatItem, PAT_ITEM;
|
|
||||||
PatGlob, PAT_GLOB;
|
|
||||||
PatGroup, PAT_GROUP;
|
|
||||||
|
|
||||||
Literal, LITERAL;
|
|
||||||
IntLit, INT_NUM;
|
|
||||||
FloatLit, FLOAT_NUM;
|
|
||||||
StringLit, STRING;
|
|
||||||
|
|
||||||
Matrix, MATRIX;
|
|
||||||
MatrixRow, MAT_ROW;
|
|
||||||
Vector, VEC;
|
|
||||||
List, LIST;
|
|
||||||
CollectionItem, COLLECTION_ITEM;
|
|
||||||
|
|
||||||
ParenthesizedExpr, PARENTHESIZED_EXPR;
|
|
||||||
Expression, EXPR;
|
|
||||||
|
|
||||||
Pipeline, PIPELINE;
|
|
||||||
|
|
||||||
Instruction, INSTR;
|
|
||||||
InstructionName, INSTR_NAME;
|
|
||||||
InstructionParams, INSTR_PARAMS;
|
|
||||||
|
|
||||||
AttributeSet, ATTR_SET;
|
|
||||||
Attribute, ATTR;
|
|
||||||
AttributeName, ATTR_NAME;
|
|
||||||
AttributeValue, ATTR_VALUE;
|
|
||||||
|
|
||||||
ParseError, PARSE_ERR;
|
|
||||||
LexError, LEX_ERR;
|
|
||||||
|
|
||||||
Root, ROOT;
|
|
||||||
Eof, EOF
|
|
||||||
);
|
|
|
@ -1,25 +0,0 @@
|
||||||
#![feature(type_alias_impl_trait, lint_reasons, box_into_inner)]
|
|
||||||
|
|
||||||
use crate::lst_parser::syntax_kind::SyntaxKind;
|
|
||||||
|
|
||||||
pub mod ast;
|
|
||||||
pub mod lst_parser;
|
|
||||||
pub mod world;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub enum Lang {}
|
|
||||||
impl rowan::Language for Lang {
|
|
||||||
type Kind = SyntaxKind;
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
|
|
||||||
assert!(raw.0 <= SyntaxKind::ROOT as u16);
|
|
||||||
unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
|
|
||||||
}
|
|
||||||
fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
|
|
||||||
kind.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type SyntaxNode = rowan::SyntaxNode<Lang>;
|
|
||||||
pub type SyntaxToken = rowan::SyntaxNode<Lang>;
|
|
||||||
pub type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
|
|
|
@ -1,169 +0,0 @@
|
||||||
use drop_bomb::DropBomb;
|
|
||||||
|
|
||||||
use self::{
|
|
||||||
error::SyntaxError,
|
|
||||||
events::{Event, NodeKind},
|
|
||||||
input::Input,
|
|
||||||
syntax_kind::SyntaxKind,
|
|
||||||
};
|
|
||||||
use std::cell::Cell;
|
|
||||||
|
|
||||||
pub mod syntax_kind;
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
pub mod error;
|
|
||||||
pub mod events;
|
|
||||||
pub mod grammar;
|
|
||||||
pub mod input;
|
|
||||||
pub mod output;
|
|
||||||
|
|
||||||
const PARSER_STEP_LIMIT: u32 = 4096;
|
|
||||||
|
|
||||||
pub struct Parser<'src, 'toks> {
|
|
||||||
input: Input<'src, 'toks>,
|
|
||||||
pos: usize,
|
|
||||||
events: Vec<Event>,
|
|
||||||
steps: Cell<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src, 'toks> Parser<'src, 'toks> {
|
|
||||||
pub fn new(input: Input<'src, 'toks>) -> Self {
|
|
||||||
Self {
|
|
||||||
input,
|
|
||||||
pos: 0,
|
|
||||||
events: Vec::new(),
|
|
||||||
steps: Cell::new(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(self) -> Vec<Event> {
|
|
||||||
self.events
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn nth(&self, n: usize) -> SyntaxKind {
|
|
||||||
self.step();
|
|
||||||
self.input.kind(self.pos + n)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eat_succeeding_ws(&mut self) {
|
|
||||||
self.push_ev(Event::Eat {
|
|
||||||
count: self.input.meaningless_tail_len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn current(&self) -> SyntaxKind {
|
|
||||||
self.step();
|
|
||||||
self.input.kind(self.pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn start(&mut self, name: &str) -> Marker {
|
|
||||||
let pos = self.events.len();
|
|
||||||
self.push_ev(Event::tombstone());
|
|
||||||
Marker::new(pos, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn at(&self, kind: SyntaxKind) -> bool {
|
|
||||||
self.nth_at(0, kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn eat(&mut self, kind: SyntaxKind) -> bool {
|
|
||||||
if !self.at(kind) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.do_bump();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn nth_at(&self, n: usize, kind: SyntaxKind) -> bool {
|
|
||||||
self.nth(n) == kind
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_bump(&mut self) {
|
|
||||||
self.push_ev(Event::Eat {
|
|
||||||
count: self.input.preceding_meaningless(self.pos),
|
|
||||||
});
|
|
||||||
self.pos += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_ev(&mut self, event: Event) {
|
|
||||||
self.events.push(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn step(&self) {
|
|
||||||
let steps = self.steps.get();
|
|
||||||
assert!(steps <= PARSER_STEP_LIMIT, "the parser seems stuck...");
|
|
||||||
self.steps.set(steps + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct Marker {
|
|
||||||
pos: usize,
|
|
||||||
bomb: DropBomb,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Marker {
|
|
||||||
pub(crate) fn new(pos: usize, name: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
pos,
|
|
||||||
bomb: DropBomb::new(format!("Marker {name} must be completed or abandoned")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close_node(mut self, p: &mut Parser, kind: NodeKind) -> CompletedMarker {
|
|
||||||
self.bomb.defuse();
|
|
||||||
match &mut p.events[self.pos] {
|
|
||||||
Event::Start { kind: slot, .. } => *slot = kind.clone(),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
p.push_ev(Event::Finish);
|
|
||||||
|
|
||||||
CompletedMarker {
|
|
||||||
pos: self.pos,
|
|
||||||
kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn complete(self, p: &mut Parser<'_, '_>, kind: SyntaxKind) -> CompletedMarker {
|
|
||||||
self.close_node(p, NodeKind::Syntax(kind))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn error(self, p: &mut Parser, kind: SyntaxError) -> CompletedMarker {
|
|
||||||
self.close_node(p, NodeKind::Error(kind))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn abandon(mut self, p: &mut Parser<'_, '_>) {
|
|
||||||
self.bomb.defuse();
|
|
||||||
if self.pos == p.events.len() - 1 {
|
|
||||||
match p.events.pop() {
|
|
||||||
Some(Event::Start {
|
|
||||||
kind: NodeKind::Syntax(SyntaxKind::TOMBSTONE),
|
|
||||||
forward_parent: None,
|
|
||||||
}) => (),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct CompletedMarker {
|
|
||||||
pos: usize,
|
|
||||||
kind: NodeKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompletedMarker {
|
|
||||||
pub(crate) fn precede(self, p: &mut Parser<'_, '_>, name: &str) -> Marker {
|
|
||||||
let new_pos = p.start(name);
|
|
||||||
|
|
||||||
match &mut p.events[self.pos] {
|
|
||||||
Event::Start { forward_parent, .. } => {
|
|
||||||
*forward_parent = Some(new_pos.pos - self.pos);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
new_pos
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
use crate::lst_parser::syntax_kind::SyntaxKind;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub enum SyntaxError {
|
|
||||||
Expected(Vec<SyntaxKind>),
|
|
||||||
PipelineNeedsSink,
|
|
||||||
// if there was two space seperated items in a list
|
|
||||||
SpaceSepInList,
|
|
||||||
SemicolonInList,
|
|
||||||
CommaInMatOrVec,
|
|
||||||
UnterminatedTopLevelItem,
|
|
||||||
UnclosedModuleBody,
|
|
||||||
UnfinishedPath,
|
|
||||||
PathSepContainsSemicolon,
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
use crate::lst_parser::syntax_kind::SyntaxKind;
|
|
||||||
|
|
||||||
use super::error::SyntaxError;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Event {
|
|
||||||
Start {
|
|
||||||
kind: NodeKind,
|
|
||||||
forward_parent: Option<usize>,
|
|
||||||
},
|
|
||||||
Finish,
|
|
||||||
Eat {
|
|
||||||
count: usize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum NodeKind {
|
|
||||||
Syntax(SyntaxKind),
|
|
||||||
Error(SyntaxError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NodeKind {
|
|
||||||
pub fn is_syntax(&self) -> bool {
|
|
||||||
matches!(self, Self::Syntax(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_error(&self) -> bool {
|
|
||||||
matches!(self, Self::Error(_))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SyntaxKind> for NodeKind {
|
|
||||||
fn from(value: SyntaxKind) -> Self {
|
|
||||||
NodeKind::Syntax(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SyntaxError> for NodeKind {
|
|
||||||
fn from(value: SyntaxError) -> Self {
|
|
||||||
NodeKind::Error(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<SyntaxKind> for NodeKind {
|
|
||||||
fn eq(&self, other: &SyntaxKind) -> bool {
|
|
||||||
match self {
|
|
||||||
NodeKind::Syntax(s) => s == other,
|
|
||||||
NodeKind::Error(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<SyntaxError> for NodeKind {
|
|
||||||
fn eq(&self, other: &SyntaxError) -> bool {
|
|
||||||
match self {
|
|
||||||
NodeKind::Syntax(_) => false,
|
|
||||||
NodeKind::Error(e) => e == other,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Event {
|
|
||||||
pub(crate) fn tombstone() -> Self {
|
|
||||||
Self::Start {
|
|
||||||
kind: SyntaxKind::TOMBSTONE.into(),
|
|
||||||
forward_parent: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use crate::lst_parser::syntax_kind::SyntaxKind::*;
|
|
||||||
|
|
||||||
use self::module::{mod_body, top_level_item};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
input::Input,
|
|
||||||
output::Output,
|
|
||||||
syntax_kind::{self, lex},
|
|
||||||
Parser,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod expression;
|
|
||||||
mod module;
|
|
||||||
|
|
||||||
pub fn source_file(p: &mut Parser) {
|
|
||||||
let root = p.start("root");
|
|
||||||
|
|
||||||
mod_body(p);
|
|
||||||
// expression::expression(p, false);
|
|
||||||
p.eat_succeeding_ws();
|
|
||||||
|
|
||||||
root.complete(p, ROOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_parser(input: &str, parser_fn: fn(&mut Parser), output: &str) {
|
|
||||||
let toks = lex(input);
|
|
||||||
let mut parser = Parser::new(Input::new(&toks));
|
|
||||||
|
|
||||||
parser_fn(&mut parser);
|
|
||||||
|
|
||||||
let p_out = dbg!(parser.finish());
|
|
||||||
let o = Output::from_parser_output(toks, p_out);
|
|
||||||
|
|
||||||
let s = strip_ansi_escapes::strip_str(format!("{o:?}"));
|
|
||||||
assert_eq!(&s, output);
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
use crate::lst_parser::{error::SyntaxError, syntax_kind::SyntaxKind::*, CompletedMarker, Parser};
|
|
||||||
|
|
||||||
use self::{collection::collection, instruction::instr, lit::literal, pipeline::PIPES};
|
|
||||||
|
|
||||||
mod collection;
|
|
||||||
mod instruction;
|
|
||||||
mod lit;
|
|
||||||
mod pipeline;
|
|
||||||
|
|
||||||
pub fn expression(p: &mut Parser, in_pipe: bool) -> Option<CompletedMarker> {
|
|
||||||
let expr = p.start("expr");
|
|
||||||
|
|
||||||
if atom(p).or_else(|| instr(p)).is_none() {
|
|
||||||
expr.abandon(p);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = expr.complete(p, EXPR);
|
|
||||||
|
|
||||||
if PIPES.contains(p.current()) && !in_pipe {
|
|
||||||
pipeline::pipeline(p, r)
|
|
||||||
} else {
|
|
||||||
Some(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn atom(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
literal(p)
|
|
||||||
.or_else(|| collection(p))
|
|
||||||
.or_else(|| parenthesized_expr(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parenthesized_expr(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
if p.eat(L_PAREN) {
|
|
||||||
let par_expr = p.start("parenthesized");
|
|
||||||
expression(p, false);
|
|
||||||
if !p.eat(R_PAREN) {
|
|
||||||
return Some(par_expr.error(p, SyntaxError::Expected(vec![R_PAREN])));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(par_expr.complete(p, PARENTHESIZED_EXPR));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
use enumset::enum_set;
|
|
||||||
|
|
||||||
use crate::lst_parser::{
|
|
||||||
syntax_kind::{SyntaxKind::*, TokenSet},
|
|
||||||
CompletedMarker, Parser,
|
|
||||||
};
|
|
||||||
|
|
||||||
use self::{attr_set::attr_set, vec::vec_matrix_list};
|
|
||||||
|
|
||||||
mod attr_set;
|
|
||||||
mod vec;
|
|
||||||
|
|
||||||
const COLLECTION_START: TokenSet = enum_set!(L_BRACK | L_BRACE);
|
|
||||||
|
|
||||||
pub fn collection(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
if !COLLECTION_START.contains(p.current()) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(match p.current() {
|
|
||||||
L_BRACK => vec_matrix_list(p),
|
|
||||||
L_BRACE => attr_set(p),
|
|
||||||
_ => unreachable!(),
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
use crate::lst_parser::{
|
|
||||||
error::SyntaxError,
|
|
||||||
grammar::expression::{atom, expression},
|
|
||||||
CompletedMarker, Marker, Parser,
|
|
||||||
SyntaxKind::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn attr_set(p: &mut Parser) -> CompletedMarker {
|
|
||||||
let start = p.start("attr_set_start");
|
|
||||||
assert!(p.eat(L_BRACE));
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if attr(p).is_some() {
|
|
||||||
// TODO: handle others
|
|
||||||
if p.eat(COMMA) {
|
|
||||||
continue;
|
|
||||||
} else if p.eat(R_BRACE) {
|
|
||||||
return start.complete(p, ATTR_SET);
|
|
||||||
}
|
|
||||||
// TODO: check for newline and stuff following that for recov of others
|
|
||||||
} else if p.eat(R_BRACE) {
|
|
||||||
return start.complete(p, ATTR_SET);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn attr(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
if p.at(IDENT) {
|
|
||||||
let attr_start = p.start("attr");
|
|
||||||
let attr_name_start = p.start("attr_name");
|
|
||||||
p.do_bump();
|
|
||||||
attr_name_start.complete(p, ATTR_NAME);
|
|
||||||
|
|
||||||
// TODO: handle comma, expr/atom, other
|
|
||||||
p.eat(COLON);
|
|
||||||
|
|
||||||
// TODO: handle failed expr parser too
|
|
||||||
let attr_value = p.start("attr_value");
|
|
||||||
let _ = expression(p, false);
|
|
||||||
attr_value.complete(p, ATTR_VALUE);
|
|
||||||
Some(attr_start.complete(p, ATTR))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
use crate::lst_parser::{
|
|
||||||
error::SyntaxError, grammar::expression::atom, CompletedMarker, Marker, Parser, SyntaxKind::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn vec_matrix_list(p: &mut Parser) -> CompletedMarker {
|
|
||||||
let start = p.start("vec_matrix_list_start");
|
|
||||||
assert!(p.eat(L_BRACK));
|
|
||||||
let row_start = p.start("matrix_row_start");
|
|
||||||
if let Some(item) = atom(p) {
|
|
||||||
item.precede(p, "coll_item_start")
|
|
||||||
.complete(p, COLLECTION_ITEM);
|
|
||||||
|
|
||||||
if p.at(COMMA) {
|
|
||||||
row_start.abandon(p);
|
|
||||||
return finish_list(p, start);
|
|
||||||
}
|
|
||||||
|
|
||||||
finish_mat_or_vec(p, start, row_start)
|
|
||||||
} else if p.eat(R_BRACK) {
|
|
||||||
row_start.abandon(p);
|
|
||||||
start.complete(p, LIST)
|
|
||||||
} else {
|
|
||||||
row_start.abandon(p);
|
|
||||||
start.error(p, SyntaxError::Expected(vec![EXPR, R_BRACK]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish_list(p: &mut Parser, list_start: Marker) -> CompletedMarker {
|
|
||||||
loop {
|
|
||||||
if p.eat(COMMA) {
|
|
||||||
if let Some(item) = atom(p) {
|
|
||||||
item.precede(p, "coll_item_start")
|
|
||||||
.complete(p, COLLECTION_ITEM);
|
|
||||||
} else if p.eat(R_BRACK) {
|
|
||||||
return list_start.complete(p, LIST);
|
|
||||||
}
|
|
||||||
} else if p.eat(R_BRACK) {
|
|
||||||
return list_start.complete(p, LIST);
|
|
||||||
} else if let Some(item) = atom(p) {
|
|
||||||
item.precede(p, "next_item")
|
|
||||||
.complete(p, COLLECTION_ITEM)
|
|
||||||
.precede(p, "err_space_sep")
|
|
||||||
.error(p, SyntaxError::SpaceSepInList);
|
|
||||||
} else if p.at(SEMICOLON) {
|
|
||||||
let semi_err = p.start("semicolon_err");
|
|
||||||
p.eat(SEMICOLON);
|
|
||||||
semi_err.error(p, SyntaxError::SemicolonInList);
|
|
||||||
if let Some(item) = atom(p) {
|
|
||||||
item.precede(p, "coll_item_start")
|
|
||||||
.complete(p, COLLECTION_ITEM);
|
|
||||||
} else if p.eat(R_BRACK) {
|
|
||||||
return list_start.complete(p, LIST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: handle commas, general other wrong toks
|
|
||||||
fn finish_mat_or_vec(p: &mut Parser, coll_start: Marker, mut row_start: Marker) -> CompletedMarker {
|
|
||||||
let mut is_matrix = false;
|
|
||||||
let mut row_item_count = 1;
|
|
||||||
loop {
|
|
||||||
if let Some(item) = atom(p) {
|
|
||||||
item.precede(p, "coll_item_start")
|
|
||||||
.complete(p, COLLECTION_ITEM);
|
|
||||||
row_item_count += 1;
|
|
||||||
} else if p.at(SEMICOLON) {
|
|
||||||
is_matrix = true;
|
|
||||||
row_start.complete(p, MAT_ROW);
|
|
||||||
p.eat(SEMICOLON);
|
|
||||||
row_start = p.start("matrix_row_start");
|
|
||||||
row_item_count = 0;
|
|
||||||
} else if p.at(R_BRACK) {
|
|
||||||
if is_matrix && row_item_count == 0 {
|
|
||||||
row_start.abandon(p);
|
|
||||||
p.eat(R_BRACK);
|
|
||||||
return coll_start.complete(p, MATRIX);
|
|
||||||
} else if is_matrix {
|
|
||||||
row_start.complete(p, MAT_ROW);
|
|
||||||
p.eat(R_BRACK);
|
|
||||||
return coll_start.complete(p, MATRIX);
|
|
||||||
} else {
|
|
||||||
row_start.abandon(p);
|
|
||||||
p.eat(R_BRACK);
|
|
||||||
return coll_start.complete(p, VEC);
|
|
||||||
}
|
|
||||||
} else if p.at(COMMA) {
|
|
||||||
let err_unexpected_comma = p.start("err_unexpected_comma");
|
|
||||||
p.do_bump();
|
|
||||||
err_unexpected_comma.error(p, SyntaxError::CommaInMatOrVec);
|
|
||||||
} else {
|
|
||||||
let err_unexpected = p.start("err_unexpected_tok");
|
|
||||||
p.do_bump();
|
|
||||||
err_unexpected.error(p, SyntaxError::Expected(vec![EXPR, SEMICOLON, R_BRACK]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
use crate::lst_parser::{syntax_kind::SyntaxKind::*, CompletedMarker, Parser};
|
|
||||||
|
|
||||||
use super::{atom, lit::literal};
|
|
||||||
|
|
||||||
pub fn instr(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
if !p.at(IDENT) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let instr = p.start("instr");
|
|
||||||
|
|
||||||
instr_name(p);
|
|
||||||
instr_params(p);
|
|
||||||
|
|
||||||
Some(instr.complete(p, INSTR))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instr_name(p: &mut Parser) {
|
|
||||||
let instr_name = p.start("instr_name");
|
|
||||||
|
|
||||||
while p.at(IDENT) {
|
|
||||||
p.do_bump();
|
|
||||||
}
|
|
||||||
|
|
||||||
instr_name.complete(p, INSTR_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instr_params(p: &mut Parser) {
|
|
||||||
if let Some(start) = atom(p) {
|
|
||||||
while atom(p).is_some() {}
|
|
||||||
|
|
||||||
start.precede(p, "params_start").complete(p, INSTR_PARAMS);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
use enumset::enum_set;
|
|
||||||
use indoc::indoc;
|
|
||||||
|
|
||||||
use crate::lst_parser::{
|
|
||||||
grammar::check_parser,
|
|
||||||
syntax_kind::{SyntaxKind::*, TokenSet},
|
|
||||||
CompletedMarker, Parser,
|
|
||||||
};
|
|
||||||
|
|
||||||
const LIT_TOKENS: TokenSet = enum_set!(INT_NUM | FLOAT_NUM | STRING);
|
|
||||||
|
|
||||||
pub fn literal(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
if !LIT_TOKENS.contains(p.current()) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lit = p.start("lit");
|
|
||||||
|
|
||||||
p.do_bump();
|
|
||||||
|
|
||||||
Some(lit.complete(p, LITERAL))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_lst_lit() {
|
|
||||||
check_parser(
|
|
||||||
"42",
|
|
||||||
|p| {
|
|
||||||
literal(p);
|
|
||||||
},
|
|
||||||
indoc! {r#"
|
|
||||||
LITERAL {
|
|
||||||
INT_NUM "42";
|
|
||||||
}
|
|
||||||
"#},
|
|
||||||
);
|
|
||||||
check_parser(
|
|
||||||
"3.14",
|
|
||||||
|p| {
|
|
||||||
literal(p);
|
|
||||||
},
|
|
||||||
indoc! {r#"
|
|
||||||
LITERAL {
|
|
||||||
FLOAT_NUM "3.14";
|
|
||||||
}
|
|
||||||
"#},
|
|
||||||
);
|
|
||||||
check_parser(
|
|
||||||
r#""Meow""#,
|
|
||||||
|p| {
|
|
||||||
literal(p);
|
|
||||||
},
|
|
||||||
indoc! {r#"
|
|
||||||
LITERAL {
|
|
||||||
STRING "\"Meow\"";
|
|
||||||
}
|
|
||||||
"#},
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
use enumset::enum_set;
|
|
||||||
|
|
||||||
use crate::lst_parser::{
|
|
||||||
error::SyntaxError,
|
|
||||||
syntax_kind::{SyntaxKind::*, TokenSet},
|
|
||||||
CompletedMarker, Parser,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::expression;
|
|
||||||
|
|
||||||
pub fn pipeline(p: &mut Parser, start_expr: CompletedMarker) -> Option<CompletedMarker> {
|
|
||||||
if !pipe(p) {
|
|
||||||
return Some(start_expr);
|
|
||||||
}
|
|
||||||
let pipeline_marker = start_expr.precede(p, "pipeline_start");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if expression(p, true).is_none() {
|
|
||||||
return Some(pipeline_marker.error(p, SyntaxError::PipelineNeedsSink));
|
|
||||||
}
|
|
||||||
if !pipe(p) {
|
|
||||||
return Some(pipeline_marker.complete(p, PIPELINE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const PIPES: TokenSet = enum_set!(PIPE | MAPPING_PIPE | NULL_PIPE);
|
|
||||||
|
|
||||||
fn pipe(p: &mut Parser) -> bool {
|
|
||||||
if PIPES.contains(p.current()) {
|
|
||||||
p.do_bump();
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,191 +0,0 @@
|
||||||
use enumset::enum_set;
|
|
||||||
|
|
||||||
use crate::lst_parser::{
|
|
||||||
error::SyntaxError,
|
|
||||||
grammar::expression::expression,
|
|
||||||
syntax_kind::{SyntaxKind::*, TokenSet},
|
|
||||||
CompletedMarker, Parser,
|
|
||||||
};
|
|
||||||
|
|
||||||
const TOP_LEVEL_ITEM_START: TokenSet = enum_set!(DEF_KW | MOD_KW | USE_KW);
|
|
||||||
|
|
||||||
pub fn mod_body(p: &mut Parser) {
|
|
||||||
loop {
|
|
||||||
if top_level_item(p).is_none() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mod_decl(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
let mod_start = p.start("module");
|
|
||||||
if !p.eat(MOD_KW) {
|
|
||||||
mod_start.abandon(p);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mod_name = p.start("module_name");
|
|
||||||
if p.eat(IDENT) {
|
|
||||||
mod_name.complete(p, MODULE_NAME);
|
|
||||||
} else {
|
|
||||||
mod_name.error(p, SyntaxError::Expected(vec![IDENT]));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mod_body_marker = p.start("mod_body");
|
|
||||||
if p.eat(SEMICOLON) {
|
|
||||||
mod_body_marker.abandon(p);
|
|
||||||
Some(mod_start.complete(p, MODULE))
|
|
||||||
} else if p.eat(L_BRACE) {
|
|
||||||
mod_body(p);
|
|
||||||
if !p.eat(R_BRACE) {
|
|
||||||
mod_body_marker
|
|
||||||
.complete(p, MODULE_BODY)
|
|
||||||
.precede(p, "unclosed_mod_body_err")
|
|
||||||
.error(p, SyntaxError::UnclosedModuleBody);
|
|
||||||
} else {
|
|
||||||
mod_body_marker.complete(p, MODULE_BODY);
|
|
||||||
}
|
|
||||||
Some(mod_start.complete(p, MODULE))
|
|
||||||
} else {
|
|
||||||
Some(mod_start.error(p, SyntaxError::Expected(vec![MODULE_BODY])))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn top_level_item(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
if !TOP_LEVEL_ITEM_START.contains(p.current()) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
def(p).or_else(|| mod_decl(p)).or_else(|| r#use(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn def(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
let def_start = p.start("top_level_def");
|
|
||||||
if !p.eat(DEF_KW) {
|
|
||||||
def_start.abandon(p);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let def_name = p.start("def_name");
|
|
||||||
if p.eat(IDENT) {
|
|
||||||
def_name.complete(p, DEF_NAME);
|
|
||||||
} else {
|
|
||||||
def_name.error(p, SyntaxError::Expected(vec![IDENT]));
|
|
||||||
}
|
|
||||||
|
|
||||||
let maybe_expected_eq = p.start("maybe_expect_eq");
|
|
||||||
if !p.eat(EQ) {
|
|
||||||
maybe_expected_eq.error(p, SyntaxError::Expected(vec![EQ]));
|
|
||||||
} else {
|
|
||||||
maybe_expected_eq.abandon(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = p.start("def_body");
|
|
||||||
if expression(p, false).is_some() {
|
|
||||||
body.complete(p, DEF_BODY);
|
|
||||||
} else {
|
|
||||||
body.error(p, SyntaxError::Expected(vec![DEF_BODY]));
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(if p.eat(SEMICOLON) {
|
|
||||||
def_start.complete(p, DEF)
|
|
||||||
} else if TOP_LEVEL_ITEM_START.contains(p.current()) || p.at(EOF) {
|
|
||||||
def_start
|
|
||||||
.complete(p, DEF)
|
|
||||||
.precede(p, "unterminated_tl_item")
|
|
||||||
.error(p, SyntaxError::UnterminatedTopLevelItem)
|
|
||||||
} else {
|
|
||||||
def_start
|
|
||||||
.complete(p, DEF)
|
|
||||||
.precede(p, "err_unexpected")
|
|
||||||
.error(p, SyntaxError::Expected(vec![SEMICOLON]))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn r#use(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
let use_start = p.start("use_start");
|
|
||||||
if !p.eat(USE_KW) {
|
|
||||||
use_start.abandon(p);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if use_pat(p).is_none() {
|
|
||||||
p.start("expected_use_pat")
|
|
||||||
.error(p, SyntaxError::Expected(vec![USE_PAT]));
|
|
||||||
}
|
|
||||||
|
|
||||||
let use_item = use_start.complete(p, USE);
|
|
||||||
Some(if p.eat(SEMICOLON) {
|
|
||||||
use_item
|
|
||||||
} else if TOP_LEVEL_ITEM_START.contains(p.current()) || p.at(EOF) {
|
|
||||||
use_item
|
|
||||||
.precede(p, "unterminated_tl_item")
|
|
||||||
.error(p, SyntaxError::UnterminatedTopLevelItem)
|
|
||||||
} else {
|
|
||||||
use_item
|
|
||||||
.precede(p, "err_unexpected")
|
|
||||||
.error(p, SyntaxError::Expected(vec![SEMICOLON]))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn use_pat(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
let use_pat_marker = p.start("use_pat");
|
|
||||||
if !p.eat(IDENT) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if p.eat(PATH_SEP) {
|
|
||||||
if pat_item(p).is_none() {
|
|
||||||
break Some(use_pat_marker.error(p, SyntaxError::UnfinishedPath));
|
|
||||||
}
|
|
||||||
} else if p.at(SEMICOLON) && p.nth_at(1, COLON) {
|
|
||||||
let broken_sep = p.start("broken_path_sep");
|
|
||||||
let wrong_semi = p.start("semi_typo");
|
|
||||||
p.eat(SEMICOLON);
|
|
||||||
wrong_semi.error(p, SyntaxError::PathSepContainsSemicolon);
|
|
||||||
p.eat(COLON);
|
|
||||||
broken_sep.complete(p, PATH_SEP);
|
|
||||||
if pat_item(p).is_none() {
|
|
||||||
break Some(use_pat_marker.error(p, SyntaxError::UnfinishedPath));
|
|
||||||
}
|
|
||||||
} else if p.at(COLON) && p.nth_at(1, SEMICOLON) {
|
|
||||||
let broken_sep = p.start("broken_path_sep");
|
|
||||||
p.eat(COLON);
|
|
||||||
let wrong_semi = p.start("semi_typo");
|
|
||||||
p.eat(SEMICOLON);
|
|
||||||
wrong_semi.error(p, SyntaxError::PathSepContainsSemicolon);
|
|
||||||
broken_sep.complete(p, PATH_SEP);
|
|
||||||
if pat_item(p).is_none() {
|
|
||||||
break Some(use_pat_marker.error(p, SyntaxError::UnfinishedPath));
|
|
||||||
}
|
|
||||||
} else if p.at(SEMICOLON) && p.nth_at(1, SEMICOLON) {
|
|
||||||
let broken_sep = p.start("broken_path_sep");
|
|
||||||
p.eat(SEMICOLON);
|
|
||||||
p.eat(SEMICOLON);
|
|
||||||
broken_sep
|
|
||||||
.complete(p, PATH_SEP)
|
|
||||||
.precede(p, "semi_typo_err")
|
|
||||||
.error(p, SyntaxError::PathSepContainsSemicolon);
|
|
||||||
if pat_item(p).is_none() {
|
|
||||||
break Some(use_pat_marker.error(p, SyntaxError::UnfinishedPath));
|
|
||||||
}
|
|
||||||
} else if p.eat(SEMICOLON) {
|
|
||||||
break Some(use_pat_marker.complete(p, USE_PAT));
|
|
||||||
} else {
|
|
||||||
break Some(use_pat_marker.error(p, SyntaxError::Expected(vec![PATH_SEP, SEMICOLON])));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pat_item(p: &mut Parser) -> Option<CompletedMarker> {
|
|
||||||
let item_start = p.start("pat_item_start");
|
|
||||||
if p.eat(IDENT) {
|
|
||||||
Some(item_start.complete(p, PAT_ITEM))
|
|
||||||
} else if p.eat(STAR) {
|
|
||||||
Some(item_start.complete(p, PAT_GLOB))
|
|
||||||
} else if p.eat(L_BRACE) {
|
|
||||||
todo!("write PAT_GROUPs")
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
use enumset::enum_set;
|
|
||||||
|
|
||||||
use crate::lst_parser::syntax_kind::SyntaxKind;
|
|
||||||
|
|
||||||
use super::syntax_kind::TokenSet;
|
|
||||||
|
|
||||||
pub struct Input<'src, 'toks> {
|
|
||||||
raw: &'toks Vec<(SyntaxKind, &'src str)>,
|
|
||||||
/// indices of the "meaningful" tokens (not whitespace etc)
|
|
||||||
/// includes newlines because those might indeed help with finding errors
|
|
||||||
meaningful: Vec<usize>,
|
|
||||||
/// indices of newlines for the purpose of easily querying them
|
|
||||||
/// can be helpful with missing commas etc
|
|
||||||
newlines: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const MEANINGLESS_TOKS: TokenSet = enum_set!(SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE);
|
|
||||||
|
|
||||||
impl<'src, 'toks> Input<'src, 'toks> {
|
|
||||||
pub fn new(raw_toks: &'toks Vec<(SyntaxKind, &'src str)>) -> Self {
|
|
||||||
let meaningful = raw_toks
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, tok)| {
|
|
||||||
if MEANINGLESS_TOKS.contains(tok.0) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(i)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let newlines = raw_toks
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, tok)| match tok.0 {
|
|
||||||
SyntaxKind::NEWLINE => Some(i),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
raw: raw_toks,
|
|
||||||
meaningful,
|
|
||||||
newlines,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used, reason = "meaningful indices cannot be invalid")]
|
|
||||||
pub(crate) fn kind(&self, idx: usize) -> SyntaxKind {
|
|
||||||
let Some(meaningful_idx) = self.meaningful.get(idx) else {
|
|
||||||
return SyntaxKind::EOF;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.raw.get(*meaningful_idx).unwrap().0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn preceding_meaningless(&self, idx: usize) -> usize {
|
|
||||||
assert!(self.meaningful.len() > idx);
|
|
||||||
|
|
||||||
if idx == 0 {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
self.meaningful[idx] - self.meaningful[idx - 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn meaningless_tail_len(&self) -> usize {
|
|
||||||
self.raw.len() - (self.meaningful.last().unwrap() + 1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,208 +0,0 @@
|
||||||
use clap::builder;
|
|
||||||
use owo_colors::{unset_override, OwoColorize};
|
|
||||||
use rowan::{GreenNode, GreenNodeBuilder, GreenNodeData, GreenTokenData, Language, NodeOrToken};
|
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
lst_parser::{input::MEANINGLESS_TOKS, syntax_kind::SyntaxKind},
|
|
||||||
Lang, SyntaxNode,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
error::SyntaxError,
|
|
||||||
events::{Event, NodeKind},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Output {
|
|
||||||
pub green_node: GreenNode,
|
|
||||||
pub errors: Vec<SyntaxError>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for Output {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let mut errs: Vec<&SyntaxError> = self.errors.iter().collect();
|
|
||||||
errs.reverse();
|
|
||||||
|
|
||||||
debug_print_green_node(NodeOrToken::Node(&self.green_node), f, 0, &mut errs, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const INDENT_STR: &str = " ";
|
|
||||||
/// colored argument currently broken
|
|
||||||
fn debug_print_green_node(
|
|
||||||
node: NodeOrToken<&GreenNodeData, &GreenTokenData>,
|
|
||||||
f: &mut dyn std::fmt::Write,
|
|
||||||
lvl: i32,
|
|
||||||
errs: &mut Vec<&SyntaxError>,
|
|
||||||
colored: bool,
|
|
||||||
) -> std::fmt::Result {
|
|
||||||
for _ in 0..lvl {
|
|
||||||
f.write_str(INDENT_STR)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = match node {
|
|
||||||
NodeOrToken::Node(n) => {
|
|
||||||
let kind = Lang::kind_from_raw(node.kind());
|
|
||||||
if kind != SyntaxKind::PARSE_ERR {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{:?} {}",
|
|
||||||
Lang::kind_from_raw(node.kind()).bright_yellow().bold(),
|
|
||||||
"{".yellow()
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
let err = errs
|
|
||||||
.pop()
|
|
||||||
.expect("all error syntax nodes should correspond to an error")
|
|
||||||
.bright_red();
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{:?}{} {err:?} {}",
|
|
||||||
kind.bright_red().bold(),
|
|
||||||
":".red(),
|
|
||||||
"{".bright_red().bold()
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
for c in n.children() {
|
|
||||||
debug_print_green_node(c, f, lvl + 1, errs, colored)?;
|
|
||||||
}
|
|
||||||
for _ in 0..lvl {
|
|
||||||
f.write_str(INDENT_STR)?;
|
|
||||||
}
|
|
||||||
if kind != SyntaxKind::PARSE_ERR {
|
|
||||||
write!(f, "{}", "}\n".yellow())
|
|
||||||
} else {
|
|
||||||
write!(f, "{}", "}\n".bright_red().bold())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NodeOrToken::Token(t) => {
|
|
||||||
let tok = Lang::kind_from_raw(t.kind());
|
|
||||||
if MEANINGLESS_TOKS.contains(tok) {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{:?} {:?}{}",
|
|
||||||
Lang::kind_from_raw(t.kind()).white(),
|
|
||||||
t.text().white(),
|
|
||||||
";".white()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{:?} {:?}{}",
|
|
||||||
Lang::kind_from_raw(t.kind()).bright_cyan().bold(),
|
|
||||||
t.text().green(),
|
|
||||||
";".yellow()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Output {
|
|
||||||
pub fn debug_colored(&self) -> String {
|
|
||||||
let mut out = String::new();
|
|
||||||
let mut errs: Vec<&SyntaxError> = self.errors.iter().collect();
|
|
||||||
errs.reverse();
|
|
||||||
|
|
||||||
let _ = debug_print_green_node(
|
|
||||||
NodeOrToken::Node(&self.green_node),
|
|
||||||
&mut out,
|
|
||||||
0,
|
|
||||||
&mut errs,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
out
|
|
||||||
}
|
|
||||||
pub fn from_parser_output(
|
|
||||||
mut raw_toks: Vec<(SyntaxKind, &str)>,
|
|
||||||
mut events: Vec<Event>,
|
|
||||||
) -> Self {
|
|
||||||
let mut builder = GreenNodeBuilder::new();
|
|
||||||
let mut fw_parents = Vec::new();
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
raw_toks.reverse();
|
|
||||||
|
|
||||||
for i in 0..events.len() {
|
|
||||||
match mem::replace(&mut events[i], Event::tombstone()) {
|
|
||||||
Event::Start {
|
|
||||||
kind,
|
|
||||||
forward_parent,
|
|
||||||
} => {
|
|
||||||
if kind == SyntaxKind::TOMBSTONE && forward_parent.is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
fw_parents.push(kind);
|
|
||||||
let mut idx = i;
|
|
||||||
let mut fp = forward_parent;
|
|
||||||
while let Some(fwd) = fp {
|
|
||||||
idx += fwd as usize;
|
|
||||||
fp = match mem::replace(&mut events[idx], Event::tombstone()) {
|
|
||||||
Event::Start {
|
|
||||||
kind,
|
|
||||||
forward_parent,
|
|
||||||
} => {
|
|
||||||
fw_parents.push(kind);
|
|
||||||
forward_parent
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove whitespace bc it's ugly
|
|
||||||
while let Some((SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE, _)) =
|
|
||||||
raw_toks.last()
|
|
||||||
{
|
|
||||||
match events.iter_mut().find(|ev| matches!(ev, Event::Eat { .. })) {
|
|
||||||
Some(Event::Eat { count }) => *count -= 1,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tok, text): (SyntaxKind, &str) = raw_toks.pop().unwrap();
|
|
||||||
builder.token(tok.into(), text);
|
|
||||||
}
|
|
||||||
|
|
||||||
for kind in fw_parents.drain(..).rev() {
|
|
||||||
match kind {
|
|
||||||
NodeKind::Syntax(kind) if kind != SyntaxKind::TOMBSTONE => {
|
|
||||||
builder.start_node(kind.into())
|
|
||||||
}
|
|
||||||
NodeKind::Error(err) => {
|
|
||||||
errors.push(err);
|
|
||||||
builder.start_node(SyntaxKind::PARSE_ERR.into())
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Finish => builder.finish_node(),
|
|
||||||
Event::Eat { count } => (0..count).for_each(|_| {
|
|
||||||
let (tok, text): (SyntaxKind, &str) = raw_toks.pop().unwrap();
|
|
||||||
builder.token(tok.into(), text);
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
green_node: builder.finish(),
|
|
||||||
errors,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn syntax(&self) -> SyntaxNode {
|
|
||||||
SyntaxNode::new_root(self.green_node.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn errors(&self) -> Vec<SyntaxError> {
|
|
||||||
self.errors.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dissolve(self) -> (GreenNode, Vec<SyntaxError>) {
|
|
||||||
let Self { green_node, errors } = self;
|
|
||||||
(green_node, errors)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
use enumset::EnumSet;
|
|
||||||
use logos::Logos;
|
|
||||||
|
|
||||||
pub fn lex(src: &str) -> Vec<(SyntaxKind, &str)> {
|
|
||||||
let mut lex = SyntaxKind::lexer(src);
|
|
||||||
let mut r = Vec::new();
|
|
||||||
|
|
||||||
while let Some(tok_res) = lex.next() {
|
|
||||||
r.push((tok_res.unwrap_or(SyntaxKind::LEX_ERR), lex.slice()))
|
|
||||||
}
|
|
||||||
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(enumset::EnumSetType, Logos, Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
|
|
||||||
#[repr(u16)]
|
|
||||||
#[enumset(no_super_impls)]
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
pub enum SyntaxKind {
|
|
||||||
#[token("def")]
|
|
||||||
DEF_KW = 0,
|
|
||||||
DEF,
|
|
||||||
DEF_NAME,
|
|
||||||
DEF_BODY,
|
|
||||||
#[token("let")]
|
|
||||||
LET_KW,
|
|
||||||
#[token("in")]
|
|
||||||
IN_KW,
|
|
||||||
LET_IN,
|
|
||||||
#[token("::")]
|
|
||||||
PATH_SEP,
|
|
||||||
#[token("mod")]
|
|
||||||
MOD_KW,
|
|
||||||
MODULE,
|
|
||||||
MODULE_NAME,
|
|
||||||
MODULE_BODY,
|
|
||||||
USE,
|
|
||||||
#[token("use")]
|
|
||||||
USE_KW,
|
|
||||||
USE_PAT,
|
|
||||||
PAT_ITEM,
|
|
||||||
PAT_GLOB,
|
|
||||||
PAT_GROUP,
|
|
||||||
#[regex("[\\d]+")]
|
|
||||||
INT_NUM,
|
|
||||||
#[regex("[+-]?([\\d]+\\.[\\d]*|[\\d]*\\.[\\d]+)")]
|
|
||||||
FLOAT_NUM,
|
|
||||||
#[regex(r#""([^"\\]|\\["\\bnfrt]|u[a-fA-F0-9]{4})*""#)]
|
|
||||||
STRING,
|
|
||||||
MATRIX,
|
|
||||||
MAT_ROW,
|
|
||||||
VEC,
|
|
||||||
LIST,
|
|
||||||
// either of a vec, a matrix or a list
|
|
||||||
COLLECTION_ITEM,
|
|
||||||
PARENTHESIZED_EXPR,
|
|
||||||
EXPR,
|
|
||||||
LITERAL,
|
|
||||||
#[token("(")]
|
|
||||||
L_PAREN,
|
|
||||||
#[token(")")]
|
|
||||||
R_PAREN,
|
|
||||||
#[token("{")]
|
|
||||||
L_BRACE,
|
|
||||||
#[token("}")]
|
|
||||||
R_BRACE,
|
|
||||||
#[token("[")]
|
|
||||||
L_BRACK,
|
|
||||||
#[token("]")]
|
|
||||||
R_BRACK,
|
|
||||||
#[token("<")]
|
|
||||||
L_ANGLE,
|
|
||||||
#[token(">")]
|
|
||||||
R_ANGLE,
|
|
||||||
#[token("+")]
|
|
||||||
PLUS,
|
|
||||||
#[token("-")]
|
|
||||||
MINUS,
|
|
||||||
#[token("*")]
|
|
||||||
STAR,
|
|
||||||
#[token("/")]
|
|
||||||
SLASH,
|
|
||||||
#[token("%")]
|
|
||||||
PERCENT,
|
|
||||||
#[token("^")]
|
|
||||||
CARET,
|
|
||||||
INSTR,
|
|
||||||
INSTR_NAME,
|
|
||||||
INSTR_PARAMS,
|
|
||||||
ATTR_SET,
|
|
||||||
ATTR,
|
|
||||||
ATTR_NAME,
|
|
||||||
ATTR_VALUE,
|
|
||||||
#[regex("[a-zA-Z_]+[a-zA-Z_\\-\\d]*")]
|
|
||||||
IDENT,
|
|
||||||
#[regex("\\$[a-zA-Z0-9_\\-]+")]
|
|
||||||
VAR,
|
|
||||||
#[regex("\\@[a-zA-Z0-9_\\-]+")]
|
|
||||||
INPUT_VAR,
|
|
||||||
#[token("$")]
|
|
||||||
DOLLAR,
|
|
||||||
#[token("@")]
|
|
||||||
AT,
|
|
||||||
#[token(",")]
|
|
||||||
COMMA,
|
|
||||||
#[token("|")]
|
|
||||||
PIPE,
|
|
||||||
#[token("@|")]
|
|
||||||
MAPPING_PIPE,
|
|
||||||
#[token("!|")]
|
|
||||||
NULL_PIPE,
|
|
||||||
PIPELINE,
|
|
||||||
#[token("=")]
|
|
||||||
EQ,
|
|
||||||
#[token(":")]
|
|
||||||
COLON,
|
|
||||||
#[token(";")]
|
|
||||||
SEMICOLON,
|
|
||||||
#[token(".")]
|
|
||||||
DOT,
|
|
||||||
#[token("!")]
|
|
||||||
BANG,
|
|
||||||
#[regex("[ \\t\\f]+")]
|
|
||||||
WHITESPACE,
|
|
||||||
#[token("\n")]
|
|
||||||
NEWLINE,
|
|
||||||
PARSE_ERR,
|
|
||||||
LEX_ERR,
|
|
||||||
ROOT,
|
|
||||||
EOF,
|
|
||||||
TOMBSTONE,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type TokenSet = EnumSet<SyntaxKind>;
|
|
||||||
|
|
||||||
impl From<SyntaxKind> for rowan::SyntaxKind {
|
|
||||||
fn from(kind: SyntaxKind) -> Self {
|
|
||||||
Self(kind as u16)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
use clap::Parser;
|
|
||||||
use std::{fs, path::PathBuf};
|
|
||||||
|
|
||||||
use lang::lst_parser::{self, grammar, input, output::Output, syntax_kind};
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
struct Args {
|
|
||||||
file: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
fn main() {
|
|
||||||
let args = Args::parse();
|
|
||||||
let n = args.file.clone();
|
|
||||||
let f = fs::read_to_string(n.clone()).expect("failed to read file");
|
|
||||||
|
|
||||||
let toks = dbg!(syntax_kind::lex(&f));
|
|
||||||
let input = input::Input::new(&toks);
|
|
||||||
let mut parser = lst_parser::Parser::new(input);
|
|
||||||
|
|
||||||
grammar::source_file(&mut parser);
|
|
||||||
|
|
||||||
let p_out = dbg!(parser.finish());
|
|
||||||
let o = Output::from_parser_output(toks, p_out);
|
|
||||||
|
|
||||||
println!("{}", o.debug_colored());
|
|
||||||
|
|
||||||
// World::new(n);
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use self::files::{Files, OpenFileError};
|
|
||||||
|
|
||||||
mod error;
|
|
||||||
mod files;
|
|
||||||
|
|
||||||
struct World;
|
|
||||||
|
|
||||||
impl World {
|
|
||||||
pub fn new(entry_point: &Path) -> Result<Self, WorldCreationError> {
|
|
||||||
let mut files = Files::default();
|
|
||||||
let (entry_point_id, errors) = files.add_file(entry_point)?;
|
|
||||||
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum WorldCreationError {
|
|
||||||
FailedToOpenEntryPoint(OpenFileError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<OpenFileError> for WorldCreationError {
|
|
||||||
fn from(value: OpenFileError) -> Self {
|
|
||||||
Self::FailedToOpenEntryPoint(value)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::{ast::ParseError, lst_parser::error::SyntaxError};
|
|
||||||
|
|
||||||
use super::files::{FileId, Loc, OpenFileError};
|
|
||||||
|
|
||||||
pub enum Error {
|
|
||||||
Syntax(Loc<ParseError>, SyntaxError),
|
|
||||||
OpenFileError(OpenFileError),
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
io,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
mod loc;
|
|
||||||
|
|
||||||
pub use loc::Loc;
|
|
||||||
use rowan::ast::AstNode;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
ast::ParseError,
|
|
||||||
lst_parser::{self, error::SyntaxError, input, output::Output},
|
|
||||||
world::{error::Error, files::source_file::SourceFile},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Files {
|
|
||||||
inner: Vec<source_file::SourceFile>,
|
|
||||||
path_to_id_map: HashMap<PathBuf, FileId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Files {
|
|
||||||
pub fn add_file(&mut self, path: &Path) -> Result<(FileId, Vec<Error>), OpenFileError> {
|
|
||||||
if !path.exists() {
|
|
||||||
return Err(OpenFileError::NotFound(path.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_id = FileId(self.inner.len());
|
|
||||||
let (source_file, errs) = match SourceFile::open(path) {
|
|
||||||
Ok((source_file, errs)) => {
|
|
||||||
let errs = errs
|
|
||||||
.into_iter()
|
|
||||||
.map(|(ptr, err)| Error::Syntax(Loc::from_ptr(ptr, file_id), err))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
(source_file, errs)
|
|
||||||
}
|
|
||||||
Err(e) => return Err(OpenFileError::IoError(path.to_path_buf(), e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.inner.push(source_file);
|
|
||||||
self.path_to_id_map.insert(path.to_path_buf(), file_id);
|
|
||||||
|
|
||||||
Ok((file_id, errs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum OpenFileError {
|
|
||||||
NotFound(PathBuf),
|
|
||||||
IoError(PathBuf, std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct FileId(usize);
|
|
||||||
|
|
||||||
mod source_file;
|
|
|
@ -1,29 +0,0 @@
|
||||||
use rowan::ast::{AstNode, AstPtr};
|
|
||||||
|
|
||||||
use crate::Lang;
|
|
||||||
|
|
||||||
use super::FileId;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Loc<N: AstNode<Language = Lang>> {
|
|
||||||
file: FileId,
|
|
||||||
syntax: AstPtr<N>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: AstNode<Language = Lang>> Loc<N> {
|
|
||||||
pub fn new(node: N, file: FileId) -> Self {
|
|
||||||
Self::from_ptr(AstPtr::new(&node), file)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_ptr(ptr: AstPtr<N>, file: FileId) -> Self {
|
|
||||||
Self { file, syntax: ptr }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file(&self) -> FileId {
|
|
||||||
self.file
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn syntax(&self) -> AstPtr<N> {
|
|
||||||
self.syntax.clone()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
use crate::lst_parser::{self, grammar, input, syntax_kind};
|
|
||||||
use crate::SyntaxNode;
|
|
||||||
|
|
||||||
use crate::lst_parser::output::Output;
|
|
||||||
|
|
||||||
use crate::lst_parser::error::SyntaxError;
|
|
||||||
|
|
||||||
use crate::ast::ParseError;
|
|
||||||
|
|
||||||
use rowan::ast::{AstNode, AstPtr};
|
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
use std::{fs, io};
|
|
||||||
|
|
||||||
use rowan::GreenNode;
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
pub(crate) struct SourceFile {
|
|
||||||
pub(crate) path: PathBuf,
|
|
||||||
pub(crate) lst: rowan::GreenNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SourceFile {
|
|
||||||
pub(crate) fn open(p: &Path) -> io::Result<(Self, Vec<(AstPtr<ParseError>, SyntaxError)>)> {
|
|
||||||
assert!(p.exists());
|
|
||||||
|
|
||||||
let f = fs::read_to_string(p)?;
|
|
||||||
let (lst, errs) = Self::parse(f);
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
Self {
|
|
||||||
path: p.to_path_buf(),
|
|
||||||
lst,
|
|
||||||
},
|
|
||||||
errs,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse(f: String) -> (GreenNode, Vec<(AstPtr<ParseError>, SyntaxError)>) {
|
|
||||||
let toks = syntax_kind::lex(&f);
|
|
||||||
let input = input::Input::new(&toks);
|
|
||||||
let mut parser = lst_parser::Parser::new(input);
|
|
||||||
|
|
||||||
grammar::source_file(&mut parser);
|
|
||||||
|
|
||||||
let p_out = parser.finish();
|
|
||||||
let (lst, errs) = Output::from_parser_output(toks, p_out).dissolve();
|
|
||||||
|
|
||||||
(lst.clone(), Self::find_errs(lst, errs))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn find_errs(
|
|
||||||
lst: GreenNode,
|
|
||||||
mut errs: Vec<SyntaxError>,
|
|
||||||
) -> Vec<(AstPtr<ParseError>, SyntaxError)> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
errs.reverse();
|
|
||||||
|
|
||||||
let lst = SyntaxNode::new_root(lst);
|
|
||||||
Self::find_errs_recursive(&mut out, lst, &mut errs);
|
|
||||||
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn find_errs_recursive(
|
|
||||||
mut out: &mut Vec<(AstPtr<ParseError>, SyntaxError)>,
|
|
||||||
lst: SyntaxNode,
|
|
||||||
mut errs: &mut Vec<SyntaxError>,
|
|
||||||
) {
|
|
||||||
lst.children()
|
|
||||||
.filter_map(|c| ParseError::cast(c))
|
|
||||||
.for_each(|e| out.push((AstPtr::new(&e), errs.pop().unwrap())));
|
|
||||||
|
|
||||||
lst.children()
|
|
||||||
.for_each(|c| Self::find_errs_recursive(out, c, errs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::world::files::source_file::SourceFile;
|
|
||||||
|
|
||||||
fn check_find_errs(input: &str, expected: &[&str]) {
|
|
||||||
let (_, errs) = SourceFile::parse(input.to_string());
|
|
||||||
|
|
||||||
let errs = errs
|
|
||||||
.into_iter()
|
|
||||||
.map(|(loc, err)| format!("{:?}@{:?}", err, loc.syntax_node_ptr().text_range()))
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
errs,
|
|
||||||
expected
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_find_errs() {
|
|
||||||
check_find_errs(
|
|
||||||
"def meow = ;\n mod ;",
|
|
||||||
&["Expected([DEF_BODY])@11..11", "Expected([IDENT])@18..18"],
|
|
||||||
);
|
|
||||||
|
|
||||||
check_find_errs(
|
|
||||||
"def awawa = a |",
|
|
||||||
&["UnterminatedTopLevelItem@0..15", "PipelineNeedsSink@12..15"],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "pawarser"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
rowan = "0.15.15"
|
|
||||||
drop_bomb = "0.1.5"
|
|
||||||
enumset = "1.1.3"
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
|
@ -1,8 +0,0 @@
|
||||||
#![feature(iter_collect_into)]
|
|
||||||
pub mod parser;
|
|
||||||
|
|
||||||
pub use parser::{
|
|
||||||
error::SyntaxError,
|
|
||||||
marker::{CompletedMarker, Marker},
|
|
||||||
Parser, SyntaxElement,
|
|
||||||
};
|
|
|
@ -1,253 +0,0 @@
|
||||||
use std::{cell::Cell, fmt, marker::PhantomData, mem};
|
|
||||||
|
|
||||||
use enumset::{EnumSet, EnumSetType};
|
|
||||||
use rowan::{GreenNode, GreenNodeBuilder};
|
|
||||||
|
|
||||||
use crate::parser::event::NodeKind;
|
|
||||||
|
|
||||||
use self::{event::Event, input::Input, marker::Marker};
|
|
||||||
pub use {error::SyntaxError, output::ParserOutput};
|
|
||||||
|
|
||||||
pub mod error;
|
|
||||||
mod event;
|
|
||||||
mod input;
|
|
||||||
pub mod marker;
|
|
||||||
pub mod output;
|
|
||||||
|
|
||||||
/// this is used to define some required SyntaxKinds like an EOF token or an error token
|
|
||||||
pub trait SyntaxElement
|
|
||||||
where
|
|
||||||
Self: EnumSetType
|
|
||||||
+ Into<rowan::SyntaxKind>
|
|
||||||
+ From<rowan::SyntaxKind>
|
|
||||||
+ fmt::Debug
|
|
||||||
+ Clone
|
|
||||||
+ PartialEq
|
|
||||||
+ Eq,
|
|
||||||
{
|
|
||||||
/// EOF value. This will be used by the rest of the parser library to represent an EOF.
|
|
||||||
const SYNTAX_EOF: Self;
|
|
||||||
/// Error value. This will be used as a placeholder for associated respective errors.
|
|
||||||
const SYNTAX_ERROR: Self;
|
|
||||||
const SYNTAX_ROOT: Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Parser<'src, SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError> {
|
|
||||||
input: Input<'src, SyntaxKind>,
|
|
||||||
pos: usize,
|
|
||||||
events: Vec<Event<SyntaxKind, SyntaxErr>>,
|
|
||||||
step_limit: u32,
|
|
||||||
steps: Cell<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src, 'toks, SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError>
|
|
||||||
Parser<'src, SyntaxKind, SyntaxErr>
|
|
||||||
{
|
|
||||||
/// eat all meaningless tokens at the end of the file.
|
|
||||||
pub fn eat_succeeding_meaningless(&mut self) {
|
|
||||||
self.push_ev(Event::Eat {
|
|
||||||
count: self.input.meaningless_tail_len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get token from current position of the parser.
|
|
||||||
pub fn current(&self) -> SyntaxKind {
|
|
||||||
self.step();
|
|
||||||
self.input.kind(self.pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(&mut self, name: &str) -> Marker {
|
|
||||||
let pos = self.events.len();
|
|
||||||
self.push_ev(Event::tombstone());
|
|
||||||
Marker::new(pos, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Eat next token if it's of kind `kind` and return `true`.
|
|
||||||
/// Otherwise, `false`.
|
|
||||||
pub fn eat(&mut self, kind: SyntaxKind) -> bool {
|
|
||||||
if !self.at(kind) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.do_bump();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn do_bump(&mut self) {
|
|
||||||
self.push_ev(Event::Eat {
|
|
||||||
count: self.input.preceding_meaningless(self.pos),
|
|
||||||
});
|
|
||||||
self.pos += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the token at the current parser position is of `kind`
|
|
||||||
pub fn at(&self, kind: SyntaxKind) -> bool {
|
|
||||||
self.nth_at(0, kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the token that is `n` ahead is of `kind`
|
|
||||||
pub fn nth_at(&self, n: usize, kind: SyntaxKind) -> bool {
|
|
||||||
self.nth(n) == kind
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn nth(&self, n: usize) -> SyntaxKind {
|
|
||||||
self.step();
|
|
||||||
self.input.kind(self.pos + n)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_ev(&mut self, event: Event<SyntaxKind, SyntaxErr>) {
|
|
||||||
self.events.push(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn step(&self) {
|
|
||||||
let steps = self.steps.get();
|
|
||||||
assert!(steps <= self.step_limit, "the parser seems stuck.");
|
|
||||||
self.steps.set(steps + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(self) -> ParserOutput<SyntaxKind, SyntaxErr> {
|
|
||||||
let Self {
|
|
||||||
input,
|
|
||||||
pos,
|
|
||||||
mut events,
|
|
||||||
step_limit,
|
|
||||||
steps,
|
|
||||||
} = self;
|
|
||||||
let (mut raw_toks, meaningless_tokens) = input.dissolve();
|
|
||||||
let mut builder = GreenNodeBuilder::new();
|
|
||||||
// TODO: document what the hell a forward parent is
|
|
||||||
let mut fw_parents = Vec::new();
|
|
||||||
let mut errors: Vec<SyntaxErr> = Vec::new();
|
|
||||||
raw_toks.reverse();
|
|
||||||
|
|
||||||
// always have an implicit root node to avoid [`GreenNodeBuilder::finish()`] panicking due to multiple root elements.
|
|
||||||
builder.start_node(SyntaxKind::SYNTAX_ROOT.into());
|
|
||||||
|
|
||||||
for i in 0..events.len() {
|
|
||||||
match mem::replace(&mut events[i], Event::tombstone()) {
|
|
||||||
Event::Start {
|
|
||||||
kind,
|
|
||||||
forward_parent,
|
|
||||||
} => {
|
|
||||||
if kind == NodeKind::Tombstone && forward_parent.is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolving forward parents
|
|
||||||
// temporarily jump around with the parser index and replace them with tombstones
|
|
||||||
fw_parents.push(kind);
|
|
||||||
let mut idx = i;
|
|
||||||
let mut fp = forward_parent;
|
|
||||||
while let Some(fwd) = fp {
|
|
||||||
idx += fwd as usize;
|
|
||||||
fp = match mem::replace(&mut events[idx], Event::tombstone()) {
|
|
||||||
Event::Start {
|
|
||||||
kind,
|
|
||||||
forward_parent,
|
|
||||||
} => {
|
|
||||||
fw_parents.push(kind);
|
|
||||||
forward_parent
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear semantically meaningless tokens before the new tree node for aesthetic reasons
|
|
||||||
while raw_toks
|
|
||||||
.last()
|
|
||||||
.is_some_and(|v| meaningless_tokens.contains(v.0))
|
|
||||||
{
|
|
||||||
// update first next Eat event
|
|
||||||
match events.iter_mut().find(|ev| matches!(ev, Event::Eat { .. })) {
|
|
||||||
Some(Event::Eat { count }) => *count -= 1,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// put whitespace into lst
|
|
||||||
let (tok, text) = raw_toks.pop().unwrap();
|
|
||||||
builder.token(tok.into(), text);
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert forward parents into the tree in correct order
|
|
||||||
for kind in fw_parents.drain(..).rev() {
|
|
||||||
match kind {
|
|
||||||
NodeKind::Syntax(kind) => builder.start_node(kind.into()),
|
|
||||||
NodeKind::Error(err) => {
|
|
||||||
errors.push(err);
|
|
||||||
builder.start_node(SyntaxKind::SYNTAX_ERROR.into())
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Finish => builder.finish_node(),
|
|
||||||
Event::Eat { count } => (0..count).for_each(|_| {
|
|
||||||
let (tok, text) = raw_toks.pop().unwrap();
|
|
||||||
builder.token(tok.into(), text);
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// finish SYNTAX_ROOT
|
|
||||||
builder.finish_node();
|
|
||||||
|
|
||||||
ParserOutput {
|
|
||||||
green_node: builder.finish(),
|
|
||||||
errors,
|
|
||||||
_syntax_kind: PhantomData::<SyntaxKind>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ParserBuilder<
|
|
||||||
'src,
|
|
||||||
SyntaxKind: SyntaxElement,
|
|
||||||
// SyntaxErr: SyntaxError,
|
|
||||||
> {
|
|
||||||
raw_toks: Vec<(SyntaxKind, &'src str)>,
|
|
||||||
meaningless_token_kinds: EnumSet<SyntaxKind>,
|
|
||||||
step_limit: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src, SyntaxKind: SyntaxElement> ParserBuilder<'src, SyntaxKind> {
|
|
||||||
pub fn new(raw_toks: Vec<(SyntaxKind, &'src str)>) -> Self {
|
|
||||||
Self {
|
|
||||||
raw_toks,
|
|
||||||
meaningless_token_kinds: EnumSet::new(),
|
|
||||||
step_limit: 4096,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the parser step limit.
|
|
||||||
/// Defaults to 4096
|
|
||||||
pub fn step_limit(mut self, new: u32) -> Self {
|
|
||||||
self.step_limit = new;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_meaningless(mut self, kind: SyntaxKind) -> Self {
|
|
||||||
self.meaningless_token_kinds.insert(kind);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_meaningless_many(mut self, kind: Vec<SyntaxKind>) -> Self {
|
|
||||||
self.meaningless_token_kinds
|
|
||||||
.insert_all(kind.into_iter().collect());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build<SyntaxErr: SyntaxError>(self) -> Parser<'src, SyntaxKind, SyntaxErr> {
|
|
||||||
let Self {
|
|
||||||
raw_toks,
|
|
||||||
meaningless_token_kinds,
|
|
||||||
step_limit,
|
|
||||||
} = self;
|
|
||||||
Parser {
|
|
||||||
input: Input::new(raw_toks, Some(meaningless_token_kinds)),
|
|
||||||
pos: 0,
|
|
||||||
events: Vec::new(),
|
|
||||||
step_limit,
|
|
||||||
steps: Cell::new(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
/// A marker trait... for now!
|
|
||||||
// TODO: constrain that conversion to `NodeKind::Error` is enforced to be possible
|
|
||||||
pub trait SyntaxError
|
|
||||||
where
|
|
||||||
Self: fmt::Debug + Clone + PartialEq + Eq,
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
use enumset::EnumSetType;
|
|
||||||
|
|
||||||
use super::{error::SyntaxError, SyntaxElement};
|
|
||||||
|
|
||||||
pub enum Event<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError> {
|
|
||||||
Start {
|
|
||||||
kind: NodeKind<SyntaxKind, SyntaxErr>,
|
|
||||||
forward_parent: Option<usize>,
|
|
||||||
},
|
|
||||||
Finish,
|
|
||||||
Eat {
|
|
||||||
count: usize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError> Event<SyntaxKind, SyntaxErr> {
|
|
||||||
pub fn tombstone() -> Self {
|
|
||||||
Self::Start {
|
|
||||||
kind: NodeKind::Tombstone,
|
|
||||||
forward_parent: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
|
||||||
pub enum NodeKind<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError> {
|
|
||||||
Tombstone,
|
|
||||||
Syntax(SyntaxKind),
|
|
||||||
Error(SyntaxErr),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError> NodeKind<SyntaxKind, SyntaxErr> {
|
|
||||||
pub fn is_tombstone(&self) -> bool {
|
|
||||||
matches!(self, Self::Tombstone)
|
|
||||||
}
|
|
||||||
pub fn is_syntax(&self) -> bool {
|
|
||||||
matches!(self, Self::Syntax(_))
|
|
||||||
}
|
|
||||||
pub fn is_error(&self) -> bool {
|
|
||||||
matches!(self, Self::Error(_))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
use enumset::{EnumSet, EnumSetType};
|
|
||||||
|
|
||||||
use super::SyntaxElement;
|
|
||||||
|
|
||||||
pub struct Input<'src, SyntaxKind: SyntaxElement> {
|
|
||||||
raw: Vec<(SyntaxKind, &'src str)>,
|
|
||||||
// enumset of meaningless tokens
|
|
||||||
semantically_meaningless: EnumSet<SyntaxKind>,
|
|
||||||
// indices of non-meaningless tokens
|
|
||||||
meaningful_toks: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src, SyntaxKind: SyntaxElement> Input<'src, SyntaxKind> {
|
|
||||||
pub fn new(
|
|
||||||
raw_toks: Vec<(SyntaxKind, &'src str)>,
|
|
||||||
meaningless: Option<EnumSet<SyntaxKind>>,
|
|
||||||
) -> Self {
|
|
||||||
let mut meaningful_toks = Vec::new();
|
|
||||||
|
|
||||||
if let Some(meaningless) = meaningless {
|
|
||||||
let meaningful_toks = raw_toks
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, tok)| (!meaningless.contains(tok.0)).then_some(i))
|
|
||||||
.collect_into(&mut meaningful_toks);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
raw: raw_toks,
|
|
||||||
semantically_meaningless: meaningless.unwrap_or_default(),
|
|
||||||
meaningful_toks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn kind(&self, idx: usize) -> SyntaxKind {
|
|
||||||
let Some(meaningful_idx) = self.meaningful_toks.get(idx) else {
|
|
||||||
return SyntaxKind::SYNTAX_EOF;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.raw.get(*meaningful_idx).unwrap().0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn preceding_meaningless(&self, idx: usize) -> usize {
|
|
||||||
assert!(self.meaningful_toks.len() > idx);
|
|
||||||
|
|
||||||
if idx == 0 {
|
|
||||||
// maybe should be `self.meaningful_toks[idx]` instead??
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
self.meaningful_toks[idx] - self.meaningful_toks[idx - 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get the count of meaningless tokens at the end of the file.
|
|
||||||
pub fn meaningless_tail_len(&self) -> usize {
|
|
||||||
self.raw.len() - (self.meaningful_toks.last().unwrap() + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dissolve(self) -> (Vec<(SyntaxKind, &'src str)>, EnumSet<SyntaxKind>) {
|
|
||||||
let Self {
|
|
||||||
raw,
|
|
||||||
semantically_meaningless,
|
|
||||||
..
|
|
||||||
} = self;
|
|
||||||
(raw, semantically_meaningless)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
use drop_bomb::DropBomb;
|
|
||||||
use rowan::SyntaxKind;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
error::SyntaxError,
|
|
||||||
event::{Event, NodeKind},
|
|
||||||
Parser, SyntaxElement,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Marker {
|
|
||||||
pos: usize,
|
|
||||||
bomb: DropBomb,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Marker {
|
|
||||||
pub(super) fn new(pos: usize, name: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
pos,
|
|
||||||
bomb: DropBomb::new(format!("Marker {name} must be completed or abandoned.")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close_node<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError>(
|
|
||||||
mut self,
|
|
||||||
p: &mut Parser<SyntaxKind, SyntaxErr>,
|
|
||||||
kind: NodeKind<SyntaxKind, SyntaxErr>,
|
|
||||||
) -> CompletedMarker<SyntaxKind, SyntaxErr> {
|
|
||||||
self.bomb.defuse();
|
|
||||||
|
|
||||||
match &mut p.events[self.pos] {
|
|
||||||
Event::Start { kind: slot, .. } => *slot = kind.clone(),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
p.push_ev(Event::Finish);
|
|
||||||
CompletedMarker {
|
|
||||||
pos: self.pos,
|
|
||||||
kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn complete<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError>(
|
|
||||||
self,
|
|
||||||
p: &mut Parser<SyntaxKind, SyntaxErr>,
|
|
||||||
kind: SyntaxKind,
|
|
||||||
) -> CompletedMarker<SyntaxKind, SyntaxErr> {
|
|
||||||
self.close_node(p, NodeKind::Syntax(kind))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError>(
|
|
||||||
self,
|
|
||||||
p: &mut Parser<SyntaxKind, SyntaxErr>,
|
|
||||||
kind: SyntaxErr,
|
|
||||||
) -> CompletedMarker<SyntaxKind, SyntaxErr> {
|
|
||||||
self.close_node(p, NodeKind::Error(kind))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn abandon<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError>(
|
|
||||||
mut self,
|
|
||||||
p: &mut Parser<SyntaxKind, SyntaxErr>,
|
|
||||||
) {
|
|
||||||
self.bomb.defuse();
|
|
||||||
|
|
||||||
// clean up empty tombstone event from marker
|
|
||||||
if self.pos == p.events.len() - 1 {
|
|
||||||
match p.events.pop() {
|
|
||||||
Some(Event::Start {
|
|
||||||
kind: NodeKind::Tombstone,
|
|
||||||
forward_parent: None,
|
|
||||||
}) => (),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CompletedMarker<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError> {
|
|
||||||
pos: usize,
|
|
||||||
kind: NodeKind<SyntaxKind, SyntaxErr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError> CompletedMarker<SyntaxKind, SyntaxErr> {
|
|
||||||
pub fn precede(self, p: &mut Parser<SyntaxKind, SyntaxErr>, name: &str) -> Marker {
|
|
||||||
let new_pos = p.start(name);
|
|
||||||
|
|
||||||
match &mut p.events[self.pos] {
|
|
||||||
Event::Start { forward_parent, .. } => {
|
|
||||||
// point forward parent of the node this marker completed to the new node
|
|
||||||
// will later be used to make the new node a parent of the current node.
|
|
||||||
*forward_parent = Some(new_pos.pos - self.pos)
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
new_pos
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
use std::{fmt, marker::PhantomData};
|
|
||||||
|
|
||||||
use rowan::{GreenNode, GreenNodeData, GreenTokenData, NodeOrToken};
|
|
||||||
|
|
||||||
use crate::{SyntaxElement, SyntaxError};
|
|
||||||
|
|
||||||
pub struct ParserOutput<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError> {
|
|
||||||
pub green_node: GreenNode,
|
|
||||||
pub errors: Vec<SyntaxErr>,
|
|
||||||
pub(super) _syntax_kind: PhantomData<SyntaxKind>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError> std::fmt::Debug
|
|
||||||
for ParserOutput<SyntaxKind, SyntaxErr>
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let mut errs: Vec<&SyntaxErr> = self.errors.iter().collect();
|
|
||||||
errs.reverse();
|
|
||||||
debug_print_output::<SyntaxKind, SyntaxErr>(
|
|
||||||
NodeOrToken::Node(&self.green_node),
|
|
||||||
f,
|
|
||||||
0,
|
|
||||||
&mut errs,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug_print_output<SyntaxKind: SyntaxElement, SyntaxErr: SyntaxError>(
|
|
||||||
node: NodeOrToken<&GreenNodeData, &GreenTokenData>,
|
|
||||||
f: &mut std::fmt::Formatter<'_>,
|
|
||||||
lvl: i32,
|
|
||||||
errs: &mut Vec<&SyntaxErr>,
|
|
||||||
) -> std::fmt::Result {
|
|
||||||
if f.alternate() {
|
|
||||||
for _ in 0..lvl {
|
|
||||||
f.write_str(" ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let maybe_newline = if f.alternate() { "\n" } else { " " };
|
|
||||||
|
|
||||||
match node {
|
|
||||||
NodeOrToken::Node(n) => {
|
|
||||||
let kind: SyntaxKind = node.kind().into();
|
|
||||||
if kind != SyntaxKind::SYNTAX_ERROR {
|
|
||||||
write!(f, "{:?} {{{maybe_newline}", kind)?;
|
|
||||||
} else {
|
|
||||||
let err = errs
|
|
||||||
.pop()
|
|
||||||
.expect("all error syntax nodes should correspond to an error");
|
|
||||||
|
|
||||||
write!(f, "{:?}: {err:?} {{{maybe_newline}", kind)?;
|
|
||||||
}
|
|
||||||
for c in n.children() {
|
|
||||||
debug_print_output::<SyntaxKind, SyntaxErr>(c, f, lvl + 1, errs)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.alternate() {
|
|
||||||
for _ in 0..lvl {
|
|
||||||
f.write_str(" ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write!(f, "}}{maybe_newline}")
|
|
||||||
}
|
|
||||||
NodeOrToken::Token(t) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{:?} {:?};{maybe_newline}",
|
|
||||||
Into::<SyntaxKind>::into(t.kind()),
|
|
||||||
t.text()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ir"
|
name = "pl-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
either = "1.9"
|
clap = { workspace = true, features = [ "derive" ] }
|
||||||
ron = "0.8"
|
rpl = { path = "../rpl" }
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
executor = { path = "../executor" }
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
19
crates/pl-cli/src/main.rs
Normal file
19
crates/pl-cli/src/main.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use executor::{execute_all, Executors};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct Args {
|
||||||
|
file: PathBuf,
|
||||||
|
primary_executor: Executors,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = dbg!(Args::parse());
|
||||||
|
|
||||||
|
let f = fs::read_to_string(args.file).unwrap();
|
||||||
|
let pl = rpl::from_ron(&f);
|
||||||
|
|
||||||
|
execute_all(pl.0);
|
||||||
|
}
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod enum_based;
|
|
||||||
pub mod trait_based;
|
|
|
@ -1,64 +0,0 @@
|
||||||
pub enum Instruction {
|
|
||||||
Uppercase,
|
|
||||||
Lowercase,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Pipeline {
|
|
||||||
pipeline: Vec<fn(String) -> String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pipeline {
|
|
||||||
pub fn run(&self, val: String) -> String {
|
|
||||||
let mut current = val;
|
|
||||||
|
|
||||||
for instr in &self.pipeline {
|
|
||||||
current = instr(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
current
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PipelineBuilder {
|
|
||||||
pipeline: Vec<Instruction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PipelineBuilder {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
pipeline: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn insert(mut self, instr: Instruction) -> Self {
|
|
||||||
self.pipeline.push(instr);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(&self) -> Pipeline {
|
|
||||||
fn uppercase(v: String) -> String {
|
|
||||||
str::to_uppercase(&v)
|
|
||||||
}
|
|
||||||
fn lowercase(v: String) -> String {
|
|
||||||
str::to_lowercase(&v)
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut res = Vec::new();
|
|
||||||
|
|
||||||
for item in &self.pipeline {
|
|
||||||
res.push(match item {
|
|
||||||
Instruction::Uppercase => uppercase,
|
|
||||||
Instruction::Lowercase => lowercase,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Pipeline { pipeline: res }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PipelineBuilder {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
//! An experiment for a hyper-modular trait-based architecture.
|
|
||||||
//!
|
|
||||||
//! Patterns defining this (or well, which I reference a lot while writing this):
|
|
||||||
//! - [Command pattern using trait objects](https://rust-unofficial.github.io/patterns/patterns/behavioural/command.html)
|
|
||||||
//! - [Builder pattern](https://rust-unofficial.github.io/patterns/patterns/creational/builder.html)
|
|
||||||
|
|
||||||
pub mod data;
|
|
||||||
#[macro_use]
|
|
||||||
pub mod element;
|
|
||||||
pub mod ops;
|
|
||||||
pub mod pipeline;
|
|
|
@ -1,5 +0,0 @@
|
||||||
//! Definitions of the data transfer and storage types.
|
|
||||||
|
|
||||||
pub mod io;
|
|
||||||
|
|
||||||
pub mod raw;
|
|
|
@ -1,53 +0,0 @@
|
||||||
//! Types for element and pipeline IO
|
|
||||||
|
|
||||||
use std::{borrow::ToOwned, convert::Into};
|
|
||||||
|
|
||||||
use super::raw::Data;
|
|
||||||
|
|
||||||
/// Newtype struct with borrowed types for pipeline/element inputs, so that doesn't force a move or clone
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
|
||||||
pub struct Inputs<'a>(pub Vec<&'a Data>);
|
|
||||||
|
|
||||||
impl<'a> From<Vec<&'a Data>> for Inputs<'a> {
|
|
||||||
fn from(value: Vec<&'a Data>) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Into<&'a Data>> From<T> for Inputs<'a> {
|
|
||||||
fn from(value: T) -> Self {
|
|
||||||
Self(vec![value.into()])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a Outputs> for Inputs<'a> {
|
|
||||||
fn from(value: &'a Outputs) -> Self {
|
|
||||||
Self(value.0.iter().map(Into::into).collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used for pipeline/element outputs
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
|
||||||
pub struct Outputs(pub Vec<Data>);
|
|
||||||
|
|
||||||
impl Outputs {
|
|
||||||
/// consume self and return inner value(s)
|
|
||||||
pub fn into_inner(self) -> Vec<Data> {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<Vec<Data>> for Outputs {
|
|
||||||
fn from(value: Vec<Data>) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Into<Data>> From<T> for Outputs {
|
|
||||||
fn from(value: T) -> Self {
|
|
||||||
Self(vec![value.into()])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<Inputs<'_>> for Outputs {
|
|
||||||
fn from(value: Inputs) -> Self {
|
|
||||||
Self(value.0.into_iter().map(ToOwned::to_owned).collect())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
//! Dynamic data storage and transfer types for use in [`io`]
|
|
||||||
|
|
||||||
// Dynamic data type
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum Data {
|
|
||||||
String(String),
|
|
||||||
Int(i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Data {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
Self::String(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i32> for Data {
|
|
||||||
fn from(value: i32) -> Self {
|
|
||||||
Self::Int(value)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
//! The trait and type representations
|
|
||||||
|
|
||||||
use std::any::TypeId;
|
|
||||||
|
|
||||||
use crate::experimental::trait_based::data::io::Inputs;
|
|
||||||
|
|
||||||
use super::data::io::Outputs;
|
|
||||||
|
|
||||||
pub(crate) trait PipelineElement {
|
|
||||||
/// return a static runner function pointer to avoid dynamic dispatch during pipeline execution - Types MUST match the signature
|
|
||||||
fn runner(&self) -> fn(&Inputs) -> Outputs;
|
|
||||||
/// return the signature of the element
|
|
||||||
fn signature(&self) -> ElementSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type signature for an element used for static checking
|
|
||||||
pub(crate) struct ElementSignature {
|
|
||||||
pub inputs: Vec<TypeId>,
|
|
||||||
pub outputs: Vec<TypeId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! signature {
|
|
||||||
($($inputs:ty),+ => $($outputs:ty),+) => (
|
|
||||||
ElementSignature {
|
|
||||||
inputs: vec![$(std::any::TypeId::of::<$inputs>(), )+],
|
|
||||||
outputs: vec![$(std::any::TypeId::of::<$outputs>(), )+]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
mod num;
|
|
||||||
mod str;
|
|
||||||
|
|
||||||
pub mod prelude {
|
|
||||||
pub(crate) use super::num::*;
|
|
||||||
pub(crate) use super::str::*;
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
//! Operations on numeric data
|
|
||||||
use core::panic;
|
|
||||||
use std::any::TypeId;
|
|
||||||
|
|
||||||
use crate::experimental::trait_based::{
|
|
||||||
data::{
|
|
||||||
io::{Inputs, Outputs},
|
|
||||||
raw::Data,
|
|
||||||
},
|
|
||||||
element::{ElementSignature, PipelineElement},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Addition
|
|
||||||
pub struct Add(pub i32);
|
|
||||||
impl PipelineElement for Add {
|
|
||||||
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
|
||||||
|input| {
|
|
||||||
let [Data::Int(i0), Data::Int(i1), ..] = input.0[..] else {
|
|
||||||
panic!("Invalid data passed")
|
|
||||||
};
|
|
||||||
(i0 + i1).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> ElementSignature {
|
|
||||||
signature!(i32, i32 => i32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subtraction
|
|
||||||
pub struct Subtract(pub i32);
|
|
||||||
impl PipelineElement for Subtract {
|
|
||||||
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
|
||||||
|input| {
|
|
||||||
let [Data::Int(i0), Data::Int(i1), ..] = input.0[..] else {
|
|
||||||
panic!("Invalid data passed")
|
|
||||||
};
|
|
||||||
(i0 + i1).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> ElementSignature {
|
|
||||||
signature!(i32, i32 => i32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Turn input to string
|
|
||||||
pub struct Stringify;
|
|
||||||
impl PipelineElement for Stringify {
|
|
||||||
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
|
||||||
|input| {
|
|
||||||
let [Data::Int(int), ..] = input.0[..] else {
|
|
||||||
panic!("Invalid data passed")
|
|
||||||
};
|
|
||||||
int.to_string().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> ElementSignature {
|
|
||||||
signature!(i32 => String)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
//! Operation on String/text data
|
|
||||||
use crate::experimental::trait_based::{
|
|
||||||
data::{
|
|
||||||
io::{Inputs, Outputs},
|
|
||||||
raw::Data,
|
|
||||||
},
|
|
||||||
element::{ElementSignature, PipelineElement},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Concatenate the inputs
|
|
||||||
pub struct Concatenate(pub String);
|
|
||||||
impl PipelineElement for Concatenate {
|
|
||||||
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
|
||||||
|input| {
|
|
||||||
let [Data::String(s0), Data::String(s1), ..] = input.0[..] else {
|
|
||||||
panic!("Invalid data passed")
|
|
||||||
};
|
|
||||||
format!("{s0}{s1}").into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> ElementSignature {
|
|
||||||
signature!(String, String => String)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Turn input text to uppercase
|
|
||||||
pub struct Upper;
|
|
||||||
impl PipelineElement for Upper {
|
|
||||||
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
|
||||||
|input| {
|
|
||||||
let [Data::String(s), ..] = input.0[..] else {
|
|
||||||
panic!("Invalid data passed")
|
|
||||||
};
|
|
||||||
s.to_uppercase().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> ElementSignature {
|
|
||||||
signature!(String => String)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Turn input text to lowercase
|
|
||||||
pub struct Lower;
|
|
||||||
impl PipelineElement for Lower {
|
|
||||||
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
|
||||||
|input| {
|
|
||||||
let [Data::String(s), ..] = input.0[..] else {
|
|
||||||
panic!("Invalid data passed")
|
|
||||||
};
|
|
||||||
s.to_lowercase().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> ElementSignature {
|
|
||||||
signature!(String => String)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
use super::data::io::{Inputs, Outputs};
|
|
||||||
use super::element::PipelineElement;
|
|
||||||
use super::ops::prelude::*;
|
|
||||||
|
|
||||||
/// Builder for the pipelines that are actually run
|
|
||||||
///
|
|
||||||
/// TODO:
|
|
||||||
/// - Bind additional inputs if instruction has more then one and is passd without any additional
|
|
||||||
/// - allow binding to pointers to other pipelines?
|
|
||||||
/// - allow referencing earlier data
|
|
||||||
pub struct PipelineBuilder {
|
|
||||||
elements: Vec<Box<dyn PipelineElement>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PipelineBuilder {
|
|
||||||
/// Create new, empty builder
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
elements: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert element into pipeline
|
|
||||||
fn insert<T: PipelineElement + 'static>(mut self, el: T) -> Self {
|
|
||||||
if let Some(previous_item) = self.elements.last() {
|
|
||||||
assert_eq!(
|
|
||||||
previous_item.signature().outputs[0],
|
|
||||||
el.signature().inputs[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.elements.push(Box::new(el));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// insert string concatenattion element
|
|
||||||
#[must_use]
|
|
||||||
pub fn concatenate(self, sec: String) -> Self {
|
|
||||||
self.insert(Concatenate(sec))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// insert string uppercase element
|
|
||||||
#[must_use]
|
|
||||||
pub fn upper(self) -> Self {
|
|
||||||
self.insert(Upper)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// insert string lowercase element
|
|
||||||
#[must_use]
|
|
||||||
pub fn lower(self) -> Self {
|
|
||||||
self.insert(Lower)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// insert numeric addition element
|
|
||||||
#[must_use]
|
|
||||||
#[allow(
|
|
||||||
clippy::should_implement_trait,
|
|
||||||
reason = "is not equivalent to addition"
|
|
||||||
)]
|
|
||||||
pub fn add(self, sec: i32) -> Self {
|
|
||||||
self.insert(Add(sec))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// insert numeric subtraction element
|
|
||||||
#[must_use]
|
|
||||||
pub fn subtract(self, sec: i32) -> Self {
|
|
||||||
self.insert(Subtract(sec))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// insert stringify element
|
|
||||||
#[must_use]
|
|
||||||
pub fn stringify(self) -> Self {
|
|
||||||
self.insert(Stringify)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build the pipeline. Doesn't check again - `insert` should verify correctness.
|
|
||||||
pub fn build(&self) -> Pipeline {
|
|
||||||
let mut r = Vec::new();
|
|
||||||
|
|
||||||
self.elements.iter().for_each(|el| r.push(el.runner()));
|
|
||||||
|
|
||||||
Pipeline { runners: r }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PipelineBuilder {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runnable pipeline - at the core of this library
|
|
||||||
pub struct Pipeline {
|
|
||||||
runners: Vec<fn(&Inputs) -> Outputs>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pipeline {
|
|
||||||
/// run the pipeline
|
|
||||||
pub fn run(&self, inputs: Inputs) -> Outputs {
|
|
||||||
let mut out: Outputs = inputs.into();
|
|
||||||
|
|
||||||
for runner in &self.runners {
|
|
||||||
out = runner(&(&out).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
//! # This is the image processing library for iOwO
|
|
||||||
//!
|
|
||||||
//! One of the design goals for this library is, however, to be a simple, generic image processing library.
|
|
||||||
//! For now, it's just indev... lets see what comes of it!
|
|
||||||
#![feature(lint_reasons)]
|
|
||||||
|
|
||||||
/// just some experiments, to test whether the architecture i want is even possible (or how to do it). probably temporary.
|
|
||||||
/// Gonna first try string processing...
|
|
||||||
pub mod experimental;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::experimental::{
|
|
||||||
enum_based,
|
|
||||||
trait_based::{self, data::io::Outputs},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_enums() {
|
|
||||||
let builder = enum_based::PipelineBuilder::new().insert(enum_based::Instruction::Uppercase);
|
|
||||||
let upr = builder.build();
|
|
||||||
let upr_lowr = builder.insert(enum_based::Instruction::Lowercase).build();
|
|
||||||
|
|
||||||
assert_eq!(upr.run(String::from("Test")), String::from("TEST"));
|
|
||||||
assert_eq!(upr_lowr.run(String::from("Test")), String::from("test"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add() {
|
|
||||||
let pipe = trait_based::pipeline::PipelineBuilder::new()
|
|
||||||
.add(0)
|
|
||||||
.stringify()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
pipe.run(vec![&2.into(), &3.into()].into()),
|
|
||||||
Outputs(vec![String::from("5").into()])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "prowocessing"
|
name = "rpl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
image = "0.24.8"
|
serde = { version = "1.0.193", features = [ "derive" ] }
|
||||||
palette = "0.7.4"
|
ron = "0.8"
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
47
crates/rpl/src/instructions/mod.rs
Normal file
47
crates/rpl/src/instructions/mod.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub mod read;
|
||||||
|
pub mod write;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Instruction {
|
||||||
|
Read(read::Read),
|
||||||
|
Write(write::Write),
|
||||||
|
Math(MathInstruction),
|
||||||
|
Blend(BlendInstruction),
|
||||||
|
Noise(NoiseInstruction),
|
||||||
|
Filter(FilterInstruction),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub enum MathInstruction {
|
||||||
|
Add,
|
||||||
|
Subtract,
|
||||||
|
Multiply,
|
||||||
|
Divide,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub enum BlendInstruction {
|
||||||
|
Normal,
|
||||||
|
Multiply,
|
||||||
|
Additive,
|
||||||
|
Overlay,
|
||||||
|
Screen,
|
||||||
|
Subtractive,
|
||||||
|
Difference,
|
||||||
|
Darken,
|
||||||
|
Lighten,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub enum NoiseInstruction {
|
||||||
|
Perlin,
|
||||||
|
Simplex,
|
||||||
|
Voronoi,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub enum FilterInstruction {
|
||||||
|
Invert,
|
||||||
|
}
|
19
crates/rpl/src/instructions/read.rs
Normal file
19
crates/rpl/src/instructions/read.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Read {
|
||||||
|
pub source: SourceType,
|
||||||
|
pub format: SourceFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub enum SourceType {
|
||||||
|
File(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub enum SourceFormat {
|
||||||
|
Jpeg,
|
||||||
|
Png,
|
||||||
|
}
|
19
crates/rpl/src/instructions/write.rs
Normal file
19
crates/rpl/src/instructions/write.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Write {
|
||||||
|
pub target: TargetType,
|
||||||
|
pub format: TargetFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub enum TargetType {
|
||||||
|
File(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub enum TargetFormat {
|
||||||
|
Jpeg,
|
||||||
|
Png,
|
||||||
|
}
|
38
crates/rpl/src/lib.rs
Normal file
38
crates/rpl/src/lib.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use instructions::Instruction;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::instructions::{
|
||||||
|
read::{SourceFormat, SourceType},
|
||||||
|
write::{TargetFormat, TargetType},
|
||||||
|
MathInstruction,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod instructions;
|
||||||
|
|
||||||
|
pub fn from_ron(raw: &str) -> Rpl {
|
||||||
|
ron::from_str(raw).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Rpl(pub Vec<Instruction>);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_deserialize() {
|
||||||
|
const TEST_DATA: &str =
|
||||||
|
"([Read( (source: File(\"~/example/file.png\"), format: Png) ),Math(Add),Write(( target: File(\"~/example/out.jpg\"), format: Jpeg))])";
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
from_ron(TEST_DATA),
|
||||||
|
Rpl(vec![
|
||||||
|
Instruction::Read(instructions::read::Read {
|
||||||
|
source: SourceType::File("~/example/file.png".into()),
|
||||||
|
format: SourceFormat::Png
|
||||||
|
}),
|
||||||
|
Instruction::Math(MathInstruction::Add),
|
||||||
|
Instruction::Write(instructions::write::Write {
|
||||||
|
target: TargetType::File("~/example/out.jpg".into()),
|
||||||
|
format: TargetFormat::Jpeg
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
3
crates/rpl/src/value/mod.rs
Normal file
3
crates/rpl/src/value/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub enum DynamicValue {
|
||||||
|
Image(DynamicImage),
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "svg-filters"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
csscolorparser = "0.6.2"
|
|
||||||
indexmap = "2.2.5"
|
|
||||||
petgraph = { workspace = true }
|
|
||||||
quick-xml = { version = "0.31.0", features = ["serialize"] }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
|
@ -1,158 +0,0 @@
|
||||||
use std::{
|
|
||||||
cmp,
|
|
||||||
collections::{BTreeSet, HashMap},
|
|
||||||
fmt::Display,
|
|
||||||
io::Read,
|
|
||||||
ops::Not,
|
|
||||||
};
|
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
use petgraph::{
|
|
||||||
algo::toposort,
|
|
||||||
graph::DiGraph,
|
|
||||||
prelude::{EdgeIndex, NodeIndex},
|
|
||||||
};
|
|
||||||
use quick_xml::ElementWriter;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
types::{
|
|
||||||
graph::{edge::Edge, FilterGraph, NodeInput},
|
|
||||||
nodes::{primitives::WriteElement, CommonAttrs},
|
|
||||||
},
|
|
||||||
Node,
|
|
||||||
};
|
|
||||||
|
|
||||||
use self::error::CodegenError;
|
|
||||||
|
|
||||||
pub struct SvgDocument {
|
|
||||||
filters: HashMap<String, FilterGraph>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SvgDocument {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
filters: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used, reason = "we literally just did the insertion")]
|
|
||||||
pub fn create_filter(&mut self, id: impl ToString) -> &mut FilterGraph {
|
|
||||||
let filter = FilterGraph::new();
|
|
||||||
|
|
||||||
self.filters.insert(id.to_string(), filter);
|
|
||||||
self.filters.get_mut(&id.to_string()).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_svg_pretty(&self) -> String {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
let doc_writer = quick_xml::Writer::new_with_indent(&mut result, b' ', 2);
|
|
||||||
|
|
||||||
self.generate(doc_writer);
|
|
||||||
|
|
||||||
String::from_utf8_lossy(&result).to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_svg(&self) -> String {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
let doc_writer = quick_xml::Writer::new(&mut result);
|
|
||||||
|
|
||||||
self.generate(doc_writer);
|
|
||||||
|
|
||||||
String::from_utf8_lossy(&result).to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate(&self, mut doc_writer: quick_xml::Writer<&mut Vec<u8>>) {
|
|
||||||
doc_writer
|
|
||||||
.create_element("svg")
|
|
||||||
.write_inner_content(|writer| {
|
|
||||||
self.filters
|
|
||||||
.iter()
|
|
||||||
.try_fold(writer, Self::gen_filter)
|
|
||||||
.map(|_| {})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen_filter<'w, 'r>(
|
|
||||||
writer: &'w mut quick_xml::Writer<&'r mut Vec<u8>>,
|
|
||||||
(id, graph): (&String, &FilterGraph),
|
|
||||||
) -> Result<&'w mut quick_xml::Writer<&'r mut Vec<u8>>, CodegenError> {
|
|
||||||
writer
|
|
||||||
.create_element("filter")
|
|
||||||
.with_attribute(("id", id.as_str()))
|
|
||||||
.write_inner_content(|writer| Self::graph_to_svg(writer, graph))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn graph_to_svg(
|
|
||||||
writer: &mut quick_xml::Writer<&mut Vec<u8>>,
|
|
||||||
graph: &FilterGraph,
|
|
||||||
) -> Result<(), CodegenError> {
|
|
||||||
let sorted = toposort(&graph.dag, None).expect("no cycles allowed in a DAG");
|
|
||||||
sorted
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|node_idx| {
|
|
||||||
graph
|
|
||||||
.dag
|
|
||||||
.node_weight(node_idx)
|
|
||||||
.and_then(|node| node.primitive())
|
|
||||||
.map(|(primitive, common_attrs)| (node_idx, primitive, common_attrs))
|
|
||||||
})
|
|
||||||
.try_fold(writer, |writer, (node_idx, primitive, common_attrs)| {
|
|
||||||
primitive.element_writer(
|
|
||||||
writer,
|
|
||||||
*common_attrs,
|
|
||||||
graph
|
|
||||||
.inputs(node_idx)
|
|
||||||
.into_iter()
|
|
||||||
.map(|v| v.to_string())
|
|
||||||
.collect(),
|
|
||||||
graph
|
|
||||||
.outputs(node_idx)
|
|
||||||
.is_empty()
|
|
||||||
.not()
|
|
||||||
.then_some(format!("r{}", node_idx.index())),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// convenience method to avoid fuckups during future changes
|
|
||||||
fn format_edge_idx(idx: EdgeIndex) -> String {
|
|
||||||
format!("edge{}", idx.index())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_node_idx(node_idx: NodeIndex) -> String {
|
|
||||||
format!("r{}", node_idx.index())
|
|
||||||
}
|
|
||||||
|
|
||||||
mod error {
|
|
||||||
use std::{error::Error, fmt::Display};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum CodegenError {
|
|
||||||
QuickXmlError(quick_xml::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<quick_xml::Error> for CodegenError {
|
|
||||||
fn from(value: quick_xml::Error) -> Self {
|
|
||||||
Self::QuickXmlError(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for CodegenError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
CodegenError::QuickXmlError(e) => e.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for CodegenError {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SvgDocument {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
#![feature(lint_reasons)]
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
pub mod util {
|
|
||||||
macro_rules! gen_attr {
|
|
||||||
($name:literal = $out:expr) => {
|
|
||||||
quick_xml::events::attributes::Attribute {
|
|
||||||
key: quick_xml::name::QName($name),
|
|
||||||
value: std::borrow::Cow::from(($out).to_string().into_bytes()),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! gen_attrs {
|
|
||||||
($($name:literal: $out:expr),+) => {
|
|
||||||
vec![
|
|
||||||
$(gen_attr!($name = $out)),+
|
|
||||||
]
|
|
||||||
};
|
|
||||||
($($cond:expr => $name:literal: $out:expr),+) => {
|
|
||||||
{
|
|
||||||
let mut r = Vec::new();
|
|
||||||
$(if $cond {
|
|
||||||
r.push(gen_attr!($name = $out));
|
|
||||||
})+
|
|
||||||
r
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($other:ident; $($cond:expr => $name:literal: $out:expr),+) => {
|
|
||||||
$other.append(&mut gen_attrs![$($cond => $name: $out),+]);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod codegen;
|
|
||||||
pub mod types;
|
|
||||||
pub use types::nodes::Node;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
|
@ -1,65 +0,0 @@
|
||||||
use svg_filters::{
|
|
||||||
codegen::SvgDocument,
|
|
||||||
types::nodes::{
|
|
||||||
primitives::{
|
|
||||||
blend::BlendMode,
|
|
||||||
color_matrix::ColorMatrixType,
|
|
||||||
component_transfer::TransferFn,
|
|
||||||
displacement_map::Channel,
|
|
||||||
turbulence::{NoiseType, StitchTiles},
|
|
||||||
},
|
|
||||||
standard_input::StandardInput,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut doc = SvgDocument::new();
|
|
||||||
|
|
||||||
let f = doc.create_filter("cmyk-chromabb");
|
|
||||||
|
|
||||||
let noise = f.turbulence(0., 0.1, 2, 0, StitchTiles::Stitch, NoiseType::FractalNoise);
|
|
||||||
let noise = f.component_transfer_rgba(
|
|
||||||
noise,
|
|
||||||
TransferFn::Discrete {
|
|
||||||
table_values: vec![0., 0.2, 0.4, 0.6, 0.8, 1.],
|
|
||||||
},
|
|
||||||
TransferFn::Discrete {
|
|
||||||
table_values: vec![0., 0.2, 0.4, 0.6, 0.8, 1.],
|
|
||||||
},
|
|
||||||
TransferFn::Discrete {
|
|
||||||
table_values: vec![0., 0.2, 0.4, 0.6, 0.8, 1.],
|
|
||||||
},
|
|
||||||
TransferFn::Linear {
|
|
||||||
slope: 0.,
|
|
||||||
intercept: 0.5,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let cyan = f.color_matrix(
|
|
||||||
StandardInput::SourceGraphic,
|
|
||||||
ColorMatrixType::Matrix(Box::new([
|
|
||||||
0., 0., 0., 0., 0., //
|
|
||||||
0., 1., 0., 0., 0., //
|
|
||||||
0., 0., 1., 0., 0., //
|
|
||||||
0., 0., 0., 1., 0.,
|
|
||||||
])),
|
|
||||||
);
|
|
||||||
let cyan = f.offset(cyan, 25., 0.);
|
|
||||||
let cyan = f.displacement_map(cyan, noise, 50., Channel::R, Channel::A);
|
|
||||||
|
|
||||||
let magenta = f.color_matrix(
|
|
||||||
StandardInput::SourceGraphic,
|
|
||||||
ColorMatrixType::Matrix(Box::new([
|
|
||||||
1., 0., 0., 0., 0., //
|
|
||||||
0., 0., 0., 0., 0., //
|
|
||||||
0., 0., 1., 0., 0., //
|
|
||||||
0., 0., 0., 1., 0.,
|
|
||||||
])),
|
|
||||||
);
|
|
||||||
let magenta = f.displacement_map(magenta, noise, 50., Channel::R, Channel::A);
|
|
||||||
let magenta = f.offset(magenta, -25., 0.);
|
|
||||||
|
|
||||||
f.blend(cyan, magenta, BlendMode::Screen);
|
|
||||||
|
|
||||||
println!("{}", doc.generate_svg_pretty());
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
mod blend;
|
|
||||||
mod color_matrix;
|
|
||||||
mod complex;
|
|
||||||
mod component_transfer;
|
|
||||||
mod displacement_map;
|
|
||||||
mod flood;
|
|
||||||
mod gaussian_blur;
|
|
||||||
mod offset;
|
|
||||||
mod turbulence;
|
|
||||||
mod composite {}
|
|
||||||
mod convolve_matrix {}
|
|
||||||
mod diffuse_lighting {}
|
|
||||||
mod image {}
|
|
||||||
mod merge {}
|
|
||||||
mod morphology {}
|
|
||||||
mod specular_lighting {}
|
|
||||||
mod tile {}
|
|
|
@ -1,20 +0,0 @@
|
||||||
use crate::{
|
|
||||||
codegen::SvgDocument,
|
|
||||||
types::nodes::{primitives::blend::BlendMode, standard_input::StandardInput},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_offset_blend() {
|
|
||||||
let mut doc = SvgDocument::new();
|
|
||||||
|
|
||||||
let blend = doc.create_filter("blend");
|
|
||||||
|
|
||||||
let offset0 = blend.offset(StandardInput::SourceGraphic, 100., 0.);
|
|
||||||
let offset1 = blend.offset(StandardInput::SourceGraphic, -100., 0.);
|
|
||||||
blend.blend(offset0, offset1, BlendMode::Multiply);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
doc.generate_svg(),
|
|
||||||
r#"<svg><filter id="blend"><feOffset dx="-100" dy="0" in="SourceGraphic" result="r7"/><feOffset dx="100" dy="0" in="SourceGraphic" result="r6"/><feBlend mode="multiply" in="r6" in2="r7"/></filter></svg>"#
|
|
||||||
);
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue