feat: add statement parsing.

This commit is contained in:
James Harton 2022-07-20 20:43:13 +00:00
parent 4864ef980e
commit 1fe2de4444
33 changed files with 1968 additions and 154 deletions

76
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1,76 @@
FROM ubuntu:latest
ENV DEBIAN_FRONTEND="noninteractive"
# Install basic dependencies
RUN apt-get -q -y update && \
apt-get -q -y --no-install-recommends install autoconf automake \
bash build-essential bzip2 ca-certificates curl dpkg-dev file \
g++ gcc git-core imagemagick libbz2-dev libc6-dev libdb-dev libevent-dev \
libffi-dev libgdbm-dev libglib2.0-dev libgmp-dev libjpeg-dev libkrb5-dev \
liblzma-dev libmagickcore-dev libmagickwand-dev libmaxminddb-dev \
libncurses-dev libncurses5-dev libncursesw5-dev libpng-dev libpq-dev \
libreadline-dev libsctp-dev libsqlite3-dev libssl-dev libtool libwebp-dev \
libxml2-dev libxslt-dev libyaml-dev locales make make mercurial patch python3 \
unixodbc-dev unzip wget xz-utils zlib1g-dev zsh gnupg inotify-tools less \
postgresql-client ssh direnv && apt-get -q -y clean
ARG LANG=en_NZ.UTF-8 LANGUAGE=en_NZ:en
RUN locale-gen ${LANG}
ENV LANG ${LANG}
ENV LANGUAGE ${LANGUAGE}
ENV LC_ALL ${LANG}
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupadd --gid ${USER_GID} ${USERNAME}
RUN useradd --shell /usr/bin/zsh --uid ${USER_UID} --gid ${USER_GID} -m ${USERNAME}
RUN mkdir /workspace && chown ${USERNAME}:${USERNAME} /workspace
RUN touch /entrypoint.sh && chown ${USERNAME}:${USERNAME} /entrypoint.sh
RUN mkdir -p /var/tmp/history && chown -R ${USERNAME}:${USERNAME} /var/tmp/history
RUN mkdir /storage && chown ${USERNAME}:${USERNAME} /storage
USER ${USERNAME}
ENV HOME=/home/${USERNAME}
WORKDIR $HOME
# Install oh-my-zsh
RUN sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
RUN echo '\neval "$(direnv hook zsh)"' >> ~/.zshrc
# Install ASDF
ARG ASDF_VERSION=0.10.2
RUN git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v${ASDF_VERSION} && \
echo '\nsource $HOME/.asdf/asdf.sh' >> ~/.bashrc && \
echo '\nsource $HOME/.asdf/asdf.sh' >> ~/.zshrc
WORKDIR /workspace/
# Install all the tools specified in the tool versions file.
COPY .tool-versions /workspace/
RUN /bin/bash -c 'source ~/.asdf/asdf.sh && \
cat .tool-versions | cut -d \ -f 1 | xargs -n 1 asdf plugin add && \
asdf install && \
cat .tool-versions | xargs -n 2 asdf global'
USER vscode
# Generate an entrypoint.sh
RUN echo '#!/bin/bash' > /entrypoint.sh && \
echo 'source ~/.asdf/asdf.sh' >> /entrypoint.sh && \
echo 'eval "$(direnv hook bash)"' >> /entrypoint.sh && \
echo 'exec "$@"' >> /entrypoint.sh && \
chmod 755 /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
# Prodigious use of asterisk to allow for files which may not exist.
COPY .env* Cargo.toml Cargo.lock LICENSE.md README.md outrun outrun-* /workspace/
RUN /bin/bash -c 'if [ -e .envrc ]; then /usr/bin/direnv allow; fi'
CMD ["zsh"]

View file

@ -0,0 +1,35 @@
// For format details, see https://aka.ms/devcontainer.json.
{
"name": "ASDF/Rust",
"workspaceFolder": "/workspace",
"dockerComposeFile": "docker-compose.yml",
"service": "dev",
"customizations": {
"vscode": {
"settings": {
"lldb.executable": "/usr/bin/lldb",
// VS Code don't watch files under ./target
"files.watcherExclude": {
"**/target/**": true
},
"rust-analyzer.checkOnSave.command": "clippy"
},
"extensions": [
"canna.figlet",
"marclipovsky.string-manipulation",
"mutantdino.resourcemonitor",
"RobbOwen.synthwave-vscode",
"Rubymaniac.vscode-direnv",
"rust-lang.rust-analyzer",
"serayuzgur.crates",
"serayuzgur.crates",
"TabNine.tabnine-vscode",
"tamasfe.even-better-toml",
"ue.alphabetical-sorter",
"vadimcn.vscode-lldb",
"wmaurer.change-case"
]
}
},
"remoteUser": "vscode"
}

View file

@ -0,0 +1,29 @@
version: "3.2"
volumes:
apt-cache: {}
history: {}
asdf: {}
target: {}
services:
dev:
environment:
GIT_COMMITER_EMAIL:
GIT_AUTHOR_EMAIL:
build:
context: ../
dockerfile: .devcontainer/Dockerfile
labels:
- "org.opencontainers.image.source=https://gitlab.com/jimsy/outrun"
volumes:
- ..:/workspace:cached
- "apt-cache:/var/cache/apt:rw"
- "history:/var/tmp/history:rw"
- "asdf:/users/vscode/.asdf:rw"
- "target:/users/vscode/target:rw"
cap_add:
- SYS_PTRACE
security_opt:
- seccomp:unconfined
command: sleep infinity

1
.tool-versions Normal file
View file

@ -0,0 +1 @@
rust 1.62.0

11
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"cSpell.words": [
"Celldweller",
"defp",
"hashrocket",
"indoc",
"lexer",
"Scandroid",
"struct"
]
}

22
Cargo.lock generated
View file

