forked from natto1784/tricc
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
d377c8448f | |||
af30410aab | |||
360687f3c0 | |||
f7ec10646b | |||
ad57170010 | |||
1a2563f756 | |||
d70b196042 | |||
771363106f | |||
384f382cfc | |||
bcc3b29fc5 | |||
589fa73d7c | |||
32484b3d6a | |||
a15b3d013f | |||
e8192df9e2 | |||
879d3d3b65 | |||
0d7a4bdd4e | |||
6979a02408 |
34
.github/workflows/ci.yml
vendored
Normal file
34
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [ push, pull_request, workflow_dispatch ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: cachix/install-nix-action@v20
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
auto-optimise-store = true
|
||||||
|
experimental-features = nix-command flakes
|
||||||
|
- uses: cachix/cachix-action@v12
|
||||||
|
with:
|
||||||
|
name: pain
|
||||||
|
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||||
|
|
||||||
|
- name: fmt checks
|
||||||
|
run: nix build .#checks.fmt -L
|
||||||
|
|
||||||
|
- name: clippy checks
|
||||||
|
run: nix build .#checks.clippy -L
|
||||||
|
|
||||||
|
- name: nextest checks
|
||||||
|
run: nix build .#checks.nextest -L
|
||||||
|
|
||||||
|
- name: doc checks
|
||||||
|
run: nix build .#checks.doc -L
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
run: nix build .#tricc -L
|
@@ -2,6 +2,6 @@ tricc (pronounced "trick-c" or "trixie" like pixie) is a WIP toy compiler I am
|
|||||||
writing to understand compilers better, that's it. Will probably use LLVM as
|
writing to understand compilers better, that's it. Will probably use LLVM as
|
||||||
the backend.
|
the backend.
|
||||||
|
|
||||||
Notes:
|
** Notes:
|
||||||
- Does not work
|
+ Does not work
|
||||||
- Is WIP
|
+ Is WIP
|
96
flake.lock
generated
96
flake.lock
generated
@@ -10,11 +10,11 @@
|
|||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680584903,
|
"lastModified": 1688772518,
|
||||||
"narHash": "sha256-uraq+D3jcLzw/UVk0xMHcnfILfIMa0DLrtAEq2nNlxU=",
|
"narHash": "sha256-ol7gZxwvgLnxNSZwFTDJJ49xVY5teaSvF7lzlo3YQfM=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "65d3f6a3970cd46bef5eedfd458300f72c56b3c5",
|
"rev": "8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -40,12 +40,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678901627,
|
"lastModified": 1687709756,
|
||||||
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -55,12 +58,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils_2": {
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678901627,
|
"lastModified": 1689068808,
|
||||||
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -70,12 +76,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils_3": {
|
"flake-utils_3": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_3"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1659877975,
|
"lastModified": 1681202837,
|
||||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -86,11 +95,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680724564,
|
"lastModified": 1688392541,
|
||||||
"narHash": "sha256-eeUUGOTKTelYKDbUxKs0V7GUa186L2fym7jM2QQ4Oss=",
|
"narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "36adaa6aaa6b03e59102df0c1b12cdc3f23fd112",
|
"rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -120,11 +129,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680488274,
|
"lastModified": 1688351637,
|
||||||
"narHash": "sha256-0vYMrZDdokVmPQQXtFpnqA2wEgCCUXf5a3dDuDVshn0=",
|
"narHash": "sha256-CLTufJ29VxNOIZ8UTg0lepsn3X03AmopmaLTTeHDCL4=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "7ec2ff598a172c6e8584457167575b3a1a5d80d8",
|
"rev": "f9b92316727af9e6c7fee4a761242f7f46880329",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -141,11 +150,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680660688,
|
"lastModified": 1690252178,
|
||||||
"narHash": "sha256-XeQTCxWBR0Ai1VMzI5ZXYpA2lu1F8FzZKjw8RtByZOg=",
|
"narHash": "sha256-9oEz822bvbHobfCUjJLDor2BqW3I5tycIauzDlzOALY=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "2f40052be98347b479c820c00fb2fc1d87b3aa28",
|
"rev": "8d64353ca827002fb8459e44d49116c78d868eba",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -153,6 +162,51 @@
|
|||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
34
flake.nix
34
flake.nix
@@ -9,25 +9,28 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
rust-overlay = {
|
fenix = {
|
||||||
url = github:oxalica/rust-overlay;
|
url = github:nix-community/fenix;
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
flake-utils.url = github:numtide/flake-utils;
|
flake-utils.url = github:numtide/flake-utils;
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs@{ self, nixpkgs, crane, rust-overlay, flake-utils }:
|
outputs = inputs@{ self, nixpkgs, crane, fenix, flake-utils }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
overlays = [ rust-overlay.overlays.default ];
|
overlays = [ fenix.overlays.default ];
|
||||||
};
|
};
|
||||||
|
|
||||||
inherit (pkgs) lib;
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
toolchain = pkgs.fenix.fromToolchainFile {
|
||||||
|
file = ./rust-toolchain.toml;
|
||||||
|
sha256 = "sha256-n8LtGbpj/yCUGo0NFJ7FNv9fSdT9oKEUl+EPLg06JdQ=";
|
||||||
|
};
|
||||||
|
|
||||||
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
|
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
|
||||||
src = craneLib.cleanCargoSource (craneLib.path ./.);
|
src = craneLib.cleanCargoSource (craneLib.path ./.);
|
||||||
@@ -38,25 +41,34 @@
|
|||||||
|
|
||||||
tricc = craneLib.buildPackage (commonArgs // {
|
tricc = craneLib.buildPackage (commonArgs // {
|
||||||
inherit cargoArtifacts;
|
inherit cargoArtifacts;
|
||||||
|
doCheck = false;
|
||||||
});
|
});
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
checks = {
|
packages = {
|
||||||
inherit tricc;
|
inherit tricc;
|
||||||
|
default = tricc;
|
||||||
|
|
||||||
|
# not using flake checks to run them individually
|
||||||
|
checks = {
|
||||||
clippy = craneLib.cargoClippy (commonArgs // {
|
clippy = craneLib.cargoClippy (commonArgs // {
|
||||||
inherit cargoArtifacts;
|
inherit cargoArtifacts;
|
||||||
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
fmt = craneLib.cargoFmt {
|
fmt = craneLib.cargoFmt {
|
||||||
inherit src;
|
inherit src;
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
packages = {
|
doc = craneLib.cargoDoc (commonArgs // {
|
||||||
inherit tricc;
|
inherit cargoArtifacts;
|
||||||
default = tricc;
|
});
|
||||||
|
|
||||||
|
nextest = craneLib.cargoNextest (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
partitions = 1;
|
||||||
|
partitionType = "count";
|
||||||
|
});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly-2023-04-01"
|
channel = "nightly-2023-07-15"
|
||||||
components = [ "rustfmt", "clippy", "rust-analyzer", "rust-src" ]
|
components = [ "rustfmt", "clippy", "rust-analyzer", "rust-src" ]
|
||||||
|
5
rustfmt.toml
Normal file
5
rustfmt.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
comment_width = 99
|
||||||
|
format_code_in_doc_comments = true
|
||||||
|
imports_granularity = "Module"
|
||||||
|
imports_layout = "Vertical"
|
||||||
|
wrap_comments = true
|
38
src/args.rs
38
src/args.rs
@@ -1,17 +1,25 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
const CRATE: &str = env!("CARGO_CRATE_NAME");
|
const CRATE: &str = env!("CARGO_CRATE_NAME");
|
||||||
|
|
||||||
|
/// A naive argument handler
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Options {
|
pub struct Args {
|
||||||
version: bool,
|
version: bool,
|
||||||
file: String,
|
file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// naive argument handling
|
impl Args {
|
||||||
pub fn handle() -> String {
|
/// Creates a new [`Args`] instance
|
||||||
|
pub fn new() -> Args {
|
||||||
|
Args::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks for various arguments
|
||||||
|
pub fn handle(&mut self) {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
@@ -19,32 +27,36 @@ pub fn handle() -> String {
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut options: Options = Default::default();
|
|
||||||
|
|
||||||
for arg in &args[1..] {
|
for arg in &args[1..] {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"-v" | "--version" => options.version = true,
|
"-v" | "--version" => self.version = true,
|
||||||
flag if flag.starts_with('-') => panic!("option {} not implemented!", flag),
|
flag if flag.starts_with('-') => panic!("option {} not implemented!", flag),
|
||||||
file => {
|
file => {
|
||||||
if !options.file.is_empty() {
|
if self.file.is_some() {
|
||||||
panic!("please specify only a single source file!");
|
panic!("please specify only a single source file!");
|
||||||
}
|
}
|
||||||
options.file = file.to_string();
|
self.file = Some(PathBuf::from(file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.version {
|
if self.version {
|
||||||
println!("{} version: {}", CRATE, VERSION);
|
println!("{} version: {}", CRATE, VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.file.is_empty() {
|
if self.file.is_none() {
|
||||||
if options.version {
|
if self.version {
|
||||||
exit(0);
|
exit(0);
|
||||||
} else {
|
} else {
|
||||||
panic!("no file supplied!");
|
panic!("no file supplied!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
options.file
|
/// Fetches the file from the arguments.
|
||||||
|
/// Panics if there is no file in the arguments
|
||||||
|
#[inline]
|
||||||
|
pub fn get_file(self) -> PathBuf {
|
||||||
|
self.file.expect("no file supplied!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
132
src/ast.rs
Normal file
132
src/ast.rs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
//! A very naive AST definition using recursive enums
|
||||||
|
//!
|
||||||
|
//! See the parser for implementation
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub type Parent = Vec<Entity>;
|
||||||
|
|
||||||
|
/// Entities are functions, classes, and modules
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Entity {
|
||||||
|
Fn(Fn),
|
||||||
|
Class(Class),
|
||||||
|
Module(Module),
|
||||||
|
Static(Let),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A module just provides an additional scope
|
||||||
|
///
|
||||||
|
/// TODO: Add exporting and importing modules
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Module {
|
||||||
|
/// Name of module
|
||||||
|
pub name: Rc<str>,
|
||||||
|
/// Everything inside the module
|
||||||
|
pub children: Vec<ModuleChildren>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modules contain functions, classes and statements
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ModuleChildren {
|
||||||
|
Fn(Fn),
|
||||||
|
Class(Class),
|
||||||
|
Module(Module),
|
||||||
|
Static(Let),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Classes encapsulate functions and definitions.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Class {
|
||||||
|
/// Name of class
|
||||||
|
pub name: Rc<str>,
|
||||||
|
/// Everything inside the class
|
||||||
|
pub children: Vec<ClassChildren>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ClassChildren {
|
||||||
|
Fn(Fn),
|
||||||
|
Let(Let),
|
||||||
|
Static(Let),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Function
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Fn {
|
||||||
|
/// Name of the function
|
||||||
|
pub name: Rc<str>,
|
||||||
|
/// Optional return type
|
||||||
|
pub return_ty: Option<Ty>,
|
||||||
|
/// Parameters
|
||||||
|
pub params: Vec<(Rc<str>, Ty)>,
|
||||||
|
/// The function block
|
||||||
|
pub children: Vec<Statement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Statements encapsulate expressions and definitions
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Statement {
|
||||||
|
Static(Let),
|
||||||
|
Let(Let),
|
||||||
|
Expr(Expr),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A variable definition
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Let {
|
||||||
|
/// Name of variabe
|
||||||
|
pub name: Rc<str>,
|
||||||
|
/// Type of variable
|
||||||
|
pub ty: Ty,
|
||||||
|
/// Value of variable
|
||||||
|
pub expr: Option<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Primitives
|
||||||
|
///
|
||||||
|
/// TODO: add arrays and pointers maybe
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Ty {
|
||||||
|
Int,
|
||||||
|
Float,
|
||||||
|
Char,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct If {
|
||||||
|
pub cond: Box<Expr>,
|
||||||
|
pub then: Vec<Statement>,
|
||||||
|
pub or: Option<Box<ElseType>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ElseType {
|
||||||
|
If(If),
|
||||||
|
Else(Vec<Statement>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) type Op = crate::lexer::TokenSymbol;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Literal {
|
||||||
|
Int(i32),
|
||||||
|
Float(f32),
|
||||||
|
Char(char),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lowest form of expression
|
||||||
|
///
|
||||||
|
/// TODO: refine
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Expr {
|
||||||
|
Literal(Literal),
|
||||||
|
Identifier(Rc<str>),
|
||||||
|
Op(Op, Box<Expr>, Option<Box<Expr>>),
|
||||||
|
If(If),
|
||||||
|
Block(Vec<Statement>),
|
||||||
|
Loop(Vec<Statement>),
|
||||||
|
Break,
|
||||||
|
Continue,
|
||||||
|
Return(Option<Box<Expr>>),
|
||||||
|
}
|
565
src/lexer.rs
565
src/lexer.rs
@@ -0,0 +1,565 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::iter::Peekable;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
/// All token literals
|
||||||
|
///
|
||||||
|
/// TODO: Add string
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum TokenLiteral {
|
||||||
|
Int,
|
||||||
|
Float,
|
||||||
|
Char,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All token symbols
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum TokenSymbol {
|
||||||
|
// arithmetic
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Star,
|
||||||
|
Slash,
|
||||||
|
Percent,
|
||||||
|
Caret,
|
||||||
|
Tilde,
|
||||||
|
|
||||||
|
// bitwise
|
||||||
|
Shl,
|
||||||
|
Shr,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
|
||||||
|
// logical
|
||||||
|
Not,
|
||||||
|
AndAnd,
|
||||||
|
OrOr,
|
||||||
|
|
||||||
|
// relational
|
||||||
|
Gt,
|
||||||
|
Lt,
|
||||||
|
GtEq,
|
||||||
|
LtEq,
|
||||||
|
EqEq,
|
||||||
|
Ne,
|
||||||
|
|
||||||
|
// assignment
|
||||||
|
Eq,
|
||||||
|
PlusEq,
|
||||||
|
MinusEq,
|
||||||
|
StarEq,
|
||||||
|
SlashEq,
|
||||||
|
PercentEq,
|
||||||
|
CaretEq,
|
||||||
|
ShlEq,
|
||||||
|
ShrEq,
|
||||||
|
AndEq,
|
||||||
|
OrEq,
|
||||||
|
|
||||||
|
//misc
|
||||||
|
Colon,
|
||||||
|
Dot,
|
||||||
|
Comma,
|
||||||
|
Hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All token keywod
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum TokenKeyword {
|
||||||
|
// parents
|
||||||
|
Fn,
|
||||||
|
Class,
|
||||||
|
Module,
|
||||||
|
|
||||||
|
// statements
|
||||||
|
Static,
|
||||||
|
Let,
|
||||||
|
Ret,
|
||||||
|
|
||||||
|
// conditionals
|
||||||
|
If,
|
||||||
|
Else,
|
||||||
|
Elif,
|
||||||
|
|
||||||
|
// control flow
|
||||||
|
Loop,
|
||||||
|
Break,
|
||||||
|
Continue,
|
||||||
|
Return,
|
||||||
|
|
||||||
|
// primitives
|
||||||
|
Int,
|
||||||
|
Float,
|
||||||
|
Char,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All token delimiters
|
||||||
|
///
|
||||||
|
/// TODO: Maybe add \[ and \]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum TokenDelimiter {
|
||||||
|
BraceOpen,
|
||||||
|
BraceClose,
|
||||||
|
ParenOpen,
|
||||||
|
ParenClose,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All tokens
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum TokenKind {
|
||||||
|
Newline,
|
||||||
|
Eof,
|
||||||
|
Literal(TokenLiteral),
|
||||||
|
Symbol(TokenSymbol),
|
||||||
|
Keyword(TokenKeyword),
|
||||||
|
Delimiter(TokenDelimiter),
|
||||||
|
Identifier,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Token {
|
||||||
|
pub kind: TokenKind,
|
||||||
|
/// Holds the reference to the tokenized string
|
||||||
|
///
|
||||||
|
/// For example, if `kind` is of type [`TokenKind::Identifier`], this would contain the value
|
||||||
|
/// of that identifier
|
||||||
|
pub val: Rc<str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Lexer<'a> {
|
||||||
|
/// The entire text to be tokenized
|
||||||
|
text: &'a str,
|
||||||
|
/// A peekable iterate for `text`
|
||||||
|
chars: Peekable<str::Chars<'a>>,
|
||||||
|
/// A peekable double ended queue for the tokens
|
||||||
|
tokens: VecDeque<Token>,
|
||||||
|
/// Current line number
|
||||||
|
pub(crate) line: usize,
|
||||||
|
pub(crate) col: usize,
|
||||||
|
/// Start character index for the current token
|
||||||
|
start: usize,
|
||||||
|
/// End character index for the current token
|
||||||
|
end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Lexer<'a> {
|
||||||
|
/// Creates a new [`Lexer`] instance with the provided content.
|
||||||
|
///
|
||||||
|
/// The `Lexer` is responsible for tokenizing the given text, making it easier to
|
||||||
|
/// perform various parsing operations.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `content`: The text to tokenize.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A new instance of `Lexer` initialized with the provided `content`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tricc::lexer::Lexer;
|
||||||
|
///
|
||||||
|
/// let lexer = Lexer::new("let example: int = 4");
|
||||||
|
/// ```
|
||||||
|
pub fn new(content: &'a str) -> Self {
|
||||||
|
Lexer {
|
||||||
|
text: content,
|
||||||
|
chars: content.chars().peekable(),
|
||||||
|
tokens: VecDeque::new(),
|
||||||
|
line: 1,
|
||||||
|
col: 1,
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn new_token(&self, kind: TokenKind) -> Token {
|
||||||
|
Token {
|
||||||
|
kind,
|
||||||
|
val: Rc::from(&self.text[self.start..self.end]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn error(&self, msg: &str) {
|
||||||
|
eprintln!("Lexer: {}, at \"{}:{}\"", msg, self.line, self.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn peek(&mut self) -> Option<&char> {
|
||||||
|
self.chars.peek()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<char> {
|
||||||
|
self.end += 1;
|
||||||
|
self.col += 1;
|
||||||
|
self.chars.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_whitespace(&mut self) {
|
||||||
|
let mut ignore_nl: bool = false;
|
||||||
|
|
||||||
|
while let Some(c) = self.peek() {
|
||||||
|
match c {
|
||||||
|
'\r' | '\t' | ' ' => {
|
||||||
|
self.next();
|
||||||
|
}
|
||||||
|
'\n' => {
|
||||||
|
if ignore_nl {
|
||||||
|
ignore_nl = false;
|
||||||
|
self.next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'\\' => {
|
||||||
|
self.next();
|
||||||
|
ignore_nl = true;
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_numeric(&mut self) -> Token {
|
||||||
|
let mut is_float: bool = false;
|
||||||
|
while let Some(c) = self.peek() {
|
||||||
|
match c {
|
||||||
|
'0'..='9' => {}
|
||||||
|
'.' => {
|
||||||
|
if is_float {
|
||||||
|
self.error("multiple decimals encountered");
|
||||||
|
return self.new_token(TokenKind::Invalid);
|
||||||
|
}
|
||||||
|
is_float = true;
|
||||||
|
}
|
||||||
|
'e' | 'E' => {
|
||||||
|
self.next();
|
||||||
|
is_float = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
self.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.new_token(TokenKind::Literal(if is_float {
|
||||||
|
TokenLiteral::Float
|
||||||
|
} else {
|
||||||
|
TokenLiteral::Int
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_char(&mut self) -> Token {
|
||||||
|
// skip '
|
||||||
|
self.next();
|
||||||
|
|
||||||
|
if matches!(self.next(), Some('\'') | None) {
|
||||||
|
self.error("Expected character literal");
|
||||||
|
return self.new_token(TokenKind::Invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.peek() != Some(&'\'') {
|
||||||
|
self.error("Expected '");
|
||||||
|
return self.new_token(TokenKind::Invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip '
|
||||||
|
self.next();
|
||||||
|
|
||||||
|
self.new_token(TokenKind::Literal(TokenLiteral::Char))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_alphanumeric(&mut self) -> Token {
|
||||||
|
while let Some(c) = self.peek() {
|
||||||
|
match c {
|
||||||
|
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => {}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
self.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
use TokenKeyword::*;
|
||||||
|
use TokenKind::Keyword;
|
||||||
|
|
||||||
|
self.new_token(match &self.text[self.start..self.end] {
|
||||||
|
"fn" => Keyword(Fn),
|
||||||
|
"class" => Keyword(Class),
|
||||||
|
"module" => Keyword(Module),
|
||||||
|
"static" => Keyword(Static),
|
||||||
|
"let" => Keyword(Let),
|
||||||
|
"ret" => Keyword(Ret),
|
||||||
|
"if" => Keyword(If),
|
||||||
|
"else" => Keyword(Else),
|
||||||
|
"elif" => Keyword(Elif),
|
||||||
|
"loop" => Keyword(Loop),
|
||||||
|
"break" => Keyword(Break),
|
||||||
|
"continue" => Keyword(Continue),
|
||||||
|
"return" => Keyword(Return),
|
||||||
|
"int" => Keyword(Int),
|
||||||
|
"float" => Keyword(Float),
|
||||||
|
"char" => Keyword(Char),
|
||||||
|
_ => TokenKind::Identifier,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_symbol(&mut self) -> Token {
|
||||||
|
let c = self.next().unwrap();
|
||||||
|
|
||||||
|
use TokenDelimiter::*;
|
||||||
|
use TokenKind::{
|
||||||
|
Delimiter,
|
||||||
|
Symbol,
|
||||||
|
};
|
||||||
|
use TokenSymbol::*;
|
||||||
|
|
||||||
|
// handle +, +=, -, -=, *, *=, /, /=, %, %=, ^, ^=, !, !=
|
||||||
|
macro_rules! token_symbol_eq {
|
||||||
|
($a:expr, $b:expr) => {
|
||||||
|
match self.peek() {
|
||||||
|
Some('=') => {
|
||||||
|
self.next();
|
||||||
|
Symbol($b)
|
||||||
|
}
|
||||||
|
_ => Symbol($a),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle &, |, ||, &&, &=, |=
|
||||||
|
macro_rules! token_symbol_logical {
|
||||||
|
($a:expr, $b:expr, $c:expr, $d:expr) => {
|
||||||
|
match self.peek() {
|
||||||
|
Some('=') => {
|
||||||
|
self.next();
|
||||||
|
Symbol($c)
|
||||||
|
}
|
||||||
|
Some($d) => {
|
||||||
|
self.next();
|
||||||
|
Symbol($b)
|
||||||
|
}
|
||||||
|
_ => Symbol($a),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle <, <=, >, >=, <<, >>, <<=, >>=
|
||||||
|
macro_rules! token_symbol_compare {
|
||||||
|
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => {
|
||||||
|
match self.peek() {
|
||||||
|
Some('=') => {
|
||||||
|
self.next();
|
||||||
|
Symbol($d)
|
||||||
|
}
|
||||||
|
Some($e) => {
|
||||||
|
self.next();
|
||||||
|
token_symbol_eq!($b, $c)
|
||||||
|
}
|
||||||
|
_ => Symbol($a),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let typ = match c {
|
||||||
|
'{' => Delimiter(BraceOpen),
|
||||||
|
'}' => Delimiter(BraceClose),
|
||||||
|
'(' => Delimiter(ParenOpen),
|
||||||
|
')' => Delimiter(ParenClose),
|
||||||
|
'+' => token_symbol_eq!(Plus, PlusEq),
|
||||||
|
'-' => token_symbol_eq!(Minus, MinusEq),
|
||||||
|
'*' => token_symbol_eq!(Star, StarEq),
|
||||||
|
'/' => token_symbol_eq!(Slash, SlashEq),
|
||||||
|
'%' => token_symbol_eq!(Percent, PercentEq),
|
||||||
|
'^' => token_symbol_eq!(Caret, CaretEq),
|
||||||
|
'!' => token_symbol_eq!(Not, Ne),
|
||||||
|
'=' => token_symbol_eq!(Eq, EqEq),
|
||||||
|
'&' => token_symbol_logical!(And, AndAnd, AndEq, '&'),
|
||||||
|
'|' => token_symbol_logical!(Or, OrOr, OrEq, '|'),
|
||||||
|
'<' => token_symbol_compare!(Lt, Shl, ShlEq, LtEq, '<'),
|
||||||
|
'>' => token_symbol_compare!(Gt, Shr, ShrEq, GtEq, '>'),
|
||||||
|
'~' => Symbol(Tilde),
|
||||||
|
':' => Symbol(Colon),
|
||||||
|
'.' => Symbol(Dot),
|
||||||
|
',' => Symbol(Comma),
|
||||||
|
'#' => Symbol(Hash),
|
||||||
|
_ => {
|
||||||
|
self.error("Unknown character encountered");
|
||||||
|
TokenKind::Invalid
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.new_token(typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lex(&mut self) {
|
||||||
|
self.skip_whitespace();
|
||||||
|
self.start = self.end;
|
||||||
|
|
||||||
|
let token = if let Some(c) = self.peek() {
|
||||||
|
match c {
|
||||||
|
'\n' => {
|
||||||
|
self.next();
|
||||||
|
self.line += 1;
|
||||||
|
self.col = 0;
|
||||||
|
self.new_token(TokenKind::Newline)
|
||||||
|
}
|
||||||
|
'0'..='9' => self.get_numeric(),
|
||||||
|
'a'..='z' | 'A'..='Z' | '_' => self.get_alphanumeric(),
|
||||||
|
'\'' => self.get_char(),
|
||||||
|
_ => self.get_symbol(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.new_token(TokenKind::Eof)
|
||||||
|
};
|
||||||
|
self.tokens.push_back(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Peeks at the next token and returns a reference to it
|
||||||
|
pub fn peek_token(&mut self) -> &Token {
|
||||||
|
if self.tokens.is_empty() {
|
||||||
|
self.lex();
|
||||||
|
}
|
||||||
|
&self.tokens[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next token, moving the lexer forward
|
||||||
|
pub fn next_token(&mut self) -> Token {
|
||||||
|
if self.tokens.is_empty() {
|
||||||
|
self.lex();
|
||||||
|
}
|
||||||
|
self.tokens.pop_front().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_peek_next() {
|
||||||
|
let mut lexer = Lexer::new("test01");
|
||||||
|
assert_eq!(lexer.peek(), Some(&'t'));
|
||||||
|
assert_eq!(lexer.next(), Some('t'));
|
||||||
|
assert_eq!(lexer.peek(), Some(&'e'));
|
||||||
|
assert_eq!(lexer.peek(), Some(&'e'));
|
||||||
|
assert_eq!(lexer.next(), Some('e'));
|
||||||
|
assert_eq!(lexer.next(), Some('s'));
|
||||||
|
assert_eq!(lexer.next(), Some('t'));
|
||||||
|
assert_eq!(lexer.next(), Some('0'));
|
||||||
|
assert_eq!(lexer.peek(), Some(&'1'));
|
||||||
|
assert_eq!(lexer.next(), Some('1'));
|
||||||
|
assert_eq!(lexer.peek(), None);
|
||||||
|
assert_eq!(lexer.next(), None);
|
||||||
|
assert_eq!(lexer.peek(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tokens() {
|
||||||
|
let mut lexer = Lexer::new("let test02 = 4 << 1");
|
||||||
|
|
||||||
|
use TokenKind::*;
|
||||||
|
|
||||||
|
assert_eq!(lexer.peek_token().kind, Keyword(TokenKeyword::Let));
|
||||||
|
assert_eq!(lexer.next_token().kind, Keyword(TokenKeyword::Let));
|
||||||
|
|
||||||
|
let mut token = lexer.next_token();
|
||||||
|
assert_eq!(token.kind, Identifier);
|
||||||
|
assert_eq!(*token.val, *"test02");
|
||||||
|
|
||||||
|
assert_eq!(lexer.next_token().kind, Symbol(TokenSymbol::Eq));
|
||||||
|
|
||||||
|
token = lexer.next_token();
|
||||||
|
assert_eq!(token.kind, Literal(TokenLiteral::Int));
|
||||||
|
assert_eq!(*token.val, *"4");
|
||||||
|
|
||||||
|
assert_eq!(lexer.next_token().kind, Symbol(TokenSymbol::Shl));
|
||||||
|
|
||||||
|
assert_eq!(lexer.peek_token().kind, Literal(TokenLiteral::Int));
|
||||||
|
assert_eq!(*lexer.peek_token().val, *"1");
|
||||||
|
token = lexer.next_token();
|
||||||
|
assert_eq!(token.kind, Literal(TokenLiteral::Int));
|
||||||
|
assert_eq!(*token.val, *"1");
|
||||||
|
|
||||||
|
assert_eq!(lexer.peek_token().kind, Eof);
|
||||||
|
assert_eq!(lexer.next_token().kind, Eof);
|
||||||
|
assert_eq!(lexer.peek_token().kind, Eof);
|
||||||
|
assert_eq!(lexer.next_token().kind, Eof);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tokens_2() {
|
||||||
|
let mut lexer = Lexer::new("let test03: char = 'h'");
|
||||||
|
|
||||||
|
use TokenKind::*;
|
||||||
|
|
||||||
|
assert_eq!(lexer.peek_token().kind, Keyword(TokenKeyword::Let));
|
||||||
|
assert_eq!(lexer.next_token().kind, Keyword(TokenKeyword::Let));
|
||||||
|
|
||||||
|
let mut token = lexer.next_token();
|
||||||
|
assert_eq!(token.kind, Identifier);
|
||||||
|
assert_eq!(*token.val, *"test03");
|
||||||
|
|
||||||
|
assert_eq!(lexer.next_token().kind, Symbol(TokenSymbol::Colon));
|
||||||
|
assert_eq!(lexer.next_token().kind, Keyword(TokenKeyword::Char));
|
||||||
|
assert_eq!(lexer.next_token().kind, Symbol(TokenSymbol::Eq));
|
||||||
|
|
||||||
|
assert_eq!(lexer.peek_token().kind, Literal(TokenLiteral::Char));
|
||||||
|
assert_eq!(*lexer.peek_token().val, *"'h'");
|
||||||
|
token = lexer.next_token();
|
||||||
|
assert_eq!(token.kind, Literal(TokenLiteral::Char));
|
||||||
|
assert_eq!(*token.val, *"'h'");
|
||||||
|
|
||||||
|
assert_eq!(lexer.peek_token().kind, Eof);
|
||||||
|
assert_eq!(lexer.next_token().kind, Eof);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tokens_eof() {
|
||||||
|
let mut lexer = Lexer::new("");
|
||||||
|
|
||||||
|
assert_eq!(lexer.peek_token().kind, TokenKind::Eof);
|
||||||
|
assert_eq!(lexer.next_token().kind, TokenKind::Eof);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tokens_numeric() {
|
||||||
|
let mut lexer = Lexer::new("3342");
|
||||||
|
|
||||||
|
let token = lexer.next_token();
|
||||||
|
assert_eq!(token.kind, TokenKind::Literal(TokenLiteral::Int));
|
||||||
|
assert_eq!(*token.val, *"3342");
|
||||||
|
|
||||||
|
assert_eq!(lexer.next_token().kind, TokenKind::Eof);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tokens_numeric_2() {
|
||||||
|
let mut lexer = Lexer::new("334.2e");
|
||||||
|
|
||||||
|
let token = lexer.next_token();
|
||||||
|
assert_eq!(token.kind, TokenKind::Literal(TokenLiteral::Float));
|
||||||
|
assert_eq!(*token.val, *"334.2e");
|
||||||
|
|
||||||
|
assert_eq!(lexer.next_token().kind, TokenKind::Eof);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tokens_numeric_3() {
|
||||||
|
let mut lexer = Lexer::new("334.2e-5");
|
||||||
|
|
||||||
|
let mut token = lexer.next_token();
|
||||||
|
assert_eq!(token.kind, TokenKind::Literal(TokenLiteral::Float));
|
||||||
|
assert_eq!(*token.val, *"334.2e");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
lexer.next_token().kind,
|
||||||
|
TokenKind::Symbol(TokenSymbol::Minus)
|
||||||
|
);
|
||||||
|
|
||||||
|
token = lexer.next_token();
|
||||||
|
assert_eq!(token.kind, TokenKind::Literal(TokenLiteral::Int));
|
||||||
|
assert_eq!(*token.val, *"5");
|
||||||
|
|
||||||
|
assert_eq!(lexer.next_token().kind, TokenKind::Eof);
|
||||||
|
}
|
||||||
|
@@ -1 +1,4 @@
|
|||||||
pub mod args;
|
pub mod args;
|
||||||
|
pub mod ast;
|
||||||
|
pub mod lexer;
|
||||||
|
pub mod parser;
|
||||||
|
40
src/main.rs
40
src/main.rs
@@ -1,6 +1,40 @@
|
|||||||
use tricc::args;
|
use std::{
|
||||||
|
fs,
|
||||||
|
panic,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tricc::args::Args;
|
||||||
|
use tricc::parser::Parser;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let file: String = args::handle();
|
panic::set_hook(Box::new(|panic_info| {
|
||||||
println!("{}", file);
|
if let Some(msg) = panic_info.payload().downcast_ref::<&str>() {
|
||||||
|
eprintln!("{}", msg);
|
||||||
|
} else if let Some(msg) = panic_info.payload().downcast_ref::<String>() {
|
||||||
|
eprintln!("{}", msg);
|
||||||
|
} else if let Some(location) = panic_info.location() {
|
||||||
|
eprintln!(
|
||||||
|
"panic occurred in file '{}' at line {}",
|
||||||
|
location.file(),
|
||||||
|
location.line(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
eprintln!("panic occurred");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
let mut args = Args::default();
|
||||||
|
args.handle();
|
||||||
|
|
||||||
|
let file = args.get_file();
|
||||||
|
let content = fs::read_to_string(&file).expect("Couldn't read the file");
|
||||||
|
let mut parser = Parser::new(&content);
|
||||||
|
let Some(parent) = parser.parse() else {
|
||||||
|
eprintln!(
|
||||||
|
"Failed to parse {} - See the errors above",
|
||||||
|
file.to_string_lossy()
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
};
|
||||||
|
println!("Parsed AST:\n{:#?}", parent);
|
||||||
}
|
}
|
||||||
|
215
src/parser/entity.rs
Normal file
215
src/parser/entity.rs
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
use super::Parser;
|
||||||
|
use crate::ast::*;
|
||||||
|
use crate::lexer::{
|
||||||
|
TokenDelimiter,
|
||||||
|
TokenKeyword,
|
||||||
|
TokenKind,
|
||||||
|
TokenSymbol,
|
||||||
|
};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
/// entity ::= module | class | fn | static
|
||||||
|
pub(super) fn parse_entity(&mut self) -> Option<Entity> {
|
||||||
|
use TokenKeyword::*;
|
||||||
|
let token = self.peek_token();
|
||||||
|
|
||||||
|
if let TokenKind::Keyword(keyword) = &token.kind {
|
||||||
|
Some(match keyword {
|
||||||
|
Module => Entity::Module(self.parse_module()?),
|
||||||
|
Class => Entity::Class(self.parse_class()?),
|
||||||
|
Fn => Entity::Fn(self.parse_fn()?),
|
||||||
|
Static => Entity::Static(self.parse_static()?),
|
||||||
|
_ => {
|
||||||
|
self.error_expected_peek("entity");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.error_expected_peek("entity");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// module ::= "module" ident "{" { module | fn | static | class } "}"
|
||||||
|
fn parse_module(&mut self) -> Option<Module> {
|
||||||
|
self.next_token();
|
||||||
|
|
||||||
|
let name = self.parse_ident()?;
|
||||||
|
let mut children = vec![];
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Delimiter(TokenDelimiter::BraceOpen)) {
|
||||||
|
self.error_expected_peek("{");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
use TokenKeyword::*;
|
||||||
|
self.trim_newlines();
|
||||||
|
if let TokenKind::Keyword(keyword) = &self.peek_token().kind {
|
||||||
|
children.push(match keyword {
|
||||||
|
Module => ModuleChildren::Module(self.parse_module()?),
|
||||||
|
Fn => ModuleChildren::Fn(self.parse_fn()?),
|
||||||
|
Static => ModuleChildren::Static(self.parse_static()?),
|
||||||
|
Class => ModuleChildren::Class(self.parse_class()?),
|
||||||
|
_ => {
|
||||||
|
self.error_expected_peek("module child");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if !self.check_newline_or_tok(TokenKind::Delimiter(TokenDelimiter::BraceClose)) {
|
||||||
|
self.error_expected_peek("newline or }");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else if !self.skip_token(TokenKind::Delimiter(TokenDelimiter::BraceClose)) {
|
||||||
|
self.error_expected_peek("}");
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Module { name, children })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// class ::= "class" ident "{" { fn | static | let } "}"
|
||||||
|
fn parse_class(&mut self) -> Option<Class> {
|
||||||
|
self.next_token();
|
||||||
|
|
||||||
|
let name = self.parse_ident()?;
|
||||||
|
let mut children = vec![];
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Delimiter(TokenDelimiter::BraceOpen)) {
|
||||||
|
self.error_expected_peek("{");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
use TokenKeyword::*;
|
||||||
|
self.trim_newlines();
|
||||||
|
if let TokenKind::Keyword(keyword) = &self.peek_token().kind {
|
||||||
|
children.push(match keyword {
|
||||||
|
Fn => ClassChildren::Fn(self.parse_fn()?),
|
||||||
|
Static => ClassChildren::Static(self.parse_static()?),
|
||||||
|
Let => ClassChildren::Let(self.parse_let()?),
|
||||||
|
_ => {
|
||||||
|
self.error_expected_peek("class child");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if !self.check_newline_or_tok(TokenKind::Delimiter(TokenDelimiter::BraceClose)) {
|
||||||
|
self.error_expected_peek("newline or }");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else if !self.skip_token(TokenKind::Delimiter(TokenDelimiter::BraceClose)) {
|
||||||
|
self.error_expected_peek("}");
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Class { name, children })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// fn ::= "fn" ident "(" [ identWithTy { "," identWithTy } ] ")" [ ":" ty ]
|
||||||
|
/// "{" { statement } "}"
|
||||||
|
fn parse_fn(&mut self) -> Option<Fn> {
|
||||||
|
self.next_token();
|
||||||
|
|
||||||
|
let name = self.parse_ident()?;
|
||||||
|
let mut params: Vec<(Rc<str>, Ty)> = vec![];
|
||||||
|
let mut return_typ: Option<Ty> = None;
|
||||||
|
let mut children: Vec<Statement> = vec![];
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Delimiter(TokenDelimiter::ParenOpen)) {
|
||||||
|
self.error_expected_peek("(");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if self.peek_token().kind == TokenKind::Identifier {
|
||||||
|
params.push(self.parse_ident_with_ty()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Symbol(TokenSymbol::Comma)) {
|
||||||
|
if !self.skip_token(TokenKind::Delimiter(TokenDelimiter::ParenClose)) {
|
||||||
|
self.error_expected_peek(", or )");
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.skip_token(TokenKind::Symbol(TokenSymbol::Colon)) {
|
||||||
|
return_typ = Some(self.parse_ty()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Delimiter(TokenDelimiter::BraceOpen)) {
|
||||||
|
self.error_expected_peek("{");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
self.trim_newlines();
|
||||||
|
if self.skip_token(TokenKind::Delimiter(TokenDelimiter::BraceClose)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
children.push(self.parse_statement()?);
|
||||||
|
if !self.check_newline_or_tok(TokenKind::Delimiter(TokenDelimiter::BraceClose)) {
|
||||||
|
self.error_expected_peek("newline or }");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Fn {
|
||||||
|
name,
|
||||||
|
return_ty: return_typ,
|
||||||
|
params,
|
||||||
|
children,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_entity() {
|
||||||
|
let mut parser = Parser::new(
|
||||||
|
r#"module module01 {
|
||||||
|
class class01 {
|
||||||
|
fn fn01(param01: char, param02: float) {
|
||||||
|
static let let01: int = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fn02 (): int { }
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parser.parse_entity(),
|
||||||
|
Some(Entity::Module(Module {
|
||||||
|
name: "module01".into(),
|
||||||
|
children: vec![
|
||||||
|
ModuleChildren::Class(Class {
|
||||||
|
name: "class01".into(),
|
||||||
|
children: vec![ClassChildren::Fn(Fn {
|
||||||
|
name: "fn01".into(),
|
||||||
|
return_ty: None,
|
||||||
|
params: vec![("param01".into(), Ty::Char), ("param02".into(), Ty::Float)],
|
||||||
|
children: vec![Statement::Static(Let {
|
||||||
|
name: "let01".into(),
|
||||||
|
ty: Ty::Int,
|
||||||
|
expr: Some(Expr::Literal(Literal::Int(4)))
|
||||||
|
})]
|
||||||
|
})]
|
||||||
|
}),
|
||||||
|
ModuleChildren::Fn(Fn {
|
||||||
|
name: "fn02".into(),
|
||||||
|
return_ty: Some(Ty::Int),
|
||||||
|
params: vec![],
|
||||||
|
children: vec![]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
435
src/parser/expr.rs
Normal file
435
src/parser/expr.rs
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
use super::Parser;
|
||||||
|
use crate::ast::{
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
use crate::lexer::{
|
||||||
|
TokenDelimiter,
|
||||||
|
TokenKeyword,
|
||||||
|
TokenKind,
|
||||||
|
TokenLiteral,
|
||||||
|
TokenSymbol,
|
||||||
|
};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
/// exprIf ::= "if" expr block [ else (block | exprIf ) ]
|
||||||
|
fn parse_expr_if(&mut self) -> Option<If> {
|
||||||
|
// skip "if"
|
||||||
|
self.next_token();
|
||||||
|
|
||||||
|
let cond = Box::new(self.parse_expr()?);
|
||||||
|
let then = self.parse_expr_block()?;
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Keyword(TokenKeyword::Else)) {
|
||||||
|
return Some(If {
|
||||||
|
cond,
|
||||||
|
then,
|
||||||
|
or: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.peek_token().kind != TokenKind::Keyword(TokenKeyword::If) {
|
||||||
|
return Some(If {
|
||||||
|
cond,
|
||||||
|
then,
|
||||||
|
or: Some(Box::new(ElseType::Else(self.parse_expr_block()?))),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(If {
|
||||||
|
cond,
|
||||||
|
then,
|
||||||
|
or: Some(Box::new(ElseType::If(self.parse_expr_if()?))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprBlock ::= "{" { statement } "}"
|
||||||
|
fn parse_expr_block(&mut self) -> Option<Vec<Statement>> {
|
||||||
|
let mut statements = vec![];
|
||||||
|
|
||||||
|
// skip {
|
||||||
|
self.next_token();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
self.trim_newlines();
|
||||||
|
if self.skip_token(TokenKind::Delimiter(TokenDelimiter::BraceClose)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
statements.push(self.parse_statement()?);
|
||||||
|
if !self.check_newline_or_tok(TokenKind::Delimiter(TokenDelimiter::BraceClose)) {
|
||||||
|
self.error_expected_peek("newline or }");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(statements)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprLoop ::= "loop" exprBlock
|
||||||
|
fn parse_expr_loop(&mut self) -> Option<Vec<Statement>> {
|
||||||
|
self.next_token();
|
||||||
|
if self.peek_token().kind != TokenKind::Delimiter(TokenDelimiter::BraceOpen) {
|
||||||
|
self.error_expected_peek("{");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.parse_expr_block()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprAtom ::= ( "(" expr ")" ) | ident | int | float | char | exprBlock | exprLoop | exprIf
|
||||||
|
fn parse_expr_atom(&mut self) -> Option<Expr> {
|
||||||
|
use ast::Literal::*;
|
||||||
|
use TokenKind::*;
|
||||||
|
|
||||||
|
// TODO: check lvalue validity in the analysis phase
|
||||||
|
Some(match self.peek_token().kind {
|
||||||
|
Delimiter(TokenDelimiter::ParenOpen) => {
|
||||||
|
self.next_token(); // skip (
|
||||||
|
|
||||||
|
let expr = self.parse_expr()?;
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Delimiter(TokenDelimiter::ParenClose)) {
|
||||||
|
self.error_expected_peek(")");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
Identifier => {
|
||||||
|
let token = self.next_token();
|
||||||
|
Expr::Identifier(Rc::clone(&token.val))
|
||||||
|
}
|
||||||
|
Literal(TokenLiteral::Int) => Expr::Literal(Int(self.parse_int()?)),
|
||||||
|
Literal(TokenLiteral::Float) => Expr::Literal(Float(self.parse_float()?)),
|
||||||
|
Literal(TokenLiteral::Char) => Expr::Literal(Char(self.parse_char()?)),
|
||||||
|
Delimiter(TokenDelimiter::BraceOpen) => Expr::Block(self.parse_expr_block()?),
|
||||||
|
Keyword(TokenKeyword::Loop) => Expr::Loop(self.parse_expr_loop()?),
|
||||||
|
Keyword(TokenKeyword::If) => Expr::If(self.parse_expr_if()?),
|
||||||
|
_ => {
|
||||||
|
self.error_expected_peek("expression");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprUnary ::= [ unaryOp ] exprAtom
|
||||||
|
/// unaryOp ::= "+" | "-" | "~"
|
||||||
|
fn parse_expr_unary(&mut self) -> Option<Expr> {
|
||||||
|
use TokenSymbol::*;
|
||||||
|
Some(match self.peek_token().kind {
|
||||||
|
TokenKind::Symbol(symbol @ (Minus | Plus | Tilde)) => {
|
||||||
|
self.next_token();
|
||||||
|
Expr::Op(symbol, Box::new(self.parse_expr_atom()?), None)
|
||||||
|
}
|
||||||
|
_ => self.parse_expr_atom()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprArithmeticMul ::= exprUnary [ arithmeticMulOp exprArithmeticMul ]
|
||||||
|
/// arithmeticMulOp ::= "*" | "/" | "%"
|
||||||
|
fn parse_expr_arithmetic_mul(&mut self) -> Option<Expr> {
|
||||||
|
use TokenSymbol::*;
|
||||||
|
let lhs = self.parse_expr_unary()?;
|
||||||
|
|
||||||
|
Some(match self.peek_token().kind {
|
||||||
|
TokenKind::Symbol(symbol @ (Star | Slash | Percent)) => {
|
||||||
|
self.next_token();
|
||||||
|
Expr::Op(
|
||||||
|
symbol,
|
||||||
|
Box::new(lhs),
|
||||||
|
Some(Box::new(self.parse_expr_arithmetic_mul()?)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => lhs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprArithmeticAdd ::= exprArithmeticMul [ arithmeticAddOp exprArithmeticAdd ]
|
||||||
|
/// arithmeticAddOp ::= "+" | "-"
|
||||||
|
fn parse_expr_arithmetic_add(&mut self) -> Option<Expr> {
|
||||||
|
use TokenSymbol::*;
|
||||||
|
let lhs = self.parse_expr_arithmetic_mul()?;
|
||||||
|
|
||||||
|
Some(match self.peek_token().kind {
|
||||||
|
TokenKind::Symbol(symbol @ (Plus | Minus)) => {
|
||||||
|
self.next_token();
|
||||||
|
Expr::Op(
|
||||||
|
symbol,
|
||||||
|
Box::new(lhs),
|
||||||
|
Some(Box::new(self.parse_expr_arithmetic_add()?)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => lhs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprBitwiseShift ::= exprArithmeticAdd [ bitwiseShiftOp exprBitwiseShift ]
|
||||||
|
/// bitwiseShiftOp ::= "<<" | ">>"
|
||||||
|
fn parse_expr_bitwise_shift(&mut self) -> Option<Expr> {
|
||||||
|
use TokenSymbol::*;
|
||||||
|
let lhs = self.parse_expr_arithmetic_add()?;
|
||||||
|
|
||||||
|
Some(match self.peek_token().kind {
|
||||||
|
TokenKind::Symbol(symbol @ (Shl | Shr)) => {
|
||||||
|
self.next_token();
|
||||||
|
Expr::Op(
|
||||||
|
symbol,
|
||||||
|
Box::new(lhs),
|
||||||
|
Some(Box::new(self.parse_expr_bitwise_shift()?)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => lhs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprBitwiseAnd ::= exprBitwiseShift [ "&" exprBitwiseAnd ]
|
||||||
|
fn parse_expr_bitwise_and(&mut self) -> Option<Expr> {
|
||||||
|
let lhs = self.parse_expr_bitwise_shift()?;
|
||||||
|
let symbol = TokenSymbol::And;
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Symbol(symbol)) {
|
||||||
|
return Some(lhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Expr::Op(
|
||||||
|
symbol,
|
||||||
|
Box::new(lhs),
|
||||||
|
Some(Box::new(self.parse_expr_bitwise_and()?)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprBitwiseXor ::= exprBitwiseAnd [ "^" exprBitwiseXor ]
|
||||||
|
fn parse_expr_bitwise_xor(&mut self) -> Option<Expr> {
|
||||||
|
let lhs = self.parse_expr_bitwise_and()?;
|
||||||
|
let symbol = TokenSymbol::Caret;
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Symbol(symbol)) {
|
||||||
|
return Some(lhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Expr::Op(
|
||||||
|
symbol,
|
||||||
|
Box::new(lhs),
|
||||||
|
Some(Box::new(self.parse_expr_bitwise_xor()?)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprBiwiseOr ::= exprBitwiseXor [ "|" exprBitwiseOr ]
|
||||||
|
fn parse_expr_bitwise_or(&mut self) -> Option<Expr> {
|
||||||
|
let lhs = self.parse_expr_bitwise_xor()?;
|
||||||
|
let symbol = TokenSymbol::Or;
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Symbol(symbol)) {
|
||||||
|
return Some(lhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Expr::Op(
|
||||||
|
symbol,
|
||||||
|
Box::new(lhs),
|
||||||
|
Some(Box::new(self.parse_expr_bitwise_or()?)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprAssign ::= exprBitwiseOr [ relationalOp exprRelational ]
|
||||||
|
/// relationalOp ::= ">" | "<" | ">=" | "<=" | "==" | "!="
|
||||||
|
fn parse_expr_relational(&mut self) -> Option<Expr> {
|
||||||
|
use TokenSymbol::*;
|
||||||
|
let lhs = self.parse_expr_bitwise_or()?;
|
||||||
|
|
||||||
|
Some(match self.peek_token().kind {
|
||||||
|
TokenKind::Symbol(symbol @ (Gt | Lt | GtEq | LtEq | EqEq | Ne)) => {
|
||||||
|
self.next_token();
|
||||||
|
Expr::Op(
|
||||||
|
symbol,
|
||||||
|
Box::new(lhs),
|
||||||
|
Some(Box::new(self.parse_expr_relational()?)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => lhs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprLogicalAnd ::= exprLogicalRelational [ "&&" exprLogicalAnd ]
|
||||||
|
fn parse_expr_logical_and(&mut self) -> Option<Expr> {
|
||||||
|
let lhs = self.parse_expr_relational()?;
|
||||||
|
let symbol = TokenSymbol::AndAnd;
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Symbol(symbol)) {
|
||||||
|
return Some(lhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Expr::Op(
|
||||||
|
symbol,
|
||||||
|
Box::new(lhs),
|
||||||
|
Some(Box::new(self.parse_expr_logical_and()?)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprLogicalOr ::= exprLogicalAnd [ "||" exprLogicalOr ]
|
||||||
|
fn parse_expr_logical_or(&mut self) -> Option<Expr> {
|
||||||
|
let lhs = self.parse_expr_logical_and()?;
|
||||||
|
let symbol = TokenSymbol::OrOr;
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Symbol(symbol)) {
|
||||||
|
return Some(lhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Expr::Op(
|
||||||
|
symbol,
|
||||||
|
Box::new(lhs),
|
||||||
|
Some(Box::new(self.parse_expr_logical_or()?)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprAssign ::= exprLogicalOr [ assignOp exprAssign ]
|
||||||
|
/// assignOp ::= "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "^=" | "<<=" | ">>=" | "&=" | "|="
|
||||||
|
fn parse_expr_assign(&mut self) -> Option<Expr> {
|
||||||
|
use TokenSymbol::*;
|
||||||
|
let lhs = self.parse_expr_logical_or()?;
|
||||||
|
|
||||||
|
Some(match self.peek_token().kind {
|
||||||
|
TokenKind::Symbol(
|
||||||
|
symbol @ (Eq | PlusEq | MinusEq | StarEq | SlashEq | PercentEq | CaretEq | ShlEq
|
||||||
|
| ShrEq | AndEq | OrEq),
|
||||||
|
) => {
|
||||||
|
self.next_token();
|
||||||
|
Expr::Op(
|
||||||
|
symbol,
|
||||||
|
Box::new(lhs),
|
||||||
|
Some(Box::new(self.parse_expr_assign()?)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => lhs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// exprControl ::= "continue" | "break" | "return" [ exprControl ] | exprAssign
|
||||||
|
fn parse_expr_control(&mut self) -> Option<Expr> {
|
||||||
|
use TokenKeyword::*;
|
||||||
|
|
||||||
|
Some(match self.peek_token().kind {
|
||||||
|
TokenKind::Keyword(Continue) => {
|
||||||
|
self.next_token();
|
||||||
|
Expr::Continue
|
||||||
|
}
|
||||||
|
TokenKind::Keyword(Break) => {
|
||||||
|
self.next_token();
|
||||||
|
Expr::Break
|
||||||
|
}
|
||||||
|
TokenKind::Keyword(Return) => {
|
||||||
|
self.next_token();
|
||||||
|
Expr::Return(self.parse_expr_control().map(Box::new))
|
||||||
|
}
|
||||||
|
_ => self.parse_expr_assign()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// entrypoint for expression parsing using recursive descent parsing
|
||||||
|
///
|
||||||
|
/// <https://en.wikipedia.org/wiki/Recursive_descent_parser>
|
||||||
|
/// expr ::= exprControl
|
||||||
|
pub(super) fn parse_expr(&mut self) -> Option<Expr> {
|
||||||
|
self.parse_expr_control()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_expr() {
|
||||||
|
use Literal::*;
|
||||||
|
use TokenSymbol::*;
|
||||||
|
|
||||||
|
macro_rules! b {
|
||||||
|
($expr:expr) => {
|
||||||
|
Box::new($expr)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parser = Parser::new(
|
||||||
|
r#"if if 1 { 1 } else { 0 } + 9 {
|
||||||
|
a = 4
|
||||||
|
} else if 1 {
|
||||||
|
a = 5
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
amul ^= (4 + 93 * (1 << 3) / 1.44) ^ bhatura
|
||||||
|
stove = { 44 } + amul"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parser.parse_expr(),
|
||||||
|
Some(Expr::If(If {
|
||||||
|
cond: b!(Expr::Op(
|
||||||
|
Plus,
|
||||||
|
b!(Expr::If(If {
|
||||||
|
cond: b!(Expr::Literal(Int(1))),
|
||||||
|
then: vec![Statement::Expr(Expr::Literal(Int(1)))],
|
||||||
|
or: Some(b!(ElseType::Else(vec![Statement::Expr(Expr::Literal(
|
||||||
|
Int(0)
|
||||||
|
))])))
|
||||||
|
})),
|
||||||
|
Some(b!(Expr::Literal(Int(9))))
|
||||||
|
)),
|
||||||
|
then: vec![Statement::Expr(Expr::Op(
|
||||||
|
Eq,
|
||||||
|
b!(Expr::Identifier("a".into())),
|
||||||
|
Some(b!(Expr::Literal(Int(4))))
|
||||||
|
))],
|
||||||
|
or: Some(b!(ElseType::If(If {
|
||||||
|
cond: b!(Expr::Literal(Int(1))),
|
||||||
|
then: vec![Statement::Expr(Expr::Op(
|
||||||
|
Eq,
|
||||||
|
b!(Expr::Identifier("a".into())),
|
||||||
|
Some(b!(Expr::Literal(Int(5))))
|
||||||
|
))],
|
||||||
|
or: Some(b!(ElseType::Else(vec![])))
|
||||||
|
})))
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(parser.skip_token(TokenKind::Newline), true);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parser.parse_expr(),
|
||||||
|
Some(Expr::Op(
|
||||||
|
CaretEq,
|
||||||
|
b!(Expr::Identifier("amul".into())),
|
||||||
|
Some(b!(Expr::Op(
|
||||||
|
Caret,
|
||||||
|
b!(Expr::Op(
|
||||||
|
Plus,
|
||||||
|
b!(Expr::Literal(Int(4))),
|
||||||
|
Some(b!(Expr::Op(
|
||||||
|
Star,
|
||||||
|
b!(Expr::Literal(Int(93))),
|
||||||
|
Some(b!(Expr::Op(
|
||||||
|
Slash,
|
||||||
|
b!(Expr::Op(
|
||||||
|
Shl,
|
||||||
|
b!(Expr::Literal(Int(1))),
|
||||||
|
Some(b!(Expr::Literal(Int(3))))
|
||||||
|
)),
|
||||||
|
Some(b!(Expr::Literal(Float(1.44))))
|
||||||
|
)))
|
||||||
|
)))
|
||||||
|
)),
|
||||||
|
Some(b!(Expr::Identifier("bhatura".into())))
|
||||||
|
)))
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(parser.skip_token(TokenKind::Newline), true);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parser.parse_expr(),
|
||||||
|
Some(Expr::Op(
|
||||||
|
Eq,
|
||||||
|
b!(Expr::Identifier("stove".into())),
|
||||||
|
Some(b!(Expr::Op(
|
||||||
|
Plus,
|
||||||
|
b!(Expr::Block(vec![Statement::Expr(Expr::Literal(Int(44)))])),
|
||||||
|
Some(b!(Expr::Identifier("amul".into())))
|
||||||
|
)))
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
121
src/parser/literal.rs
Normal file
121
src/parser/literal.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use super::Parser;
|
||||||
|
use crate::lexer::{
|
||||||
|
TokenKind,
|
||||||
|
TokenLiteral,
|
||||||
|
TokenSymbol,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
/// int ::= digit { digit }
|
||||||
|
pub(super) fn parse_int(&mut self) -> Option<i32> {
|
||||||
|
let val = self.next_token().val;
|
||||||
|
let mut integer: i32 = 0;
|
||||||
|
let error = || {
|
||||||
|
self.error(&format!(
|
||||||
|
"integer values must be in range [{}, {}]",
|
||||||
|
i32::MIN,
|
||||||
|
i32::MAX
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
for c in val.chars() {
|
||||||
|
// c is always ['0'..='9']
|
||||||
|
let d = c.to_digit(10)?;
|
||||||
|
|
||||||
|
match integer.checked_mul(10) {
|
||||||
|
Some(m) => integer = m,
|
||||||
|
None => {
|
||||||
|
error();
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match integer.checked_add(d as i32) {
|
||||||
|
Some(a) => integer = a,
|
||||||
|
None => {
|
||||||
|
error();
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(integer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// didnt use parse() because i wanted to do this myself for some reason
|
||||||
|
/// f32 can be NaN and inf as well
|
||||||
|
/// float ::= int [ "." { digit } ] [ "e" { digit } ]
|
||||||
|
pub(super) fn parse_float(&mut self) -> Option<f32> {
|
||||||
|
let token = self.next_token();
|
||||||
|
let mut chars = token.val.chars();
|
||||||
|
let mut float: f32 = 0.0;
|
||||||
|
let mut fraction: f32 = 0.0;
|
||||||
|
let mut prec: i32 = 0;
|
||||||
|
let mut exp: i32 = 0;
|
||||||
|
let mut decimal: bool = false;
|
||||||
|
|
||||||
|
// lexer takes care of multiple decimals and non digit characters
|
||||||
|
for c in chars.by_ref() {
|
||||||
|
match c {
|
||||||
|
'.' => decimal = true,
|
||||||
|
'e' | 'E' => {
|
||||||
|
// lexer takes care that decimal doesnt come after e
|
||||||
|
let s;
|
||||||
|
match self.peek_token().kind {
|
||||||
|
TokenKind::Symbol(TokenSymbol::Minus) => {
|
||||||
|
s = -1;
|
||||||
|
self.next_token();
|
||||||
|
}
|
||||||
|
TokenKind::Symbol(TokenSymbol::Plus) => {
|
||||||
|
s = 1;
|
||||||
|
self.next_token();
|
||||||
|
}
|
||||||
|
_ => s = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.peek_token().kind != TokenKind::Literal(TokenLiteral::Int) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
exp = self.parse_int()? * s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// c is always ['0'..='9']
|
||||||
|
let d = c.to_digit(10)? as f32;
|
||||||
|
if decimal {
|
||||||
|
fraction *= 10.0;
|
||||||
|
fraction += d;
|
||||||
|
prec += 1;
|
||||||
|
} else {
|
||||||
|
float *= 10.0;
|
||||||
|
float += d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fraction /= 10f32.powi(prec);
|
||||||
|
float += fraction;
|
||||||
|
float *= 10f32.powi(exp);
|
||||||
|
|
||||||
|
Some(float)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// char ::= "'" letter "'"
|
||||||
|
pub(super) fn parse_char(&mut self) -> Option<char> {
|
||||||
|
// the lexer ensures that the 0th and 2nd characters are both '
|
||||||
|
self.next_token().val.chars().nth(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_literals() {
|
||||||
|
let mut parser = Parser::new("4524 3123.15e4 9e2 9083482.429455 'c' 3331.13.1");
|
||||||
|
assert_eq!(parser.parse_int(), Some(4524));
|
||||||
|
assert_eq!(parser.parse_float(), Some(3123.15e4));
|
||||||
|
assert_eq!(parser.parse_float(), Some(9e2));
|
||||||
|
assert_eq!(parser.parse_float(), Some(9083482.429455));
|
||||||
|
assert_eq!(parser.parse_char(), Some('c'));
|
||||||
|
assert_eq!(parser.next_token().kind, TokenKind::Invalid);
|
||||||
|
}
|
156
src/parser/mod.rs
Normal file
156
src/parser/mod.rs
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
//! A naive parser just to get started
|
||||||
|
//!
|
||||||
|
//! Can only parse module, class and function declaration now along with let statements
|
||||||
|
|
||||||
|
mod entity;
|
||||||
|
mod expr;
|
||||||
|
mod literal;
|
||||||
|
mod statement;
|
||||||
|
|
||||||
|
use crate::ast::{
|
||||||
|
Parent,
|
||||||
|
Ty,
|
||||||
|
};
|
||||||
|
use crate::lexer::{
|
||||||
|
Lexer,
|
||||||
|
Token,
|
||||||
|
TokenKeyword,
|
||||||
|
TokenKind,
|
||||||
|
TokenSymbol,
|
||||||
|
};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub struct Parser<'a> {
|
||||||
|
pub lexer: Lexer<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
/// Creates a new [`Parser`] instance.
|
||||||
|
pub fn new(contents: &'a str) -> Parser<'a> {
|
||||||
|
Parser {
|
||||||
|
lexer: Lexer::new(contents),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn error(&self, message: &str) {
|
||||||
|
eprintln!(
|
||||||
|
"Parser: {}, at \"{}:{}\"",
|
||||||
|
message, self.lexer.line, self.lexer.col
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn error_expected(&self, expected: &str, found: &str) {
|
||||||
|
self.error(&format!("expected {}, found {}", expected, found));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn error_expected_peek(&mut self, expected: &str) {
|
||||||
|
let found = &Rc::clone(&self.peek_token().val);
|
||||||
|
self.error_expected(expected, found);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next_token(&mut self) -> Token {
|
||||||
|
let t = self.lexer.next_token();
|
||||||
|
println!("{:?}", t);
|
||||||
|
t
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn peek_token(&mut self) -> &Token {
|
||||||
|
return self.lexer.peek_token();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// newline ::= "}\n"
|
||||||
|
fn trim_newlines(&mut self) {
|
||||||
|
while self.peek_token().kind == TokenKind::Newline {
|
||||||
|
self.next_token();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_token(&mut self, kind: TokenKind) -> bool {
|
||||||
|
if self.peek_token().kind == kind {
|
||||||
|
self.next_token();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_newline_or_tok(&mut self, token: TokenKind) -> bool {
|
||||||
|
match self.peek_token().kind {
|
||||||
|
TokenKind::Newline => true,
|
||||||
|
d if d == token => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ty ::= "int" | "float" | "char"
|
||||||
|
fn parse_ty(&mut self) -> Option<Ty> {
|
||||||
|
let ty: Ty;
|
||||||
|
|
||||||
|
if let TokenKind::Keyword(keyword) = &self.peek_token().kind {
|
||||||
|
ty = match keyword {
|
||||||
|
TokenKeyword::Int => Ty::Int,
|
||||||
|
TokenKeyword::Char => Ty::Char,
|
||||||
|
TokenKeyword::Float => Ty::Float,
|
||||||
|
_ => {
|
||||||
|
self.error_expected_peek("ty");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
self.error_expected_peek("ty");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.next_token();
|
||||||
|
Some(ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ident ::= ( letter | "_" ) { letter | digit | "_" }
|
||||||
|
fn parse_ident(&mut self) -> Option<Rc<str>> {
|
||||||
|
if self.peek_token().kind != TokenKind::Identifier {
|
||||||
|
self.error_expected_peek("identifier");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Rc::clone(&self.next_token().val))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// identWithTy ::= letter ":" ty
|
||||||
|
fn parse_ident_with_ty(&mut self) -> Option<(Rc<str>, Ty)> {
|
||||||
|
let ident = self.parse_ident()?;
|
||||||
|
|
||||||
|
if !self.skip_token(TokenKind::Symbol(TokenSymbol::Colon)) {
|
||||||
|
self.error_expected_peek(":");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((ident, self.parse_ty()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an [`Entity`] vector after parsing
|
||||||
|
///
|
||||||
|
/// parent ::= { entity }
|
||||||
|
/// [`Entity`]: crate::ast::Entity
|
||||||
|
pub fn parse(&mut self) -> Option<Parent> {
|
||||||
|
let mut parent = vec![];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.peek_token().kind {
|
||||||
|
TokenKind::Newline => self.trim_newlines(),
|
||||||
|
TokenKind::Eof => break,
|
||||||
|
_ => {
|
||||||
|
parent.push(self.parse_entity()?);
|
||||||
|
if !self.check_newline_or_tok(TokenKind::Eof) {
|
||||||
|
self.error_expected_peek("newline or end of file");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(parent)
|
||||||
|
}
|
||||||
|
}
|
91
src/parser/statement.rs
Normal file
91
src/parser/statement.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use super::Parser;
|
||||||
|
use crate::ast::*;
|
||||||
|
use crate::lexer::{
|
||||||
|
TokenKeyword,
|
||||||
|
TokenKind,
|
||||||
|
TokenSymbol,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
/// statement ::= static | let | expr
|
||||||
|
pub(super) fn parse_statement(&mut self) -> Option<Statement> {
|
||||||
|
use TokenKeyword::*;
|
||||||
|
println!("STMT");
|
||||||
|
|
||||||
|
Some(match self.peek_token().kind {
|
||||||
|
TokenKind::Keyword(Static) => Statement::Static(self.parse_static()?),
|
||||||
|
TokenKind::Keyword(Let) => Statement::Let(self.parse_let()?),
|
||||||
|
_ => Statement::Expr(self.parse_expr()?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// static ::="static" let
|
||||||
|
pub(super) fn parse_static(&mut self) -> Option<Let> {
|
||||||
|
self.next_token();
|
||||||
|
|
||||||
|
if self.peek_token().kind != TokenKind::Keyword(TokenKeyword::Let) {
|
||||||
|
self.error_expected_peek("let");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.parse_let()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// let ::= "let" identWithTy "=" expr
|
||||||
|
pub(super) fn parse_let(&mut self) -> Option<Let> {
|
||||||
|
self.next_token();
|
||||||
|
|
||||||
|
let (name, ty) = self.parse_ident_with_ty()?;
|
||||||
|
|
||||||
|
let expr = if self.skip_token(TokenKind::Symbol(TokenSymbol::Eq)) {
|
||||||
|
self.parse_expr()
|
||||||
|
} else if self.peek_token().kind == TokenKind::Newline {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.error_expected_peek("= or newline");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Let { name, ty, expr })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_let() {
|
||||||
|
use Literal::*;
|
||||||
|
|
||||||
|
let mut parser = Parser::new(
|
||||||
|
r#"static let test01: int = 4
|
||||||
|
let test02: char = '6'
|
||||||
|
static let test03: float
|
||||||
|
let test04 = 9"#,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parser.parse_static(),
|
||||||
|
Some(Let {
|
||||||
|
name: "test01".into(),
|
||||||
|
ty: Ty::Int,
|
||||||
|
expr: Some(Expr::Literal(Int(4)))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(parser.skip_token(TokenKind::Newline), true);
|
||||||
|
assert_eq!(
|
||||||
|
parser.parse_let(),
|
||||||
|
Some(Let {
|
||||||
|
name: "test02".into(),
|
||||||
|
ty: Ty::Char,
|
||||||
|
expr: Some(Expr::Literal(Char('6')))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(parser.skip_token(TokenKind::Newline), true);
|
||||||
|
assert_eq!(
|
||||||
|
parser.parse_static(),
|
||||||
|
Some(Let {
|
||||||
|
name: "test03".into(),
|
||||||
|
ty: Ty::Float,
|
||||||
|
expr: None
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(parser.skip_token(TokenKind::Newline), true);
|
||||||
|
assert_eq!(parser.parse_let(), None);
|
||||||
|
}
|
Reference in New Issue
Block a user