@ -2,17 +2,37 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "assert_matches"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
[[package]]
name = "indoc"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "outrun"
version = "0.1.0"
dependencies = [
"outrun-lexer",
"outrun-parser",
]
[[package]]
name = "outrun-lexer"
version = "0.1.0"
dependencies = [
"indoc",
"unicode-general-category",
"unicode-ident",
"unicode-normalization",
@ -22,6 +42,8 @@ dependencies = [
name = "outrun-parser"
version = "0.1.0"
dependencies = [
"assert_matches",
"lazy_static",
"outrun-lexer",
]

View file

@ -1,6 +1,16 @@
[![Hippocratic License HL3-FULL](https://img.shields.io/static/v1?label=Hippocratic%20License&message=HL3-FULL&labelColor=5e2751&color=bc8c3d)](https://firstdonoharm.dev/version/3/0/full.html)
[![Build Status](https://img.shields.io/gitlab/pipeline-status/jimsy/outrun?branch=main)](https://gitlab.com/jimsy/outrun/-/pipelines)
[![Issues](https://img.shields.io/gitlab/issues/open/jimsy/outrun)](https://gitlab.com/jimsy/outrun/-/issues)
# 🌅 Outrun
The most retro-futuristic toy language in the world.
Outrun is still a work in progress.
# License
Outrun is distributed under the terms of the the Hippocratic Version 3.0 Full License.
See [LICENSE.md](https://gitlab.com/jimsy/outrun/-/blob/main/LICENSE.md) for details.

View file

@ -2,7 +2,7 @@
authors = ["James Harton <james@harton.nz>"]
description = "🌅 A lexer for the most retro-futuristic toy language in the world."
edition = "2021"
license_file = "../LICENSE.md"
license-file = "../LICENSE.md"
name = "outrun-lexer"
version = "0.1.0"
@ -12,3 +12,6 @@ version = "0.1.0"
unicode-general-category = "0.5.1"
unicode-ident = "1.0.2"
unicode-normalization = "0.1.21"
[dev-dependencies]
indoc = "1.0.6"

151
outrun-lexer/LICENSE.md Normal file
View file

@ -0,0 +1,151 @@
_Copyright 2022 James Harton_
**HIPPOCRATIC LICENSE**
**Version 3.0, October 2021**
<https://firstdonoharm.dev/version/3/0/full.md>
**TERMS AND CONDITIONS**
TERMS AND CONDITIONS FOR USE, COPY, MODIFICATION, PREPARATION OF DERIVATIVE WORK, REPRODUCTION, AND DISTRIBUTION:
**[1.](#1) DEFINITIONS:**
_This section defines certain terms used throughout this license agreement._
[1.1.](#1.1) “License” means the terms and conditions, as stated herein, for use, copy, modification, preparation of derivative work, reproduction, and distribution of Software (as defined below).
[1.2.](#1.2) “Licensor” means the copyright and/or patent owner or entity authorized by the copyright and/or patent owner that is granting the License.
[1.3.](#1.3) “Licensee” means the individual or entity exercising permissions granted by this License, including the use, copy, modification, preparation of derivative work, reproduction, and distribution of Software (as defined below).
[1.4.](#1.4) “Software” means any copyrighted work, including but not limited to software code, authored by Licensor and made available under this License.
[1.5.](#1.5) “Supply Chain” means the sequence of processes involved in the production and/or distribution of a commodity, good, or service offered by the Licensee.
[1.6.](#1.6) “Supply Chain Impacted Party” or “Supply Chain Impacted Parties” means any person(s) directly impacted by any of Licensees Supply Chain, including the practices of all persons or entities within the Supply Chain prior to a good or service reaching the Licensee.
[1.7.](#1.7) “Duty of Care” is defined by its use in tort law, delict law, and/or similar bodies of law closely related to tort and/or delict law, including without limitation, a requirement to act with the watchfulness, attention, caution, and prudence that a reasonable person in the same or similar circumstances would use towards any Supply Chain Impacted Party.
[1.8.](#1.8) “Worker” is defined to include any and all permanent, temporary, and agency workers, as well as piece-rate, salaried, hourly paid, legal young (minors), part-time, night, and migrant workers.
**[2.](#2) INTELLECTUAL PROPERTY GRANTS:**
_This section identifies intellectual property rights granted to a Licensee_.
[2.1.](#2.1) _Grant of Copyright License_: Subject to the terms and conditions of this License, Licensor hereby grants to Licensee a worldwide, non-exclusive, no-charge, royalty-free copyright license to use, copy, modify, prepare derivative work, reproduce, or distribute the Software, Licensor authored modified software, or other work derived from the Software.
[2.2.](#2.2) _Grant of Patent License_: Subject to the terms and conditions of this License, Licensor hereby grants Licensee a worldwide, non-exclusive, no-charge, royalty-free patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer Software.
**[3.](#3) ETHICAL STANDARDS:**
_This section lists conditions the Licensee must comply with in order to have rights under this License._
The rights granted to the Licensee by this License are expressly made subject to the Licensees ongoing compliance with the following conditions:
* [3.1.](#3.1) The Licensee SHALL NOT, whether directly or indirectly, through agents or assigns:
* [3.1.1.](#3.1.1) Infringe upon any persons right to life or security of person, engage in extrajudicial killings, or commit murder, without lawful cause (See Article 3, _United Nations Universal Declaration of Human Rights_; Article 6, _International Covenant on Civil and Political Rights_)
* [3.1.2.](#3.1.2) Hold any person in slavery, servitude, or forced labor (See Article 4, _United Nations Universal Declaration of Human Rights_; Article 8, _International Covenant on Civil and Political Rights_);
* [3.1.3.](#3.1.3) Contribute to the institution of slavery, slave trading, forced labor, or unlawful child labor (See Article 4, _United Nations Universal Declaration of Human Rights_; Article 8, _International Covenant on Civil and Political Rights_);
* [3.1.4.](#3.1.4) Torture or subject any person to cruel, inhumane, or degrading treatment or punishment (See Article 5, _United Nations Universal Declaration of Human Rights_; Article 7, _International Covenant on Civil and Political Rights_);
* [3.1.5.](#3.1.5) Discriminate on the basis of sex, gender, sexual orientation, race, ethnicity, nationality, religion, caste, age, medical disability or impairment, and/or any other like circumstances (See Article 7, _United Nations Universal Declaration of Human Rights_; Article 2, _International Covenant on Economic, Social and Cultural Rights_; Article 26, _International Covenant on Civil and Political Rights_);
* [3.1.6.](#3.1.6) Prevent any person from exercising his/her/their right to seek an effective remedy by a competent court or national tribunal (including domestic judicial systems, international courts, arbitration bodies, and other adjudicating bodies) for actions violating the fundamental rights granted to him/her/them by applicable constitutions, applicable laws, or by this License (See Article 8, _United Nations Universal Declaration of Human Rights_; Articles 9 and 14, _International Covenant on Civil and Political Rights_);
* [3.1.7.](#3.1.7) Subject any person to arbitrary arrest, detention, or exile (See Article 9, _United Nations Universal Declaration of Human Rights_; Article 9, _International Covenant on Civil and Political Rights_);
* [3.1.8.](#3.1.8) Subject any person to arbitrary interference with a persons privacy, family, home, or correspondence without the express written consent of the person (See Article 12, _United Nations Universal Declaration of Human Rights_; Article 17, _International Covenant on Civil and Political Rights_);
* [3.1.9.](#3.1.9) Arbitrarily deprive any person of his/her/their property (See Article 17, _United Nations Universal Declaration of Human Rights_);
* [3.1.10.](#3.1.10) Forcibly remove indigenous peoples from their lands or territories or take any action with the aim or effect of dispossessing indigenous peoples from their lands, territories, or resources, including without limitation the intellectual property or traditional knowledge of indigenous peoples, without the free, prior, and informed consent of indigenous peoples concerned (See Articles 8 and 10, _United Nations Declaration on the Rights of Indigenous Peoples_);
* [3.1.11.](#3.1.11) _Fossil Fuel Divestment_: Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, on the [FFI Solutions Carbon Underground 200 list](https://www.ffisolutions.com/research-analytics-index-solutions/research-screening/the-carbon-underground-200/?cn-reloaded=1);
* [3.1.12.](#3.1.12) _Ecocide_: Commit ecocide:
* [3.1.12.1.](#3.1.12.1) For the purpose of this section, “ecocide” means unlawful or wanton acts committed with knowledge that there is a substantial likelihood of severe and either widespread or long-term damage to the environment being caused by those acts;
* [3.1.12.2.](#3.1.12.2) For the purpose of further defining ecocide and the terms contained in the previous paragraph:
* [3.1.12.2.1.](#3.1.12.2.1) “Wanton” means with reckless disregard for damage which would be clearly excessive in relation to the social and economic benefits anticipated;
* [3.1.12.2.2.](#3.1.12.2.2) “Severe” means damage which involves very serious adverse changes, disruption, or harm to any element of the environment, including grave impacts on human life or natural, cultural, or economic resources;
* [3.1.12.2.3.](#3.1.12.2.3) “Widespread” means damage which extends beyond a limited geographic area, crosses state boundaries, or is suffered by an entire ecosystem or species or a large number of human beings;
* [3.1.12.2.4.](#3.1.12.2.4) “Long-term” means damage which is irreversible or which cannot be redressed through natural recovery within a reasonable period of time; and
* [3.1.12.2.5.](#3.1.12.2.5) “Environment” means the earth, its biosphere, cryosphere, lithosphere, hydrosphere, and atmosphere, as well as outer space
(See Section II, _Independent Expert Panel for the Legal Definition of Ecocide_, Stop Ecocide Foundation and the Promise Institute for Human Rights at UCLA School of Law, June 2021);
* [3.1.13.](#3.1.13) _Extractive Industries_: Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, that engages in fossil fuel or mineral exploration, extraction, development, or sale;
* [3.1.14.](#3.1.14) _Boycott / Divestment / Sanctions_: Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, identified by the Boycott, Divestment, Sanctions (“BDS”) movement on its website (<https://bdsmovement.net/> and <https://bdsmovement.net/get-involved/what-to-boycott>) as a target for boycott;
* [3.1.15.](#3.1.15) _Taliban_: Be an individual or entity that:
* [3.1.15.1.](#3.1.15.1) engages in any commercial transactions with the Taliban; or
* [3.1.15.2.](#3.1.15.2) is a representative, agent, affiliate, successor, attorney, or assign of the Taliban;
* [3.1.16.](#3.1.16) _Myanmar_: Be an individual or entity that:
* [3.1.16.1.](#3.1.16.1) engages in any commercial transactions with the Myanmar/Burmese military junta; or
* [3.1.16.2.](#3.1.16.2) is a representative, agent, affiliate, successor, attorney, or assign of the Myanmar/Burmese government;
* [3.1.17.](#3.1.17) _Xinjiang Uygur Autonomous Region_: Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of any individual or entity, that does business in, purchases goods from, or otherwise benefits from goods produced in the Xinjiang Uygur Autonomous Region of China;
* [3.1.18.](#3.1.18) _US Tariff Act_: Be an individual or entity:
* [3.1.18.1.](#3.1.18.1) which U.S. Customs and Border Protection (CBP) has currently issued a Withhold Release Order (WRO) or finding against based on reasonable suspicion of forced labor; or
* [3.1.18.2.](#3.1.18.2) that is a representative, agent, affiliate, successor, attorney, or assign of an individual or entity that does business with an individual or entity which currently has a WRO or finding from CBP issued against it based on reasonable suspicion of forced labor;
* [3.1.19.](#3.1.19) _Mass Surveillance_: Be a government agency or multinational corporation, or a representative, agent, affiliate, successor, attorney, or assign of a government or multinational corporation, which participates in mass surveillance programs;
* [3.1.20.](#3.1.20) _Military Activities_: Be an entity or a representative, agent, affiliate, successor, attorney, or assign of an entity which conducts military activities;
* [3.1.21.](#3.1.21) _Law Enforcement_: Be an individual or entity, or a or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, that provides good or services to, or otherwise enters into any commercial contracts with, any local, state, or federal law enforcement agency;
* [3.1.22.](#3.1.22) _Media_: Be an individual or entity, or a or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, that broadcasts messages promoting killing, torture, or other forms of extreme violence;
* [3.1.23.](#3.1.23) Interfere with Workers' free exercise of the right to organize and associate (See Article 20, United Nations Universal Declaration of Human Rights; C087 - Freedom of Association and Protection of the Right to Organise Convention, 1948 (No. 87), International Labour Organization; Article 8, International Covenant on Economic, Social and Cultural Rights); and
* [3.1.24.](#3.1.24) Harm the environment in a manner inconsistent with local, state, national, or international law.
* [3.2.](#3.2) The Licensee SHALL:
* [3.2.1.](#3.2.1) _Social Auditing_: Only use social auditing mechanisms that adhere to Worker-Driven Social Responsibility Networks Statement of Principles (<https://wsr-network.org/what-is-wsr/statement-of-principles/>) over traditional social auditing mechanisms, to the extent the Licensee uses any social auditing mechanisms at all;
* [3.2.2.](#3.2.2) _Workers on Board of Directors_: Ensure that if the Licensee has a Board of Directors, 30% of Licensees board seats are held by Workers paid no more than 200% of the compensation of the lowest paid Worker of the Licensee;
* [3.2.3.](#3.2.3) _Supply Chain_: Provide clear, accessible supply chain data to the public in accordance with the following conditions:
* [3.2.3.1.](#3.2.3.1) All data will be on Licensees website and/or, to the extent Licensee is a representative, agent, affiliate, successor, attorney, subsidiary, or assign, on Licensees principals or parents website or some other online platform accessible to the public via an internet search on a common internet search engine; and
* [3.2.3.2.](#3.2.3.2) Data published will include, where applicable, manufacturers, top tier suppliers, subcontractors, cooperatives, component parts producers, and farms;
* [3.2.4.](#3.2.4) Provide equal pay for equal work where the performance of such work requires equal skill, effort, and responsibility, and which are performed under similar working conditions, except where such payment is made pursuant to:
* [3.2.4.1.](#3.2.4.1) A seniority system;
* [3.2.4.2.](#3.2.4.2) A merit system;
* [3.2.4.3.](#3.2.4.3) A system which measures earnings by quantity or quality of production; or
* [3.2.4.4.](#3.2.4.4) A differential based on any other factor other than sex, gender, sexual orientation, race, ethnicity, nationality, religion, caste, age, medical disability or impairment, and/or any other like circumstances (See 29 U.S.C.A. § 206(d)(1); Article 23, _United Nations Universal Declaration of Human Rights_; Article 7, _International Covenant on Economic, Social and Cultural Rights_; Article 26, _International Covenant on Civil and Political Rights_); and
* [3.2.5.](#3.2.5) Allow for reasonable limitation of working hours and periodic holidays with pay (See Article 24, _United Nations Universal Declaration of Human Rights_; Article 7, _International Covenant on Economic, Social and Cultural Rights_).
**[4.](#4) SUPPLY CHAIN IMPACTED PARTIES:**
_This section identifies additional individuals or entities that a Licensee could harm as a result of violating the Ethical Standards section, the condition that the Licensee must voluntarily accept a Duty of Care for those individuals or entities, and the right to a private right of action that those individuals or entities possess as a result of violations of the Ethical Standards section._
[4.1.](#4.1) In addition to the above Ethical Standards, Licensee voluntarily accepts a Duty of Care for Supply Chain Impacted Parties of this License, including individuals and communities impacted by violations of the Ethical Standards. The Duty of Care is breached when a provision within the Ethical Standards section is violated by a Licensee, one of its successors or assigns, or by an individual or entity that exists within the Supply Chain prior to a good or service reaching the Licensee.
[4.2.](#4.2) Breaches of the Duty of Care, as stated within this section, shall create a private right of action, allowing any Supply Chain Impacted Party harmed by the Licensee to take legal action against the Licensee in accordance with applicable negligence laws, whether they be in tort law, delict law, and/or similar bodies of law closely related to tort and/or delict law, regardless if Licensee is directly responsible for the harms suffered by a Supply Chain Impacted Party. Nothing in this section shall be interpreted to include acts committed by individuals outside of the scope of his/her/their employment.
[5.](#5) **NOTICE:** _This section explains when a Licensee must notify others of the License._
[5.1.](#5.1) _Distribution of Notice_: Licensee must ensure that everyone who receives a copy of or uses any part of Software from Licensee, with or without changes, also receives the License and the copyright notice included with Software (and if included by the Licensor, patent, trademark, and attribution notice). Licensee must ensure that License is prominently displayed so that any individual or entity seeking to download, copy, use, or otherwise receive any part of Software from Licensee is notified of this License and its terms and conditions. Licensee must cause any modified versions of the Software to carry prominent notices stating that Licensee changed the Software.
[5.2.](#5.2) _Modified Software_: Licensee is free to create modifications of the Software and distribute only the modified portion created by Licensee, however, any derivative work stemming from the Software or its code must be distributed pursuant to this License, including this Notice provision.
[5.3.](#5.3) _Recipients as Licensees_: Any individual or entity that uses, copies, modifies, reproduces, distributes, or prepares derivative work based upon the Software, all or part of the Softwares code, or a derivative work developed by using the Software, including a portion of its code, is a Licensee as defined above and is subject to the terms and conditions of this License.
**[6.](#6) REPRESENTATIONS AND WARRANTIES:**
[6.1.](#6.1) _Disclaimer of Warranty_: TO THE FULL EXTENT ALLOWED BY LAW, THIS SOFTWARE COMES “AS IS,” WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED, AND LICENSOR SHALL NOT BE LIABLE TO ANY PERSON OR ENTITY FOR ANY DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THIS LICENSE, UNDER ANY LEGAL CLAIM.
[6.2.](#6.2) _Limitation of Liability_: LICENSEE SHALL HOLD LICENSOR HARMLESS AGAINST ANY AND ALL CLAIMS, DEBTS, DUES, LIABILITIES, LIENS, CAUSES OF ACTION, DEMANDS, OBLIGATIONS, DISPUTES, DAMAGES, LOSSES, EXPENSES, ATTORNEYS' FEES, COSTS, LIABILITIES, AND ALL OTHER CLAIMS OF EVERY KIND AND NATURE WHATSOEVER, WHETHER KNOWN OR UNKNOWN, ANTICIPATED OR UNANTICIPATED, FORESEEN OR UNFORESEEN, ACCRUED OR UNACCRUED, DISCLOSED OR UNDISCLOSED, ARISING OUT OF OR RELATING TO LICENSEES USE OF THE SOFTWARE. NOTHING IN THIS SECTION SHOULD BE INTERPRETED TO REQUIRE LICENSEE TO INDEMNIFY LICENSOR, NOR REQUIRE LICENSOR TO INDEMNIFY LICENSEE.
**[7.](#7) TERMINATION**
[7.1.](#7.1) _Violations of Ethical Standards or Breaching Duty of Care_: If Licensee violates the Ethical Standards section or Licensee, or any other person or entity within the Supply Chain prior to a good or service reaching the Licensee, breaches its Duty of Care to Supply Chain Impacted Parties, Licensee must remedy the violation or harm caused by Licensee within 30 days of being notified of the violation or harm. If Licensee fails to remedy the violation or harm within 30 days, all rights in the Software granted to Licensee by License will be null and void as between Licensor and Licensee.
[7.2.](#7.2) _Failure of Notice_: If any person or entity notifies Licensee in writing that Licensee has not complied with the Notice section of this License, Licensee can keep this License by taking all practical steps to comply within 30 days after the notice of noncompliance. If Licensee does not do so, Licensees License (and all rights licensed hereunder) will end immediately.
[7.3.](#7.3) _Judicial Findings_: In the event Licensee is found by a civil, criminal, administrative, or other court of competent jurisdiction, or some other adjudicating body with legal authority, to have committed actions which are in violation of the Ethical Standards or Supply Chain Impacted Party sections of this License, all rights granted to Licensee by this License will terminate immediately.
[7.4.](#7.4) _Patent Litigation_: If Licensee institutes patent litigation against any entity (including a cross-claim or counterclaim in a suit) alleging that the Software, all or part of the Softwares code, or a derivative work developed using the Software, including a portion of its code, constitutes direct or contributory patent infringement, then any patent license, along with all other rights, granted to Licensee under this License will terminate as of the date such litigation is filed.
[7.5.](#7.5) _Additional Remedies_: Termination of the License by failing to remedy harms in no way prevents Licensor or Supply Chain Impacted Party from seeking appropriate remedies at law or in equity.
**[8.](#8) MISCELLANEOUS:**
[8.1.](#8.1) _Conditions_: Sections 3, 4.1, 5.1, 5.2, 7.1, 7.2, 7.3, and 7.4 are conditions of the rights granted to Licensee in the License.
[8.2.](#8.2) _Equitable Relief_: Licensor and any Supply Chain Impacted Party shall be entitled to equitable relief, including injunctive relief or specific performance of the terms hereof, in addition to any other remedy to which they are entitled at law or in equity.
[8.3.](#8.3) _Copyleft_: Modified software, source code, or other derivative work must be licensed, in its entirety, under the exact same conditions as this License.
[8.4.](#8.4) _Severability_: If any term or provision of this License is determined to be invalid, illegal, or unenforceable by a court of competent jurisdiction, any such determination of invalidity, illegality, or unenforceability shall not affect any other term or provision of this License or invalidate or render unenforceable such term or provision in any other jurisdiction. If the determination of invalidity, illegality, or unenforceability by a court of competent jurisdiction pertains to the terms or provisions contained in the Ethical Standards section of this License, all rights in the Software granted to Licensee shall be deemed null and void as between Licensor and Licensee.
[8.5.](#8.5) _Section Titles_: Section titles are solely written for organizational purposes and should not be used to interpret the language within each section.
[8.6.](#8.6) _Citations_: Citations are solely written to provide context for the source of the provisions in the Ethical Standards.
[8.7.](#8.7) _Section Summaries_: Some sections have a brief _italicized description_ which is provided for the sole purpose of briefly describing the section and should not be used to interpret the terms of the License.
[8.8.](#8.8) _Entire License_: This is the entire License between the Licensor and Licensee with respect to the claims released herein and that the consideration stated herein is the only consideration or compensation to be paid or exchanged between them for this License. This License cannot be modified or amended except in a writing signed by Licensor and Licensee.
[8.9.](#8.9) _Successors and Assigns_: This License shall be binding upon and inure to the benefit of the Licensors and Licensees respective heirs, successors, and assigns.

19
outrun-lexer/README.md Normal file
View file

@ -0,0 +1,19 @@
[![Hippocratic License HL3-FULL](https://img.shields.io/static/v1?label=Hippocratic%20License&message=HL3-FULL&labelColor=5e2751&color=bc8c3d)](https://firstdonoharm.dev/version/3/0/full.html)
[![Issues](https://img.shields.io/gitlab/issues/open/jimsy/outrun)](https://gitlab.com/jimsy/outrun/-/issues)
# 🌅 Outrun Lexer
This crate is the lexer for the Outrun programming language.
It is deliberately simple as we're still in the bootstrapping phase of development.
## Planned improvements:
* Hex, binary and octal integers
* Better float representations
* More thorough distaste for whitespace
# License
Outrun Lexer is distributed under the terms of the the Hippocratic Version 3.0 Full License.
See [LICENSE.md](https://gitlab.com/jimsy/outrun/-/blob/main/outrun-lexer/LICENSE.md) for details.

View file

@ -0,0 +1,28 @@
use unicode_general_category::{get_general_category, GeneralCategory};
/// Matches ascii digits only.
///
/// This is a bit dumb, but it works for now.
pub fn is_numeric(chr: char) -> bool {
matches!(chr, '0'..='9')
}
/// Matches whitespace.
///
/// Matches "space", "tab", "newline" and "carriage return". If those fail then
/// tries to match with the [Unicode character
/// categories](https://www.compart.com/en/unicode/category) "space separator",
/// "line separator" and "paragraph separator".
pub fn is_whitespace(chr: char) -> bool {
matches!(chr, ' ' | '\t' | '\n' | '\r')
|| matches!(
get_general_category(chr),
GeneralCategory::SpaceSeparator
| GeneralCategory::LineSeparator
| GeneralCategory::ParagraphSeparator
)
}
pub fn is_newline(chr: char) -> bool {
matches!(chr, '\n') || matches!(get_general_category(chr), GeneralCategory::LineSeparator)
}

View file

@ -1,20 +1,22 @@
//! Lexing errors.
use crate::span::Span;
/// An error that occurred while attempting to generate a token.
#[derive(Debug)]
pub enum Error {
UnexpectedEof {
span: Span,
},
Internal {
span: Span,
message: String,
},
/// We ran out of input unexpectedly.
UnexpectedEof { span: Span },
/// An internal error in the scanner.
Internal { span: Span, message: String },
/// An unexpected character in the input.
UnexpectedChar {
span: Span,
actual: Option<char>,
expected: Vec<char>,
},
/// A generic matching failure.
Unmatched,
/// The end of the input was reached.
Eof,
}

View file

@ -1,4 +1,13 @@
//! Outrun Lexer
//!
//! This crate contains the input scanner and the token types for the Outrun language.
#[cfg(test)]
extern crate indoc;
mod classes;
mod error;
mod pos;
mod scanner;
mod span;
mod token;

17
outrun-lexer/src/pos.rs Normal file
View file

@ -0,0 +1,17 @@
#[derive(Debug, PartialEq)]
pub struct Position {
pub line: usize,
pub column: usize,
}
impl Position {
pub fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
}
impl Default for Position {
fn default() -> Self {
Self::new(0, 0)
}
}

View file

@ -1,15 +0,0 @@
use unicode_general_category::{get_general_category, GeneralCategory};
pub fn is_numeric(chr: char) -> bool {
matches!(chr, '0'..='9')
}
pub fn is_whitespace(chr: char) -> bool {
matches!(chr, ' ' | '\t' | '\n' | '\r')
|| matches!(
get_general_category(chr),
GeneralCategory::SpaceSeparator
| GeneralCategory::LineSeparator
| GeneralCategory::ParagraphSeparator
)
}

View file

@ -2,6 +2,11 @@ use crate::error::Error;
use crate::span::Span;
use crate::Scanner;
/// Match a single character.
///
/// If `matcher` returns true for the next character in the input, then it returns a new
/// scanner and the span of the matched character.
/// If the match is unsuccessful, then `Error::Unmatched` is returned.
pub fn match_one<F>(scanner: Scanner<'_>, matcher: F) -> Result<(Scanner<'_>, Span), Error>
where
F: Fn(char) -> bool,
@ -15,6 +20,11 @@ where
Err(Error::Unmatched)
}
/// Optionally match many characters.
///
/// Returns a span representing a how many continuous characters matched.
/// Because this function is optional, it always returns `Ok` but the span may
/// be zero length.
pub fn match_many0<F>(scanner: Scanner<'_>, matcher: F) -> Result<(Scanner<'_>, Span), Error>
where
F: Fn(char) -> bool,
@ -32,6 +42,10 @@ where
Ok((new_scanner, (scanner.pos, new_scanner.pos).into()))
}
/// Match at least one character.
///
/// Returns a span representing a how many continuous characters matched.
/// If no characters are matched then returns `Error::Unmatched`.
pub fn match_many1<F>(scanner: Scanner<'_>, matcher: F) -> Result<(Scanner<'_>, Span), Error>
where
F: Fn(char) -> bool,
@ -41,6 +55,9 @@ where
Ok((scanner, head.extend(tail)))
}
/// Match a string literal with the input.
///
/// Returns a span containing the location of the literal, otherwise `Error::Unmatched`.
pub fn match_exact<'a>(scanner: Scanner<'a>, pattern: &str) -> Result<(Scanner<'a>, Span), Error> {
let mut pattern_chars = pattern.chars();
let mut new_scanner = scanner;

View file

@ -0,0 +1,30 @@
//! An iterator over tokens.
//!
//! You probably don't want to use this for anything other than debugging,
//! because it doesn't report sensible errors.
use crate::scanner::Scanner;
use crate::token::Token;
use std::iter::{IntoIterator, Iterator};
/// ScannerIterator
pub struct ScannerIterator<'a>(Scanner<'a>);
impl Iterator for ScannerIterator<'_> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
let (scanner, token) = self.0.next().ok()?;
self.0 = scanner;
Some(token)
}
}
impl<'a> IntoIterator for Scanner<'a> {
type Item = Token;
type IntoIter = ScannerIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
ScannerIterator(self)
}
}

View file

@ -1,7 +1,11 @@
//! The main Outrun scanner.
//!
//! The scanner performs character-wise matching to identify input into tokens.
use crate::error::Error;
use crate::token::{Token, TokenKind};
mod classes;
mod combinators;
mod iter;
mod rules;
use rules::*;
@ -9,6 +13,11 @@ use rules::*;
pub type ScanResult<'a> = std::result::Result<(Scanner<'a>, Token), Error>;
type Result<T> = std::result::Result<T, Error>;
/// Scanner
///
/// Each scanner contains a reference to the input stream, it's current position
/// within the stream and the maximum number of characters present in the input
/// (may be different to the number of bytes).
#[derive(Clone, Debug, PartialEq, Copy)]
pub struct Scanner<'a> {
input: &'a str,
@ -17,11 +26,18 @@ pub struct Scanner<'a> {
}
impl<'a> Scanner<'a> {
/// Create a new scanner with the given input.
pub fn new(input: &'a str) -> Self {
let len = input.chars().count();
Scanner { input, len, pos: 0 }
}
/// Try and get the next token.
///
/// On success it will return `Ok((scanner, token))` where scanner is a
/// _new_ scanner which has been advanced to the next unconsumed character,
/// and the matched token.
/// On failure it will return `Err(Error)`.
pub fn next(self) -> ScanResult<'a> {
self.eof()?;
let scanner = skip_whitespace(self)?;
@ -32,12 +48,13 @@ impl<'a> Scanner<'a> {
.or_else(|_| match_boolean(scanner))
.or_else(|_| match_string(scanner))
.or_else(|_| match_terminal(scanner, "use", TokenKind::KeywordUse))
.or_else(|_| match_terminal(scanner, "type", TokenKind::KeywordType))
.or_else(|_| match_terminal(scanner, "struct", TokenKind::KeywordStruct))
.or_else(|_| match_terminal(scanner, "protocol", TokenKind::KeywordProtocol))
.or_else(|_| match_terminal(scanner, "end", TokenKind::KeywordEnd))
.or_else(|_| match_terminal(scanner, "impl", TokenKind::KeywordImpl))
.or_else(|_| match_terminal(scanner, "defp", TokenKind::KeywordDefPrivate))
.or_else(|_| match_terminal(scanner, "def", TokenKind::KeywordDef))
.or_else(|_| match_terminal(scanner, "let", TokenKind::KeywordLet))
.or_else(|_| match_identifier(scanner))
.or_else(|_| match_terminal(scanner, "(", TokenKind::LeftParen))
.or_else(|_| match_terminal(scanner, ")", TokenKind::RightParen))
@ -47,6 +64,7 @@ impl<'a> Scanner<'a> {
.or_else(|_| match_terminal(scanner, "}", TokenKind::RightBrace))
.or_else(|_| match_terminal(scanner, "+", TokenKind::Plus))
.or_else(|_| match_terminal(scanner, "-", TokenKind::Minus))
.or_else(|_| match_terminal(scanner, "**", TokenKind::StarStar))
.or_else(|_| match_terminal(scanner, "*", TokenKind::Star))
.or_else(|_| match_terminal(scanner, "/", TokenKind::ForwardSlash))
.or_else(|_| match_terminal(scanner, "%", TokenKind::Percent))
@ -59,6 +77,7 @@ impl<'a> Scanner<'a> {
.or_else(|_| match_terminal(scanner, "<<", TokenKind::LtLt))
.or_else(|_| match_terminal(scanner, "<=", TokenKind::LtEq))
.or_else(|_| match_terminal(scanner, "<", TokenKind::Lt))
.or_else(|_| match_terminal(scanner, "=>", TokenKind::EqGt))
.or_else(|_| match_terminal(scanner, "==", TokenKind::EqEq))
.or_else(|_| match_terminal(scanner, "=", TokenKind::Eq))
.or_else(|_| match_terminal(scanner, "&&", TokenKind::AndAnd))
@ -67,6 +86,7 @@ impl<'a> Scanner<'a> {
.or_else(|_| match_terminal(scanner, "||", TokenKind::PipePipe))
.or_else(|_| match_terminal(scanner, "!=", TokenKind::BangEq))
.or_else(|_| match_terminal(scanner, "!", TokenKind::Bang))
.or_else(|_| match_terminal(scanner, "^", TokenKind::Hat))
.map_err(|error| match error {
Error::Unmatched => {
Error::unexpected(scanner.pos.into(), scanner.current_char(), Vec::new())
@ -75,10 +95,12 @@ impl<'a> Scanner<'a> {
})
}
/// Has the scanner advanced to the end of the input?
pub fn is_at_end(&self) -> bool {
self.pos >= self.len
}
/// Create a new scanner with it's position incremented by one.
pub fn advance(self) -> Self {
Scanner {
input: self.input,
@ -87,6 +109,7 @@ impl<'a> Scanner<'a> {
}
}
/// Return the remaining unscanned input.
pub fn remaining(&self) -> &str {
if self.pos < self.len {
let (index, _) = self.input.char_indices().nth(self.pos).unwrap();
@ -96,6 +119,7 @@ impl<'a> Scanner<'a> {
}
}
/// A wrapper around `is_at_end` that returns a result.
pub fn eof(&self) -> Result<()> {
if self.is_at_end() {
Err(Error::Eof)
@ -104,14 +128,12 @@ impl<'a> Scanner<'a> {
}
}
pub fn next_char(&self) -> Option<char> {
self.input.chars().nth(self.pos + 1)
}
/// Try and return the character at the current scanner position.
pub fn current_char(&self) -> Option<char> {
self.input.chars().nth(self.pos)
}
/// A wrapper around `current_char` that returns a Result.
pub fn current_char_or_error(&self) -> Result<char> {
self.input.chars().nth(self.pos).ok_or_else(|| {
Error::internal(

View file

@ -1,5 +1,5 @@
use crate::classes::*;
use crate::error::Error;
use crate::scanner::classes::*;
use crate::scanner::combinators::*;
use crate::scanner::{ScanResult, Scanner};
use crate::span::Span;
@ -8,11 +8,13 @@ use crate::token::{Token, TokenKind, TokenValue};
use unicode_ident::{is_xid_continue, is_xid_start};
use unicode_normalization::UnicodeNormalization;
/// Advances the scanner past any whitespace.
pub fn skip_whitespace(scanner: Scanner<'_>) -> Result<Scanner<'_>, Error> {
let (scanner, _) = match_many0(scanner, is_whitespace)?;
Ok(scanner)
}
/// Attempts to match and return an integer token.
pub fn match_integer(scanner: Scanner<'_>) -> ScanResult<'_> {
let (scanner, span) = match_many1(scanner, is_numeric)?;
let value = span
@ -28,6 +30,7 @@ pub fn match_integer(scanner: Scanner<'_>) -> ScanResult<'_> {
Ok((scanner, token))
}
/// Attempts to match and return a float token.
pub fn match_float(scanner: Scanner<'_>) -> ScanResult<'_> {
let (scanner, head) = match_many1(scanner, is_numeric)?;
let (scanner, _) = match_one(scanner, |c| c == '.')?;
@ -47,11 +50,13 @@ pub fn match_float(scanner: Scanner<'_>) -> ScanResult<'_> {
Ok((scanner, token))
}
/// Attempts to match a specific string and return a token of `kind`.
pub fn match_terminal<'a>(scanner: Scanner<'a>, pattern: &str, kind: TokenKind) -> ScanResult<'a> {
match_exact(scanner, pattern)
.map(|(scanner, span)| (scanner, Token::new(kind, TokenValue::None, span)))
}
/// Attempts to match literal `"true"` and `"false"` and returns a token.
pub fn match_boolean(scanner: Scanner<'_>) -> ScanResult<'_> {
match_exact(scanner, "true")
.map(|(scanner, span)| {
@ -70,6 +75,12 @@ pub fn match_boolean(scanner: Scanner<'_>) -> ScanResult<'_> {
})
}
/// Attempts to match a string literal.
///
/// At the moment this is a bit dumb because it matches anything between two `"`
/// symbols. The only escaping provided is `\"` to escape quotes.
///
/// This needs to be a lot better before prime time.
pub fn match_string(scanner: Scanner<'_>) -> ScanResult<'_> {
let (scanner, begin) = match_exact(scanner, r#"""#)?;
@ -107,6 +118,10 @@ pub fn match_string(scanner: Scanner<'_>) -> ScanResult<'_> {
))
}
/// Uses [UAX#31](https://unicode.org/reports/tr31/) to match identifiers in the
/// input string.
///
/// Normalises identifiers with normalization form "C".
pub fn match_identifier(scanner: Scanner<'_>) -> ScanResult<'_> {
let (scanner, start) = match_one(scanner, is_xid_start)?;
let (scanner, end) = match_many0(scanner, is_xid_continue)?;
@ -120,7 +135,7 @@ pub fn match_identifier(scanner: Scanner<'_>) -> ScanResult<'_> {
Ok((
scanner,
Token::new(TokenKind::Identifier, TokenValue::Identifier(value), span),
Token::new(TokenKind::Identifier, TokenValue::String(value), span),
))
}
@ -228,10 +243,7 @@ mod test {
let scanner = Scanner::new("DarkAllDay");
let (_, token) = match_identifier(scanner).unwrap();
assert_eq!(token.kind, TokenKind::Identifier);
assert_eq!(
token.value,
TokenValue::Identifier("DarkAllDay".to_string())
);
assert_eq!(token.value, TokenValue::String("DarkAllDay".to_string()));
assert_eq!(token.span, Span::new(0, 10));
}
@ -240,10 +252,7 @@ mod test {
let scanner = Scanner::new("dark_all_day");
let (_, token) = match_identifier(scanner).unwrap();
assert_eq!(token.kind, TokenKind::Identifier);
assert_eq!(
token.value,
TokenValue::Identifier("dark_all_day".to_string())
);
assert_eq!(token.value, TokenValue::String("dark_all_day".to_string()));
assert_eq!(token.span, Span::new(0, 12));
}
@ -252,7 +261,7 @@ mod test {
let scanner = Scanner::new("");
let (_, token) = match_identifier(scanner).unwrap();
assert_eq!(token.kind, TokenKind::Identifier);
assert_eq!(token.value, TokenValue::Identifier("".to_string()));
assert_eq!(token.value, TokenValue::String("".to_string()));
assert_eq!(token.span, Span::new(0, 1));
}
}

View file

@ -1,10 +1,26 @@
//! Span
//!
//! A span represents a substring based on the number of *characters* offset
//! from the start of the input string.
//! It is key to keeping track of the location of tokens in the input stream.
use crate::classes::is_newline;
use crate::pos::Position;
use crate::token::Token;
/// A span.
#[derive(Clone, Debug, PartialEq, Copy)]
pub struct Span {
/// The index into the input of the start of the span.
pub start: usize,
/// The index into the input of the end of the span.
pub end: usize,
}
impl Span {
/// Create a new "span" which represents a single point in the input stream.
///
/// ie it has zero length.
pub fn at(pos: usize) -> Span {
Span {
start: pos,
@ -12,10 +28,27 @@ impl Span {
}
}
/// Create a new span with a starting position and an end position.
pub fn new(start: usize, end: usize) -> Span {
Span { start, end }
let min = start.min(end);
let max = start.max(end);
Span {
start: min,
end: max,
}
}
/// Given a string slice, return a substring based on the character indexes
/// of the input.
///
/// # Examples
///
/// ```rust
/// use outrun_lexer::Span;
/// let input = "My Tears Are Becoming a Sea";
/// let span = Span::new(4, 8);
/// assert_eq!(span.extract(input), Some("ears"));
/// ```
pub fn extract<'a>(&'a self, input: &'a str) -> Option<&'a str> {
let len = self.end - self.start;
@ -45,11 +78,65 @@ impl Span {
.or_else(|| input.get(start..))
}
/// Given two spans, create a new span which covers the entire range.
///
/// Note that this includes any gaps between spans.
///
/// # Examples
///
/// ```
/// use outrun_lexer::Span;
/// // A new span between two spans.
/// let span0 = Span::new(1, 3);
/// let span1 = Span::new(4, 5);
/// let span = span0.extend(span1);
/// assert_eq!(span.start, 1);
/// assert_eq!(span.end, 5);
/// ```
pub fn extend(self, other: Span) -> Span {
let start = self.start.min(other.start);
let end = self.end.max(other.end);
Span { start, end }
}
/// Return the length of the span
pub fn len(&self) -> usize {
self.end - self.start
}
/// Returns `true` if the span has zero length
pub fn is_empty(&self) -> bool {
self.end - self.start == 0
}
/// Return the line and column numbers of the span as applied to `input`.
pub fn positions(&self, input: &str) -> Option<(Position, Position)> {
let mut start = None;
let mut end = None;
let mut chars = input.chars();
let mut line = 1;
let mut col = 1;
for (count, chr) in (&mut chars).enumerate() {
if count == self.start {
start = Some(Position::new(line, col));
}
if count == self.end {
end = Some(Position::new(line, col));
}
if is_newline(chr) {
line += 1;
col = 1;
} else {
col += 1;
}
}
match (start, end) {
(Some(start), Some(end)) => Some((start, end)),
_ => None,
}
}
}
impl From<usize> for Span {
@ -61,6 +148,18 @@ impl From<usize> for Span {
}
}
impl From<Token> for Span {
fn from(token: Token) -> Span {
token.span
}
}
impl<T: Into<Span>> From<&T> for Span {
fn from(span: &T) -> Self {
span.into()
}
}
impl From<(usize, usize)> for Span {
fn from(span: (usize, usize)) -> Span {
Span {
@ -73,6 +172,7 @@ impl From<(usize, usize)> for Span {
#[cfg(test)]
mod test {
use super::*;
use indoc::indoc;
#[test]
fn span_at() {
@ -117,4 +217,30 @@ mod test {
assert_eq!(&s[3..4], "d");
assert_eq!(&s[3..=4], "de");
}
#[test]
fn test_positions() {
let input = indoc! {"
I'm slowly drifting to
You
The stars and the planets
Are calling me
A billion years away from you
I'm on my way
I'm on...
I'm on...
"};
let span = Span::new(31, 52);
// Sanity check.
assert_eq!(span.extract(input).unwrap(), "stars and the planets");
let (start, end) = span.positions(input).unwrap();
assert_eq!(start.line, 3);
assert_eq!(start.column, 5);
assert_eq!(end.line, 3);
assert_eq!(end.column, 26);
}
}

View file

@ -1,6 +1,13 @@
//! Token
//!
//! This module contains the main Token structures and enums.
use crate::span::Span;
#[derive(Clone, Debug, PartialEq, Copy)]
/// TokenKind
///
/// An enum of possible token types. Used as a tag to ease matching.
#[derive(Clone, Debug, PartialEq, Copy, Eq, Hash)]
pub enum TokenKind {
// Literals
Boolean,
@ -19,10 +26,12 @@ pub enum TokenKind {
Dot,
Eq,
EqEq,
EqGt,
ForwardSlash,
Gt,
GtEq,
GtGt,
Hat,
LeftBrace,
LeftParen,
LeftSquare,
@ -38,20 +47,26 @@ pub enum TokenKind {
RightParen,
RightSquare,
Star,
StarStar,
// Keywords
KeywordDef,
KeywordDefPrivate,
KeywordFn,
KeywordEnd,
KeywordImpl,
KeywordLet,
KeywordProtocol,
KeywordType,
KeywordStruct,
KeywordUse,
}
/// TokenValue
///
/// Contains any value associated with the token. For example a Token with kind
/// `TokenKind::Integer` will contain a `TokenValue::Integer(n)` value.
#[derive(Clone, Debug, PartialEq)]
pub enum TokenValue {
Identifier(String),
Integer(i64),
Float(f64),
Boolean(bool),
@ -59,6 +74,51 @@ pub enum TokenValue {
None,
}
impl TokenValue {
/// If the TokenValue is `String` then returns an `&str`.
pub fn as_str(&self) -> Option<&str> {
match self {
TokenValue::String(value) => Some(value),
_ => None,
}
}
/// If the TokenValue is `String` then returns a `String`.
pub fn as_string(&self) -> Option<String> {
match self {
TokenValue::String(value) => Some(value.to_string()),
_ => None,
}
}
/// If the TokenValue is `Integer` then returns an `i64`.
pub fn as_int(&self) -> Option<i64> {
match self {
TokenValue::Integer(value) => Some(*value),
_ => None,
}
}
/// If the TokenValue is `Float` then returns an `f64`.
pub fn as_float(&self) -> Option<f64> {
match self {
TokenValue::Float(value) => Some(*value),
_ => None,
}
}
/// If the TokenValue is `Boolean` then returns a ``bool`.
pub fn as_bool(&self) -> Option<bool> {
match self {
TokenValue::Boolean(value) => Some(*value),
_ => None,
}
}
}
/// Token
///
/// Contains a `TokenKind`, a `TokenValue` and a `Span`.
#[derive(Clone, Debug, PartialEq)]
pub struct Token {
pub kind: TokenKind,
@ -67,6 +127,7 @@ pub struct Token {
}
impl Token {
/// Create a new token.
pub fn new(kind: TokenKind, value: TokenValue, span: Span) -> Token {
Token { kind, value, span }
}

View file

@ -2,11 +2,13 @@
authors = ["James Harton <james@harton.nz>"]
description = "🌅 A parser for the most retro-futuristic toy language in the world."
edition = "2021"
license_file = "../LICENSE.md"
license-file = "../LICENSE.md"
name = "outrun-parser"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
assert_matches = "1.5.0"
lazy_static = "1.4.0"
outrun-lexer = {path = "../outrun-lexer"}

20
outrun-parser/README.md Normal file
View file

@ -0,0 +1,20 @@
[![Hippocratic License HL3-FULL](https://img.shields.io/static/v1?label=Hippocratic%20License&message=HL3-FULL&labelColor=5e2751&color=bc8c3d)](https://firstdonoharm.dev/version/3/0/full.html)
[![Issues](https://img.shields.io/gitlab/issues/open/jimsy/outrun)](https://gitlab.com/jimsy/outrun/-/issues)
# 🌅 Outrun Parser
This crate is the parser for the Outrun programming language.
This parser is deliberately designed to accept invalid Outrun code, in an effort
to make the language tooling more helpful.
## Planned improvements
* Syntax for anonymous functions
* Definition of enums
# License
Outrun Parser is distributed under the terms of the the Hippocratic Version 3.0 Full License.
See [LICENSE.md](https://gitlab.com/jimsy/outrun/-/blob/main/outrun-parser/LICENSE.md) for details.

View file

@ -1,18 +1,31 @@
//! Parsing errors.
use outrun_lexer::TokenKind;
/// An error that occurred while parsing input tokens.
#[derive(Debug, PartialEq)]
pub enum Error {
/// The end of the input was reached.
Eoi,
/// A generic "unmatched" which is used in the parser combinators.
Unmatched,
/// An unexpected token was encountered.
Unexpected {
actual: TokenKind,
expected: Vec<TokenKind>,
},
/// The end of the input was reached - but was not expected.
UnexpectedEoi,
/// An internal error occurred within the parser.
Internal(String),
}
impl Error {
pub fn unexpected(actual: TokenKind, expected: Vec<TokenKind>) -> Error {
Error::Unexpected { actual, expected }
}
pub fn internal<T: ToString>(message: T) -> Error {
Error::Internal(message.to_string())
}
}

View file

@ -1,8 +1,19 @@
//! Outrun Parser
//!
//! This crate contains the parser and node types for the Outrun language.
#[macro_use]
extern crate lazy_static;
extern crate outrun_lexer;
#[cfg(test)]
#[macro_use]
extern crate assert_matches;
mod error;
mod node;
mod parser;
pub use node::Node;
pub use node::{Node, NodeKind, NodeValue};
pub use parser::{ParseResult, Parser};

View file

@ -1,6 +1,109 @@
use outrun_lexer::Token;
//! Node
//!
//! Structures and enums related to the parsed abstract syntax tree.
#[derive(Debug, PartialEq)]
pub enum Node {
Constant(Token),
use outrun_lexer::{Span, TokenKind};
/// The kind of node.
#[derive(Debug, PartialEq, Clone)]
pub enum NodeKind {
Atom,
Block,
Boolean,
Call,
Constant,
Float,
GetLocal,
Infix,
Integer,
KeywordPair,
List,
Map,
Operator,
Prefix,
SetLocal,
Definition,
String,
Module,
}
/// Any possible value of the node (including potential child nodes).
#[derive(Debug, PartialEq, Clone)]
pub enum NodeValue {
Collection(Vec<Node>),
Boolean(bool),
Call {
receiver: Box<Node>,
arguments: Vec<Node>,
},
Constant(Vec<String>),
Float(f64),
Infix(Box<Node>, TokenKind, Box<Node>),
Integer(i64),
KeywordPair(Box<Node>, Box<Node>),
Operator(TokenKind),
Prefix(TokenKind, Box<Node>),
SetLocal(String, Box<Node>),
String(String),
Definition {
kind: TokenKind,
name: Box<Node>,
fields: Vec<Node>,
arguments: Vec<Node>,
},
}
impl NodeValue {
/// If the `NodeValue` is a string, returns an `&str`.
pub fn as_str(&self) -> Option<&str> {
match self {
NodeValue::String(s) => Some(s),
_ => None,
}
}
/// If the `NodeValue` is a string, returns an `String`.
pub fn as_string(&self) -> Option<String> {
match self {
NodeValue::String(s) => Some(s.to_string()),
_ => None,
}
}
/// If the `NodeValue` is a constant, returns an `&[String]`.
pub fn as_strings(&self) -> Option<&[String]> {
match self {
NodeValue::Constant(strings) => Some(strings),
_ => None,
}
}
/// If the NodeValue is an operator, return it.
pub fn operator(&self) -> Option<TokenKind> {
match self {
NodeValue::Operator(s) => Some(*s),
_ => None,
}
}
}
/// An individual node of the abstract syntax tree.
#[derive(Debug, PartialEq, Clone)]
pub struct Node {
pub kind: NodeKind,
pub value: NodeValue,
pub span: Span,
}
impl Node {
/// Create a new node.
pub fn new(kind: NodeKind, value: NodeValue, span: Span) -> Self {
Self { kind, value, span }
}
}
impl From<Node> for Span {
fn from(node: Node) -> Self {
node.span
}
}

View file

@ -1,93 +1,136 @@
use crate::error::Error;
//! Combinators
//!
//! Coalesces matchers and results into output.
use crate::parser::matcher::Matcher;
use crate::parser::{Parser, Result};
use outrun_lexer::{Token, TokenKind};
pub fn match_one(parser: Parser<'_>, token_kind: TokenKind) -> Result<(Parser, Token)> {
let token = parser.current_token().ok_or(Error::UnexpectedEoi)?;
if token.kind == token_kind {
let parser = parser.advance();
Ok((parser, token))
} else {
Err(Error::unexpected(token.kind, vec![token_kind]))
}
/// Match exactly once
pub fn match_one<T: Matcher>(parser: Parser<'_>, matcher: T) -> Result<(Parser, T::Output)> {
matcher.try_match(parser)
}
pub fn match_many0(parser: Parser<'_>, token_kind: TokenKind) -> Result<(Parser, Vec<Token>)> {
/// Match zero or more times
pub fn match_many0<T: Matcher>(parser: Parser<'_>, matcher: T) -> Result<(Parser, Vec<T::Output>)> {
let mut new_parser = parser;
let mut tokens = Vec::new();
let mut results = Vec::new();
while let Some(token) = new_parser.current_token() {
if token.kind == token_kind {
tokens.push(token);
new_parser = new_parser.advance();
} else {
break;
}
}
Ok((new_parser, tokens))
}
pub fn match_many1(parser: Parser<'_>, token_kind: TokenKind) -> Result<(Parser, Vec<Token>)> {
let (parser, head) = match_one(parser, token_kind)?;
let mut tokens = vec![head];
let (parser, tail) = match_many0(parser, token_kind)?;
tokens.extend(tail);
Ok((parser, tokens))
}
pub fn match_many0_with_sep(
parser: Parser<'_>,
value_kind: TokenKind,
sep_kind: TokenKind,
) -> Result<(Parser<'_>, Vec<Token>)> {
let mut new_parser = parser;
let mut tokens = Vec::new();
while let Ok((parser, token)) = match_one(new_parser, value_kind) {
while let Ok((parser, result)) = matcher.try_match(new_parser) {
results.push(result);
new_parser = parser;
tokens.push(token);
}
match match_one(new_parser, sep_kind) {
Ok((new_parser, results))
}
/// Match one or more times
pub fn match_many1<T: Matcher + Copy>(
parser: Parser<'_>,
matcher: T,
) -> Result<(Parser, Vec<T::Output>)> {
let (parser, head) = match_one(parser, matcher)?;
let mut results = vec![head];
let (parser, tail) = match_many0(parser, matcher)?;
results.extend(tail);
Ok((parser, results))
}
/// Match zero or more times with interleaved separator patterns.
///
/// Separator results are discarded.
pub fn match_many0_with_sep<T: Matcher + Copy, U: Matcher + Copy>(
parser: Parser<'_>,
value_matcher: T,
sep_matcher: U,
) -> Result<(Parser<'_>, Vec<T::Output>)> {
let mut new_parser = parser;
let mut results = Vec::new();
while let Ok((parser, result)) = match_one(new_parser, value_matcher) {
new_parser = parser;
results.push(result);
match match_one(new_parser, sep_matcher) {
Err(_) => break,
Ok((parser, _)) => new_parser = parser,
}
}
Ok((new_parser, tokens))
Ok((new_parser, results))
}
pub fn match_many1_with_sep(
/// Match one of more times with interleaved separator patterns.
///
/// Separator results are discarded.
pub fn match_many1_with_sep<T: Matcher + Copy, U: Matcher + Copy>(
parser: Parser<'_>,
value_kind: TokenKind,
sep_kind: TokenKind,
) -> Result<(Parser<'_>, Vec<Token>)> {
let (parser, head) = match_one(parser, value_kind)?;
let mut tokens = vec![head];
value_matcher: T,
sep_matcher: U,
) -> Result<(Parser<'_>, Vec<T::Output>)> {
let (parser, head) = match_one(parser, value_matcher)?;
let mut results = vec![head];
let mut new_parser = parser;
if let Ok((parser, _)) = match_one(new_parser, sep_kind) {
let (parser, tail) = match_many0_with_sep(parser, value_kind, sep_kind)?;
if let Ok((parser, _)) = match_one(new_parser, sep_matcher) {
let (parser, tail) = match_many0_with_sep(parser, value_matcher, sep_matcher)?;
new_parser = parser;
tokens.extend(tail);
results.extend(tail);
}
Ok((new_parser, tokens))
Ok((new_parser, results))
}
/// Match a single value which is surrounded by two other matches.
///
/// The preceding and succeeding values are discarded.
pub fn surrounded<T: Matcher + Copy, U: Matcher + Copy, V: Matcher + Copy>(
parser: Parser<'_>,
value_matcher: T,
preceding_matcher: U,
succeeding_matcher: V,
) -> Result<(Parser<'_>, T::Output)> {
let (parser, _) = match_one(parser, preceding_matcher)?;
let (parser, result) = match_one(parser, value_matcher)?;
let (parser, _) = match_one(parser, succeeding_matcher)?;
Ok((parser, result))
}
/// Match a single value which is preceded by another match.
///
/// The preceding value is discarded.
pub fn preceded<T: Matcher + Copy, U: Matcher + Copy>(
parser: Parser<'_>,
value_matcher: T,
preceding_matcher: U,
) -> Result<(Parser<'_>, T::Output)> {
let (parser, _) = match_one(parser, preceding_matcher)?;
match_one(parser, value_matcher)
}
/// Match a single value which is followed by another match.
///
/// The following value is discarded.
pub fn succeeded<T: Matcher + Copy, U: Matcher + Copy>(
parser: Parser<'_>,
value_matcher: T,
succeeding_matcher: U,
) -> Result<(Parser<'_>, T::Output)> {
let (parser, result) = match_one(parser, value_matcher)?;
let (parser, _) = match_one(parser, succeeding_matcher)?;
Ok((parser, result))
}
#[cfg(test)]
mod test {
use super::*;
use outrun_lexer::TokenValue;
use crate::error::Error;
use outrun_lexer::{Token, TokenKind, TokenValue};
fn assert_unexpected(result: Error, got: TokenKind, wanted: Vec<TokenKind>) {
match result {
Error::Unexpected { actual, expected } => {
assert_eq!(actual, got);
assert_eq!(expected, wanted);
}
other => assert!(false, "Unexpected error: {:?}", other),
}
assert_matches!(result, Error::Unexpected { actual, expected } => {
assert_eq!(actual, got);
assert_eq!(expected, wanted);
});
}
#[test]

View file

@ -0,0 +1,44 @@
//! Matcher
//!
//! A trait used to provide generic matching functionality for the parser.
use crate::error::Error;
use crate::parser::{Parser, Result};
use outrun_lexer::{Token, TokenKind};
/// The `Matcher` trait
///
/// Anything that implements this trait can be passed into the functions in the
/// parser combinators module.
pub trait Matcher {
type Output;
/// Attempt to match the Parser's current state and return a potentially new
/// parser and some.
fn try_match<'a>(&self, parser: Parser<'a>) -> Result<(Parser<'a>, Self::Output)>;
}
impl Matcher for TokenKind {
type Output = Token;
fn try_match<'a>(&self, parser: Parser<'a>) -> Result<(Parser<'a>, Token)> {
let token = parser.current_token().ok_or(Error::UnexpectedEoi)?;
if token.kind == *self {
let parser = parser.advance();
Ok((parser, token))
} else {
Err(Error::unexpected(token.kind, vec![*self]))
}
}
}
impl<T, F> Matcher for F
where
F: Fn(Parser<'_>) -> Result<(Parser<'_>, T)>,
{
type Output = T;
fn try_match<'a>(&self, parser: Parser<'a>) -> Result<(Parser<'a>, T)> {
self(parser)
}
}

View file

@ -1,12 +1,21 @@
//! The main Outrun parser.
//!
//! The parser performs token-wise matching to convert input into AST nodes.
mod combinators;
mod matcher;
mod precedence;
mod rules;
use crate::error::Error;
use crate::node::Node;
use outrun_lexer::Token;
use rules::*;
/// Parser
///
/// The parser works very similarly to the scanner. It contains a reference to
/// the input tokens, it's current position within the stream and the total
/// number of tokens present.
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Parser<'a> {
input: &'a [Token],
@ -18,7 +27,8 @@ type Result<T> = std::result::Result<T, Error>;
pub type ParseResult<'a> = std::result::Result<(Parser<'a>, Node), Error>;
impl<'a> Parser<'a> {
fn new(input: &'a [Token]) -> Self {
/// Create a new parser with a reference to the input tokens.
pub fn new(input: &'a [Token]) -> Self {
Self {
input,
pos: 0,
@ -26,12 +36,21 @@ impl<'a> Parser<'a> {
}
}
pub fn next(self) -> ParseResult<'a> {
/// Parse a whole module in one go.
pub fn parse_module(self) -> ParseResult<'a> {
self.eoi()?;
match_constant(self)
rules::module(self)
}
/// Parse a single expression.
pub fn parse_expr(self) -> ParseResult<'a> {
self.eoi()?;
rules::expr(self)
}
/// Returns a new parser with the the position incremented by one.
pub fn advance(self) -> Self {
Self {
input: self.input,
@ -40,6 +59,7 @@ impl<'a> Parser<'a> {
}
}
/// Return the current token.
fn current_token(&self) -> Option<Token> {
self.input.get(self.pos).cloned()
}

View file

@ -0,0 +1,57 @@
use crate::node::{Node, NodeKind, NodeValue};
use outrun_lexer::TokenKind;
use std::collections::HashMap;
lazy_static! {
static ref PRECEDENCE: HashMap<TokenKind, (usize, Associativity)> = {
let mut table = HashMap::new();
table.insert(TokenKind::PipePipe, (1, Associativity::Left));
table.insert(TokenKind::AndAnd, (2, Associativity::Left));
table.insert(TokenKind::EqEq, (3, Associativity::Right));
table.insert(TokenKind::Lt, (4, Associativity::Left));
table.insert(TokenKind::LtEq, (4, Associativity::Left));
table.insert(TokenKind::Gt, (4, Associativity::Left));
table.insert(TokenKind::GtEq, (4, Associativity::Left));
table.insert(TokenKind::Hat, (5, Associativity::Left));
table.insert(TokenKind::Pipe, (5, Associativity::Left));
table.insert(TokenKind::And, (6, Associativity::Left));
table.insert(TokenKind::LtLt, (7, Associativity::Left));
table.insert(TokenKind::GtGt, (7, Associativity::Left));
table.insert(TokenKind::Plus, (8, Associativity::Left));
table.insert(TokenKind::Minus, (8, Associativity::Left));
table.insert(TokenKind::Star, (9, Associativity::Left));
table.insert(TokenKind::ForwardSlash, (9, Associativity::Left));
table.insert(TokenKind::Percent, (9, Associativity::Left));
table.insert(TokenKind::StarStar, (10, Associativity::Right));
table.insert(TokenKind::Dot, (11, Associativity::Left));
table
};
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Copy)]
enum Associativity {
Right = 0,
Left = 1,
}
pub fn climb(nodes: &mut Vec<Node>, min_precedence: usize) -> Node {
let mut lhs = nodes.pop().unwrap();
while let Some(operator) = nodes.pop() {
let kind = operator.value.operator().unwrap();
let (precedence, associativity) = PRECEDENCE.get(&kind).unwrap();
if *precedence < min_precedence {
break;
}
let rhs = climb(nodes, min_precedence + *associativity as usize);
let span = lhs.span.extend(rhs.span);
lhs = Node::new(
NodeKind::Infix,
NodeValue::Infix(Box::new(lhs), kind, Box::new(rhs)),
span,
);
}
lhs
}

View file

@ -1,32 +1,870 @@
//! Outrun parser rules.
use crate::error::Error;
use crate::parser::{ParseResult, Parser};
use outrun_lexer::TokenKind;
use crate::node::Node;
use crate::node::{Node, NodeKind, NodeValue};
use crate::parser::combinators::*;
use crate::parser::precedence;
use crate::parser::{ParseResult, Parser, Result};
use outrun_lexer::{Span, Token, TokenKind};
pub fn match_constant(parser: Parser<'_>) -> ParseResult<'_> {
match_one(parser, TokenKind::Identifier).map(|(parser, token)| (parser, Node::Constant(token)))
/// Match a single expression.
pub fn expr(parser: Parser<'_>) -> ParseResult<'_> {
expr_infix(parser).or_else(|_| expr_inner(parser))
}
/// Match an entire module (ie at least one statement).
pub fn module(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, statements) = match_many1(parser, stmt)?;
let span = span(parser, &statements);
Ok((
parser,
Node::new(NodeKind::Module, NodeValue::Collection(statements), span),
))
}
fn expr_inner(parser: Parser<'_>) -> ParseResult<'_> {
literal(parser)
.or_else(|_| map_literal(parser))
.or_else(|_| list_literal(parser))
.or_else(|_| expr_call(parser))
.or_else(|_| expr_braced(parser))
.or_else(|_| expr_prefix(parser))
.or_else(|_| stmt_let(parser))
}
fn expr_prefix(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, operator) = match_one(parser, prefix_op)?;
let (parser, operand) = match_one(parser, expr)?;
let span = operator.span.extend(operand.span);
Ok((
parser,
Node::new(
NodeKind::Prefix,
NodeValue::Prefix(operator.kind, Box::new(operand)),
span,
),
))
}
fn expr_braced(parser: Parser<'_>) -> ParseResult<'_> {
surrounded(parser, expr, TokenKind::LeftParen, TokenKind::RightParen)
}
fn expr_call_args(parser: Parser<'_>) -> Result<(Parser<'_>, Vec<Node>)> {
match_many0_with_sep(parser, expr, TokenKind::Comma)
}
fn expr_call(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, receiver) = match_one(parser, TokenKind::Identifier)?;
let (parser, arguments) = surrounded(
parser,
expr_call_args,
TokenKind::LeftParen,
TokenKind::RightParen,
)?;
let receiver = Box::new(Node::new(
NodeKind::GetLocal,
NodeValue::String(receiver.value.as_string().unwrap()),
receiver.span,
));
let span = span(parser, &arguments).extend(receiver.span);
Ok((
parser,
Node::new(
NodeKind::Call,
NodeValue::Call {
receiver,
arguments,
},
span,
),
))
}
fn infix_op_followed_by_expr(parser: Parser<'_>) -> Result<(Parser<'_>, (Node, Node))> {
let (parser, operator) = infix_op(parser)?;
let (parser, operand) = expr_inner(parser)?;
Ok((parser, (operator, operand)))
}
fn expr_infix(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, lhs) = expr_inner(parser)?;
let (parser, (operator, rhs)) = infix_op_followed_by_expr(parser)?;
let mut exprs = vec![lhs, operator, rhs];
let mut new_parser = parser;
while let Ok((parser, (operator, operand))) = infix_op_followed_by_expr(new_parser) {
exprs.push(operator);
exprs.push(operand);
new_parser = parser;
}
let mut reversed_exprs = exprs.into_iter().rev().collect::<Vec<Node>>();
Ok((new_parser, precedence::climb(&mut reversed_exprs, 0)))
}
fn expr_constant(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, tokens) = match_many1_with_sep(parser, TokenKind::Identifier, TokenKind::Dot)?;
let names = tokens
.iter()
.map(|t| t.value.as_string().unwrap())
.collect::<Vec<String>>();
let first = tokens.first().unwrap();
let last = tokens.last().unwrap();
Ok((
parser,
Node::new(
NodeKind::Constant,
NodeValue::Constant(names),
first.span.extend(last.span),
),
))
}
fn expr_boolean(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, token) = match_one(parser, TokenKind::Boolean)?;
Ok((
parser,
Node::new(
NodeKind::Boolean,
NodeValue::Boolean(token.value.as_bool().unwrap()),
token.span,
),
))
}
fn expr_integer(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, token) = match_one(parser, TokenKind::Integer)?;
Ok((
parser,
Node::new(
NodeKind::Integer,
NodeValue::Integer(token.value.as_int().unwrap()),
token.span,
),
))
}
fn expr_float(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, token) = match_one(parser, TokenKind::Float)?;
Ok((
parser,
Node::new(
NodeKind::Float,
NodeValue::Float(token.value.as_float().unwrap()),
token.span,
),
))
}
fn expr_atom(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, token) = preceded(parser, TokenKind::Identifier, TokenKind::Colon)?;
let atom = token_to_atom(&token)?;
Ok((parser, atom))
}
fn stmt(parser: Parser<'_>) -> ParseResult<'_> {
stmt_use(parser)
.or_else(|_| stmt_struct(parser))
.or_else(|_| stmt_protocol(parser))
.or_else(|_| stmt_def(parser))
.or_else(|_| stmt_defp(parser))
}
fn stmt_let(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, token_let) = match_one(parser, TokenKind::KeywordLet)?;
let (parser, token_name) = match_one(parser, TokenKind::Identifier)?;
let (parser, _) = match_one(parser, TokenKind::Eq)?;
let (parser, value) = match_one(parser, expr)?;
let span = token_let.span.extend(value.span);
Ok((
parser,
Node::new(
NodeKind::SetLocal,
NodeValue::SetLocal(token_name.value.as_string().unwrap(), Box::new(value)),
span,
),
))
}
fn stmt_definition<F: Fn(Parser<'_>) -> ParseResult<'_>>(
parser: Parser<'_>,
keyword: TokenKind,
name_matcher: F,
) -> ParseResult<'_> {
let (parser, token) = match_one(parser, keyword)?;
let (parser, name) = name_matcher(parser)?;
let (parser, fields) = surrounded(
parser,
keyword_args,
TokenKind::LeftParen,
TokenKind::RightParen,
)
.or_else(|_| Ok((parser, vec![])))?;
let (parser, arguments) =
preceded(parser, keyword_args, TokenKind::Comma).or_else(|_| Ok((parser, vec![])))?;
let last_span = arguments
.last()
.or_else(|| fields.last())
.or(Some(&name))
.map(|node| node.span)
.unwrap();
Ok((
parser,
Node::new(
NodeKind::Definition,
NodeValue::Definition {
kind: keyword,
name: Box::new(name),
fields,
arguments,
},
token.span.extend(last_span),
),
))
}
fn stmt_struct(parser: Parser<'_>) -> ParseResult<'_> {
stmt_definition(parser, TokenKind::KeywordStruct, expr_constant)
}
fn stmt_protocol(parser: Parser<'_>) -> ParseResult<'_> {
stmt_definition(parser, TokenKind::KeywordProtocol, expr_constant)
}
fn identifier(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, ident) = match_one(parser, TokenKind::Identifier)?;
let node = token_to_atom(&ident)?;
Ok((parser, node))
}
fn stmt_def(parser: Parser<'_>) -> ParseResult<'_> {
stmt_definition(parser, TokenKind::KeywordDef, identifier)
}
fn stmt_defp(parser: Parser<'_>) -> ParseResult<'_> {
stmt_definition(parser, TokenKind::KeywordDefPrivate, identifier)
}
fn stmt_use(parser: Parser<'_>) -> ParseResult<'_> {
stmt_definition(parser, TokenKind::KeywordUse, expr_constant)
}
fn literal(parser: Parser<'_>) -> ParseResult<'_> {
expr_constant(parser)
.or_else(|_| expr_boolean(parser))
.or_else(|_| expr_integer(parser))
.or_else(|_| expr_float(parser))
.or_else(|_| expr_atom(parser))
.or_else(|_| expr_string(parser))
}
fn expr_string(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, token) = match_one(parser, TokenKind::String)?;
Ok((
parser,
Node::new(
NodeKind::String,
NodeValue::String(token.value.as_string().unwrap()),
token.span,
),
))
}
fn map_pair_keyword(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, name) = match_one(parser, TokenKind::Identifier)?;
let (parser, _) = match_one(parser, TokenKind::Colon)?;
let (parser, value) = match_one(parser, expr)?;
let atom = token_to_atom(&name)?;
let span = name.span.extend(value.span);
Ok((
parser,
Node::new(
NodeKind::KeywordPair,
NodeValue::KeywordPair(Box::new(atom), Box::new(value)),
span,
),
))
}
fn map_pair_expr(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, key) = match_one(parser, expr)?;
let (parser, _) = match_one(parser, TokenKind::EqGt)?;
let (parser, value) = match_one(parser, expr)?;
let span = key.span.extend(value.span);
Ok((
parser,
Node::new(
NodeKind::KeywordPair,
NodeValue::KeywordPair(Box::new(key), Box::new(value)),
span,
),
))
}
fn map_pair(parser: Parser<'_>) -> ParseResult<'_> {
map_pair_keyword(parser).or_else(|_| map_pair_expr(parser))
}
fn map_contents(parser: Parser<'_>) -> Result<(Parser, Vec<Node>)> {
match_many0_with_sep(parser, map_pair, TokenKind::Comma)
}
fn map_literal(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, nodes) = surrounded(
parser,
map_contents,
TokenKind::LeftBrace,
TokenKind::RightBrace,
)?;
let span = span(parser, &nodes);
Ok((
parser,
Node::new(NodeKind::Map, NodeValue::Collection(nodes), span),
))
}
fn list_contents(parser: Parser<'_>) -> Result<(Parser<'_>, Vec<Node>)> {
match_many0_with_sep(parser, expr, TokenKind::Comma)
}
fn list_literal(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, nodes) = surrounded(
parser,
list_contents,
TokenKind::LeftSquare,
TokenKind::RightSquare,
)?;
let span = span(parser, &nodes);
Ok((
parser,
Node::new(NodeKind::List, NodeValue::Collection(nodes), span),
))
}
fn token_to_atom(token: &Token) -> Result<Node> {
match token.kind {
TokenKind::Identifier => Ok(Node::new(
NodeKind::Atom,
NodeValue::String(token.value.as_string().unwrap()),
token.span,
)),
token_kind => Err(Error::unexpected(token_kind, vec![TokenKind::Identifier])),
}
}
fn keyword_arg(parser: Parser<'_>) -> ParseResult<'_> {
let (parser, token) = succeeded(parser, TokenKind::Identifier, TokenKind::Colon)?;
let atom = token_to_atom(&token)?;
let (parser, expr_node) =
succeeded(parser, block, TokenKind::KeywordEnd).or_else(|_| expr(parser))?;
let span = token.span.extend(expr_node.span);
Ok((
parser,
Node::new(
NodeKind::KeywordPair,
NodeValue::KeywordPair(Box::new(atom), Box::new(expr_node)),
span,
),
))
}
fn expr_or_stmt(parser: Parser<'_>) -> ParseResult<'_> {
expr(parser).or_else(|_| stmt(parser))
}
fn block(parser: Parser<'_>) -> Result<(Parser<'_>, Node)> {
let (parser, expressions) = match_many0(parser, expr_or_stmt)?;
let span = span(parser, &expressions);
Ok((
parser,
Node::new(NodeKind::Block, NodeValue::Collection(expressions), span),
))
}
fn keyword_args(parser: Parser<'_>) -> Result<(Parser<'_>, Vec<Node>)> {
match_many0_with_sep(parser, keyword_arg, TokenKind::Comma)
}
fn infix_op(parser: Parser<'_>) -> ParseResult<'_> {
match_one(parser, TokenKind::And)
.or_else(|_| match_one(parser, TokenKind::AndAnd))
.or_else(|_| match_one(parser, TokenKind::BangEq))
.or_else(|_| match_one(parser, TokenKind::Dot))
.or_else(|_| match_one(parser, TokenKind::Eq))
.or_else(|_| match_one(parser, TokenKind::EqEq))
.or_else(|_| match_one(parser, TokenKind::ForwardSlash))
.or_else(|_| match_one(parser, TokenKind::Gt))
.or_else(|_| match_one(parser, TokenKind::GtEq))
.or_else(|_| match_one(parser, TokenKind::GtGt))
.or_else(|_| match_one(parser, TokenKind::Lt))
.or_else(|_| match_one(parser, TokenKind::LtEq))
.or_else(|_| match_one(parser, TokenKind::LtLt))
.or_else(|_| match_one(parser, TokenKind::Minus))
.or_else(|_| match_one(parser, TokenKind::Percent))
.or_else(|_| match_one(parser, TokenKind::Pipe))
.or_else(|_| match_one(parser, TokenKind::PipePipe))
.or_else(|_| match_one(parser, TokenKind::Plus))
.or_else(|_| match_one(parser, TokenKind::Star))
.map(|(parser, token)| {
(
parser,
Node::new(
NodeKind::Operator,
NodeValue::Operator(token.kind),
token.span,
),
)
})
}
fn prefix_op(parser: Parser<'_>) -> Result<(Parser<'_>, Token)> {
match_one(parser, TokenKind::Bang).or_else(|_| match_one(parser, TokenKind::Minus))
}
fn span_from_nodes(nodes: &[Node]) -> Option<Span> {
let first = nodes.first()?;
let last = nodes.last()?;
Some(first.span.extend(last.span))
}
fn span_from_parser(parser: Parser<'_>) -> Option<Span> {
let token = parser.current_token()?;
Some(token.span.start.into())
}
fn span(parser: Parser<'_>, nodes: &[Node]) -> Span {
span_from_nodes(nodes)
.or_else(|| span_from_parser(parser))
.or_else(|| Some(0.into()))
.unwrap()
}
#[cfg(test)]
mod test {
use super::*;
use outrun_lexer::{Token, TokenValue};
use outrun_lexer::{Scanner, Token};
// #[test]
// fn test_match_constant() {
// let tokens = vec![Token::new(
// TokenKind::Identifier,
// TokenValue::String("Celldweller".to_string()),
// 0.into(),
// )];
// let parser = Parser::new(&tokens);
// let (_, node) = match_constant(parser).unwrap();
fn tokens_for(input: &str) -> Vec<Token> {
Scanner::new(input).into_iter().collect()
}
// assert_eq!(
// node,
// Node::Constant(tokens))
// );
// }
#[test]
fn test_expr_constant_simple() {
let tokens = tokens_for("Celldweller");
let parser = Parser::new(&tokens);
let (_, node) = expr_constant(parser).unwrap();
assert_eq!(node.kind, NodeKind::Constant);
assert_eq!(node.value.as_strings().unwrap(), vec!["Celldweller"]);
}
#[test]
fn test_expr_constant_nested() {
let tokens = tokens_for("Celldweller.EndOfAnEmpire");
let parser = Parser::new(&tokens);
let (_, node) = expr_constant(parser).unwrap();
assert_eq!(node.kind, NodeKind::Constant);
assert_eq!(
node.value.as_strings().unwrap(),
vec!["Celldweller", "EndOfAnEmpire"]
);
}
#[test]
fn test_stmt_use_no_args() {
let tokens = tokens_for("use Celldweller");
let parser = Parser::new(&tokens);
let (_, node) = stmt_use(parser).unwrap();
assert_eq!(node.kind, NodeKind::Definition);
assert_matches!(node.value, NodeValue::Definition { kind, name, fields, arguments } => {
assert_matches!(kind, TokenKind::KeywordUse);
assert_is_constant(&name, "Celldweller");
assert_eq!(fields.len(), 0);
assert_eq!(arguments.len(), 0);
});
}
#[test]
fn test_stmt_use_with_args() {
let tokens = tokens_for("use Celldweller, as: Dank");
let parser = Parser::new(&tokens);
let (_, node) = stmt_use(parser).unwrap();
assert_matches!(node.kind, NodeKind::Definition);
assert_matches!(node.value, NodeValue::Definition { kind, name, fields, arguments } => {
assert_matches!(kind, TokenKind::KeywordUse);
assert_matches!(name.kind, NodeKind::Constant);
assert_matches!(name.value, NodeValue::Constant(name) => {
assert_eq!(name, &["Celldweller"]);
});
assert_eq!(fields.len(), 0);
let arg = &arguments[0];
assert_matches!(arg.kind, NodeKind::KeywordPair);
assert_matches!(arg.value, NodeValue::KeywordPair(ref key, ref value) => {
assert_matches!(key.kind, NodeKind::Atom);
assert_matches!(key.value, NodeValue::String(ref value) => {
assert_eq!(value, "as")
});
assert_matches!(value.kind, NodeKind::Constant);
assert_matches!(value.value, NodeValue::Constant(ref name) => {
assert_eq!(name, &["Dank"]);
});
});
});
}
#[test]
fn test_expr() {
let tokens = tokens_for("1");
let parser = Parser::new(&tokens);
let (_, node) = expr(parser).unwrap();
assert_matches!(node.kind, NodeKind::Integer);
assert_matches!(node.value, NodeValue::Integer(1));
}
#[test]
fn test_expr_string() {
let tokens = tokens_for("\"Scandroid\"");
let parser = Parser::new(&tokens);
let (_, node) = expr_string(parser).unwrap();
assert_is_string(&node, "Scandroid");
}
#[test]
fn test_infix_op_followed_by_expr() {
let tokens = tokens_for("+ 1");
let parser = Parser::new(&tokens);
let (_, (op, expr)) = infix_op_followed_by_expr(parser).unwrap();
assert_matches!(op.value, NodeValue::Operator(TokenKind::Plus));
assert_is_integer(&expr, 1);
}
#[test]
fn test_expr_infix() {
let tokens = tokens_for("1 + 2 * 3");
let parser = Parser::new(&tokens);
let (_, node) = expr_infix(parser).unwrap();
println!("node: {:?}", node);
assert_matches!(node.value, NodeValue::Infix(ref lhs, ref op, ref rhs) => {
assert_is_integer(lhs, 1);
assert_eq!(*op, TokenKind::Plus);
assert_matches!(rhs.value, NodeValue::Infix(ref lhs, ref op, ref rhs) => {
assert_is_integer(lhs, 2);
assert_eq!(*op, TokenKind::Star);
assert_is_integer(rhs, 3);
});
});
}
#[test]
fn test_expr_boolean_true() {
let tokens = tokens_for("true");
let parser = Parser::new(&tokens);
let (_, node) = expr_boolean(parser).unwrap();
assert_is_boolean(&node, true);
}
#[test]
fn test_expr_boolean_false() {
let tokens = tokens_for("false");
let parser = Parser::new(&tokens);
let (_, node) = expr_boolean(parser).unwrap();
assert_is_boolean(&node, false);
}
#[test]
fn test_expr_integer() {
let tokens = tokens_for("123");
let parser = Parser::new(&tokens);
let (_, node) = expr_integer(parser).unwrap();
assert_is_integer(&node, 123);
}
#[test]
fn test_expr_float() {
let tokens = tokens_for("1.23");
let parser = Parser::new(&tokens);
let (_, node) = expr_float(parser).unwrap();
assert_matches!(node.value, NodeValue::Float(value) => {
assert_eq!(value, 1.23);
});
}
#[test]
fn test_atom() {
let tokens = tokens_for(":GUNSHIP");
let parser = Parser::new(&tokens);
let (_, node) = expr_atom(parser).unwrap();
assert_is_atom(&node, "GUNSHIP");
}
#[test]
fn test_module() {
let tokens = tokens_for("use Celldweller struct GUNSHIP(loud: Boolean)");
let parser = Parser::new(&tokens);
let (_, node) = module(parser).unwrap();
assert_matches!(node.kind, NodeKind::Module);
assert_matches!(node.value, NodeValue::Collection(stmts) => {
assert_eq!(stmts.len(), 2);
});
}
#[test]
fn test_stmt() {
let tokens = tokens_for("use Celldweller");
let parser = Parser::new(&tokens);
let (_, node) = stmt(parser).unwrap();
assert_matches!(node.kind, NodeKind::Definition);
}
#[test]
fn test_stmt_struct() {
let tokens = tokens_for("struct GUNSHIP(loud: Boolean)");
let parser = Parser::new(&tokens);
let (_, node) = stmt_struct(parser).unwrap();
assert_matches!(node.kind, NodeKind::Definition);
assert_matches!(node.value, NodeValue::Definition { ref kind, ref name, ref fields, ref arguments } => {
assert_matches!(kind, TokenKind::KeywordStruct);
assert_is_constant(name, "GUNSHIP");
assert_eq!(fields.len(), 1);
let field = &fields[0];
assert_eq!(field.kind, NodeKind::KeywordPair);
assert_matches!(field.value, NodeValue::KeywordPair(ref key, ref value) => {
assert_is_atom(key, "loud");
assert_is_constant(value, "Boolean");
});
assert!(arguments.is_empty());
});
}
#[test]
fn test_stmt_protocol() {
let tokens = tokens_for("protocol Scandroid, as: :epic end");
let parser = Parser::new(&tokens);
let (_, node) = stmt_protocol(parser).unwrap();
assert_matches!(node.kind, NodeKind::Definition);
assert_matches!(node.value, NodeValue::Definition { ref kind, ref name, ref fields, ref arguments } => {
assert_matches!(kind, TokenKind::KeywordProtocol);
assert_is_constant(name, "Scandroid");
assert_eq!(fields.len(), 0);
assert_eq!(arguments.len(), 1);
assert_matches!(arguments[0].value, NodeValue::KeywordPair(ref key, ref block) => {
assert_is_atom(key, "as");
assert_matches!(block.as_ref().value, NodeValue::Collection(ref nodes) => {
assert_is_atom(&nodes[0], "epic");
});
});
});
}
#[test]
fn test_stmt_def() {
let tokens = tokens_for("def water, as: :wet end");
let parser = Parser::new(&tokens);
let (_, node) = stmt_def(parser).unwrap();
assert_matches!(node.kind, NodeKind::Definition);
assert_matches!(node.value, NodeValue::Definition { ref kind, ref name, ref fields, ref arguments } => {
assert_matches!(kind, TokenKind::KeywordDef);
assert_is_atom(name, "water");
assert_eq!(fields.len(), 0);
assert_eq!(arguments.len(), 1);
assert_matches!(arguments[0].value, NodeValue::KeywordPair(ref key, ref block) => {
assert_is_atom(key, "as");
assert_matches!(block.as_ref().value, NodeValue::Collection(ref nodes) => {
assert_is_atom(&nodes[0], "wet");
});
});
});
}
#[test]
fn test_stmt_defp() {
let tokens = tokens_for("defp water, as: :wet end");
let parser = Parser::new(&tokens);
let (_, node) = stmt_defp(parser).unwrap();
assert_matches!(node.kind, NodeKind::Definition);
assert_matches!(node.value, NodeValue::Definition { ref kind, ref name, ref fields, ref arguments } => {
assert_matches!(kind, TokenKind::KeywordDefPrivate);
assert_is_atom(name, "water");
assert_eq!(fields.len(), 0);
assert_eq!(arguments.len(), 1);
assert_matches!(arguments[0].value, NodeValue::KeywordPair(ref key, ref block) => {
assert_is_atom(key, "as");
assert_matches!(block.as_ref().value, NodeValue::Collection(ref nodes) => {
assert_is_atom(&nodes[0], "wet");
});
});
});
}
#[test]
fn test_map_literal() {
let tokens = tokens_for("{ keyword_key: true, :hashrocket_key => false }");
let parser = Parser::new(&tokens);
let (_, node) = map_literal(parser).unwrap();
assert_matches!(node.kind, NodeKind::Map);
assert_matches!(node.value, NodeValue::Collection(ref contents) => {
assert_eq!(contents.len(), 2);
assert_matches!(contents[0].value, NodeValue::KeywordPair(ref key, ref value) => {
assert_is_atom(key, "keyword_key");
assert_is_boolean(value, true);
});
assert_matches!(contents[1].value, NodeValue::KeywordPair(ref key, ref value) => {
assert_is_atom(key, "hashrocket_key");
assert_is_boolean(value, false);
});
});
}
#[test]
fn test_list_literal() {
let tokens = tokens_for("[1,2,3]");
let parser = Parser::new(&tokens);
let (_, node) = list_literal(parser).unwrap();
assert_matches!(node.kind, NodeKind::List);
assert_matches!(node.value, NodeValue::Collection(values) => {
assert_is_integer(&values[0], 1);
assert_is_integer(&values[1], 2);
assert_is_integer(&values[2], 3);
});
}
#[test]
fn test_block() {
let tokens = tokens_for("1 2 3");
let parser = Parser::new(&tokens);
let (_, node) = block(parser).unwrap();
assert_matches!(node.kind, NodeKind::Block);
assert_matches!(node.value, NodeValue::Collection(exprs) => {
assert_eq!(exprs.len(), 3);
assert_is_integer(&exprs[0], 1);
assert_is_integer(&exprs[1], 2);
assert_is_integer(&exprs[2], 3);
});
}
fn assert_is_boolean(node: &Node, value: bool) {
assert_matches!(node.kind, NodeKind::Boolean);
assert_matches!(node.value, NodeValue::Boolean(bool_value) => {
assert_eq!(bool_value, value);
});
}
#[test]
fn test_expr_call() {
let tokens = tokens_for("example(1, 2, 3)");
let parser = Parser::new(&tokens);
let (_, node) = expr_call(parser).unwrap();
assert_matches!(node.kind, NodeKind::Call);
assert_matches!(node.value, NodeValue::Call{ ref receiver, ref arguments } => {
assert_is_get_local(receiver, "example");
assert_eq!(arguments.len(), 3);
assert_is_integer(&arguments[0], 1);
assert_is_integer(&arguments[1], 2);
assert_is_integer(&arguments[2], 3);
});
}
#[test]
fn test_expr_braced() {
let tokens = tokens_for("(1)");
let parser = Parser::new(&tokens);
let (_, node) = expr_braced(parser).unwrap();
assert_is_integer(&node, 1);
}
#[test]
fn test_expr_prefix() {
let tokens = tokens_for("!true");
let parser = Parser::new(&tokens);
let (_, node) = expr_prefix(parser).unwrap();
assert_matches!(node.kind, NodeKind::Prefix);
assert_matches!(node.value, NodeValue::Prefix(ref operator, ref operand) => {
assert_eq!(operator, &TokenKind::Bang);
assert_is_boolean(operand, true)
});
}
#[test]
fn test_stmt_let() {
let tokens = tokens_for("let genre = \"Synthwave\"");
let parser = Parser::new(&tokens);
let (_, node) = stmt_let(parser).unwrap();
assert_matches!(node.kind, NodeKind::SetLocal);
assert_matches!(node.value, NodeValue::SetLocal(ref name, ref value) => {
assert_eq!(name, "genre");
assert_is_string(value, "Synthwave");
});
}
fn assert_is_atom(node: &Node, name: &str) {
assert_matches!(node.kind, NodeKind::Atom);
assert_matches!(node.value, NodeValue::String(ref atom) => {
assert_eq!(atom, name);
});
}
fn assert_is_constant(node: &Node, name: &str) {
assert_matches!(node.kind, NodeKind::Constant);
assert_matches!(node.value, NodeValue::Constant(ref values) => {
assert_eq!(name, values.join("."));
});
}
fn assert_is_integer(node: &Node, value: i64) {
assert_matches!(node.kind, NodeKind::Integer);
assert_matches!(node.value, NodeValue::Integer(actual) => {
assert_eq!(value, actual);
});
}
fn assert_is_get_local(node: &Node, name: &str) {
assert_matches!(node.kind, NodeKind::GetLocal);
assert_matches!(node.value, NodeValue::String(ref value) => {
assert_eq!(value, name);
});
}
fn assert_is_string(node: &Node, name: &str) {
assert_matches!(node.kind, NodeKind::String);
assert_matches!(node.value, NodeValue::String(ref value) => {
assert_eq!(value, name);
});
}
}

View file

@ -2,7 +2,7 @@
authors = ["James Harton <james@harton.nz>"]
description = "🌅 The CLI for the most retro-futuristic toy language in the world."
edition = "2021"
license_file = "../LICENSE.md"
license-file = "../LICENSE.md"
name = "outrun"
version = "0.1.0"
@ -10,3 +10,4 @@ version = "0.1.0"
[dependencies]
outrun-lexer = {path = "../outrun-lexer"}
outrun-parser = {path = "../outrun-parser"}

View file

@ -1,30 +1,29 @@
//! The future home of the Outrun CLI.
use std::fs::File;
use std::io::Read;
extern crate outrun_lexer;
use outrun_lexer::Scanner;
extern crate outrun_parser;
use outrun_lexer::{Scanner, Token};
use outrun_parser::Parser;
fn main() -> std::io::Result<()> {
let mut file = File::open("test.or")?;
let mut source = String::new();
file.read_to_string(&mut source)?;
let mut scanner = Scanner::new(&source);
let tokens = Scanner::new(&source).into_iter().collect::<Vec<Token>>();
let parser = Parser::new(&tokens);
let result = parser.parse_module();
loop {
match scanner.next() {
Ok((new_scanner, token)) => {
println!("token: {:?}", token);
scanner = new_scanner;
}
Err(error) => {
println!("error: {:?}", error);
break;
}
}
if let Ok((parser, node)) = result {
println!("parser: {:?}", parser);
println!("node: {:?}", node);
} else {
println!("result: {:?}", result);
}
println!("Scanner: {:?}", scanner);
Ok(())
}