Implement import-pass
This commit is contained in:
parent
464156b2dc
commit
a71843dfe9
@ -1,12 +1,14 @@
|
|||||||
use std::{env, fs};
|
use std::{env, fs, path::PathBuf};
|
||||||
|
|
||||||
use reid::compile;
|
use reid::compile;
|
||||||
|
|
||||||
fn main() -> Result<(), std::io::Error> {
|
fn main() -> Result<(), std::io::Error> {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
if let Some(filename) = args.get(1) {
|
if let Some(filename) = args.get(1) {
|
||||||
let text = fs::read_to_string(filename)?;
|
let path = PathBuf::from(filename);
|
||||||
let output = match compile(&text) {
|
|
||||||
|
let text = fs::read_to_string(&path)?;
|
||||||
|
let output = match compile(&text, PathBuf::from(path)) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => panic!("{}", e),
|
Err(e) => panic!("{}", e),
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use reid::mir::{self, *};
|
use reid::mir::{self, *};
|
||||||
use reid_lib::Context;
|
use reid_lib::Context;
|
||||||
|
|
||||||
@ -159,7 +161,9 @@ fn main() {
|
|||||||
name: "test module".to_owned(),
|
name: "test module".to_owned(),
|
||||||
imports: vec![],
|
imports: vec![],
|
||||||
functions: vec![fibonacci, main],
|
functions: vec![fibonacci, main],
|
||||||
|
path: None,
|
||||||
}],
|
}],
|
||||||
|
base: PathBuf::new(),
|
||||||
};
|
};
|
||||||
println!("test1");
|
println!("test1");
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
//! This is the module that contains relevant code to parsing Reid, that is to
|
//! This is the module that contains relevant code to parsing Reid, that is to
|
||||||
//! say transforming a Vec of FullTokens into a loose parsed AST that can be
|
//! say transforming a Vec of FullTokens into a loose parsed AST that can be
|
||||||
//! used for unwrapping syntax sugar, and then be transformed into Reid MIR.
|
//! used for unwrapping syntax sugar, and then be transformed into Reid MIR.
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::token_stream::TokenRange;
|
use crate::token_stream::TokenRange;
|
||||||
|
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
@ -166,4 +168,5 @@ pub enum TopLevelStatement {
|
|||||||
pub struct Module {
|
pub struct Module {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub top_level_statements: Vec<TopLevelStatement>,
|
pub top_level_statements: Vec<TopLevelStatement>,
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{self},
|
ast::{self},
|
||||||
mir::{self, NamedVariableRef, StmtKind},
|
mir::{self, NamedVariableRef, StmtKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl mir::Context {
|
impl mir::Context {
|
||||||
pub fn from(modules: Vec<ast::Module>) -> mir::Context {
|
pub fn from(modules: Vec<mir::Module>, base: PathBuf) -> mir::Context {
|
||||||
mir::Context {
|
mir::Context { modules, base }
|
||||||
modules: modules.iter().map(|m| m.process()).collect(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ast::Module {
|
impl ast::Module {
|
||||||
fn process(&self) -> mir::Module {
|
pub fn process(&self) -> mir::Module {
|
||||||
let mut imports = Vec::new();
|
let mut imports = Vec::new();
|
||||||
let mut functions = Vec::new();
|
let mut functions = Vec::new();
|
||||||
|
|
||||||
@ -67,6 +67,7 @@ impl ast::Module {
|
|||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
imports,
|
imports,
|
||||||
functions,
|
functions,
|
||||||
|
path: self.path.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,7 @@ pub fn tokenize<T: Into<String>>(to_tokenize: T) -> Result<Vec<FullToken>, Error
|
|||||||
Ok(tokens)
|
Ok(tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug, Clone)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Invalid token '{}' at Ln {}, Col {}", .0, (.1).1, (.1).0)]
|
#[error("Invalid token '{}' at Ln {}, Col {}", .0, (.1).1, (.1).0)]
|
||||||
InvalidToken(char, Position),
|
InvalidToken(char, Position),
|
||||||
|
@ -40,6 +40,8 @@
|
|||||||
//! - Loops
|
//! - Loops
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use mir::{typecheck::TypeCheck, typeinference::TypeInference, typerefs::TypeRefs};
|
use mir::{typecheck::TypeCheck, typeinference::TypeInference, typerefs::TypeRefs};
|
||||||
use reid_lib::Context;
|
use reid_lib::Context;
|
||||||
|
|
||||||
@ -53,7 +55,7 @@ mod pad_adapter;
|
|||||||
mod token_stream;
|
mod token_stream;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug, Clone)]
|
||||||
pub enum ReidError {
|
pub enum ReidError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
LexerError(#[from] lexer::Error),
|
LexerError(#[from] lexer::Error),
|
||||||
@ -63,10 +65,7 @@ pub enum ReidError {
|
|||||||
TypeCheckErrors(Vec<mir::pass::Error<mir::typecheck::ErrorKind>>),
|
TypeCheckErrors(Vec<mir::pass::Error<mir::typecheck::ErrorKind>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes in a bit of source code, parses and compiles it and produces `hello.o`
|
pub fn compile_module(source: &str, path: Option<PathBuf>) -> Result<mir::Module, ReidError> {
|
||||||
/// and `hello.asm` from it, which can be linked using `ld` to produce an
|
|
||||||
/// executable file.
|
|
||||||
pub fn compile(source: &str) -> Result<String, ReidError> {
|
|
||||||
let tokens = lexer::tokenize(source)?;
|
let tokens = lexer::tokenize(source)?;
|
||||||
|
|
||||||
dbg!(&tokens);
|
dbg!(&tokens);
|
||||||
@ -83,10 +82,20 @@ pub fn compile(source: &str) -> Result<String, ReidError> {
|
|||||||
let ast_module = ast::Module {
|
let ast_module = ast::Module {
|
||||||
name: "test".to_owned(),
|
name: "test".to_owned(),
|
||||||
top_level_statements: statements,
|
top_level_statements: statements,
|
||||||
|
path,
|
||||||
};
|
};
|
||||||
|
|
||||||
dbg!(&ast_module);
|
Ok(ast_module.process())
|
||||||
let mut mir_context = mir::Context::from(vec![ast_module]);
|
}
|
||||||
|
|
||||||
|
/// Takes in a bit of source code, parses and compiles it and produces `hello.o`
|
||||||
|
/// and `hello.asm` from it, which can be linked using `ld` to produce an
|
||||||
|
/// executable file.
|
||||||
|
pub fn compile(source: &str, path: PathBuf) -> Result<String, ReidError> {
|
||||||
|
let mut mir_context = mir::Context::from(
|
||||||
|
vec![compile_module(source, Some(path.clone()))?],
|
||||||
|
path.parent().unwrap().to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
println!("{}", &mir_context);
|
println!("{}", &mir_context);
|
||||||
|
|
||||||
|
120
reid/src/mir/imports.rs
Normal file
120
reid/src/mir/imports.rs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
use std::{collections::HashMap, convert::Infallible, fs, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::{compile_module, ReidError};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
pass::{Pass, PassState},
|
||||||
|
types::EqualsIssue,
|
||||||
|
Context, FunctionDefinition, Import, Module,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug, Clone)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
#[error("Unable to import inner modules, not yet supported: {0}")]
|
||||||
|
InnerModulesNotYetSupported(Import),
|
||||||
|
#[error("No such module: {0}")]
|
||||||
|
ModuleNotFound(String),
|
||||||
|
#[error("Error while compiling module {0}: {1}")]
|
||||||
|
ModuleCompilationError(String, ReidError),
|
||||||
|
#[error("No such function {0} found in module {1}")]
|
||||||
|
NoSuchFunctionInModule(String, String),
|
||||||
|
#[error("Importing function {0}::{1} not possible: {2}")]
|
||||||
|
FunctionImportIssue(String, String, EqualsIssue),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Struct used to implement a type-checking pass that can be performed on the
|
||||||
|
/// MIR.
|
||||||
|
pub struct ImportsPass {}
|
||||||
|
|
||||||
|
impl Pass for ImportsPass {
|
||||||
|
type TError = ErrorKind;
|
||||||
|
fn context(&mut self, context: &mut Context, mut state: PassState<Self::TError>) {
|
||||||
|
let mut modules = HashMap::<String, Module>::new();
|
||||||
|
|
||||||
|
for module in context.modules.clone() {
|
||||||
|
modules.insert(module.name.clone(), module);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut modules_to_process: Vec<_> = modules.values().cloned().collect();
|
||||||
|
|
||||||
|
let iter = modules_to_process.iter_mut();
|
||||||
|
|
||||||
|
for module in iter {
|
||||||
|
let mut new_modules = Vec::new();
|
||||||
|
|
||||||
|
for import in &module.imports {
|
||||||
|
let Import(path, _) = import;
|
||||||
|
if path.len() != 2 {
|
||||||
|
state.ok::<_, Infallible>(
|
||||||
|
Err(ErrorKind::InnerModulesNotYetSupported(import.clone())),
|
||||||
|
import.1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let module_name = unsafe { path.get_unchecked(0) };
|
||||||
|
|
||||||
|
let imported = if let Some(module) = modules.get(module_name) {
|
||||||
|
module
|
||||||
|
} else {
|
||||||
|
let file_path = PathBuf::from(&context.base.clone()).join(module_name);
|
||||||
|
|
||||||
|
let Ok(source) = fs::read_to_string(&file_path) else {
|
||||||
|
state.ok::<_, Infallible>(
|
||||||
|
Err(ErrorKind::ModuleNotFound(module_name.clone())),
|
||||||
|
import.1,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
match compile_module(&source, Some(file_path)) {
|
||||||
|
Ok(m) => {
|
||||||
|
new_modules.push(m);
|
||||||
|
new_modules.last().unwrap()
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
state.ok::<_, Infallible>(
|
||||||
|
Err(ErrorKind::ModuleCompilationError(module_name.clone(), err)),
|
||||||
|
import.1,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let func_name = unsafe { path.get_unchecked(1) };
|
||||||
|
|
||||||
|
let Some(func) = imported.functions.iter().find(|f| f.name == *func_name) else {
|
||||||
|
state.ok::<_, Infallible>(
|
||||||
|
Err(ErrorKind::NoSuchFunctionInModule(
|
||||||
|
module_name.clone(),
|
||||||
|
func_name.clone(),
|
||||||
|
)),
|
||||||
|
import.1,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(existing) = module.functions.iter().find(|f| f.name == *func_name) {
|
||||||
|
if let Err(e) = existing.equals_as_imported(func) {
|
||||||
|
state.ok::<_, Infallible>(
|
||||||
|
Err(ErrorKind::FunctionImportIssue(
|
||||||
|
module_name.clone(),
|
||||||
|
func_name.clone(),
|
||||||
|
e,
|
||||||
|
)),
|
||||||
|
import.1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.functions.push(FunctionDefinition {
|
||||||
|
name: func.name.clone(),
|
||||||
|
is_pub: false,
|
||||||
|
return_type: func.return_type.clone(),
|
||||||
|
parameters: func.parameters.clone(),
|
||||||
|
kind: super::FunctionDefinitionKind::Extern,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,12 @@
|
|||||||
//! Reid. It contains a simplified version of Reid which can be e.g.
|
//! Reid. It contains a simplified version of Reid which can be e.g.
|
||||||
//! typechecked.
|
//! typechecked.
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::token_stream::TokenRange;
|
use crate::token_stream::TokenRange;
|
||||||
|
|
||||||
mod display;
|
mod display;
|
||||||
|
pub mod imports;
|
||||||
pub mod pass;
|
pub mod pass;
|
||||||
pub mod typecheck;
|
pub mod typecheck;
|
||||||
pub mod typeinference;
|
pub mod typeinference;
|
||||||
@ -205,13 +208,13 @@ pub enum ReturnKind {
|
|||||||
Soft,
|
Soft,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NamedVariableRef(pub TypeKind, pub String, pub Metadata);
|
pub struct NamedVariableRef(pub TypeKind, pub String, pub Metadata);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Import(pub Vec<String>, pub Metadata);
|
pub struct Import(pub Vec<String>, pub Metadata);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ExprKind {
|
pub enum ExprKind {
|
||||||
Variable(NamedVariableRef),
|
Variable(NamedVariableRef),
|
||||||
Index(Box<Expression>, TypeKind, u64),
|
Index(Box<Expression>, TypeKind, u64),
|
||||||
@ -223,21 +226,21 @@ pub enum ExprKind {
|
|||||||
Block(Block),
|
Block(Block),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Expression(pub ExprKind, pub Metadata);
|
pub struct Expression(pub ExprKind, pub Metadata);
|
||||||
|
|
||||||
/// Condition, Then, Else
|
/// Condition, Then, Else
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct IfExpression(pub Box<Expression>, pub Block, pub Option<Block>);
|
pub struct IfExpression(pub Box<Expression>, pub Block, pub Option<Block>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FunctionCall {
|
pub struct FunctionCall {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub return_type: TypeKind,
|
pub return_type: TypeKind,
|
||||||
pub parameters: Vec<Expression>,
|
pub parameters: Vec<Expression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FunctionDefinition {
|
pub struct FunctionDefinition {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub is_pub: bool,
|
pub is_pub: bool,
|
||||||
@ -246,7 +249,7 @@ pub struct FunctionDefinition {
|
|||||||
pub kind: FunctionDefinitionKind,
|
pub kind: FunctionDefinitionKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum FunctionDefinitionKind {
|
pub enum FunctionDefinitionKind {
|
||||||
/// Actual definition block and surrounding signature range
|
/// Actual definition block and surrounding signature range
|
||||||
Local(Block, Metadata),
|
Local(Block, Metadata),
|
||||||
@ -269,7 +272,7 @@ impl FunctionDefinition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
/// List of non-returning statements
|
/// List of non-returning statements
|
||||||
pub statements: Vec<Statement>,
|
pub statements: Vec<Statement>,
|
||||||
@ -277,22 +280,22 @@ pub struct Block {
|
|||||||
pub meta: Metadata,
|
pub meta: Metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Statement(pub StmtKind, pub Metadata);
|
pub struct Statement(pub StmtKind, pub Metadata);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct IndexedVariableReference {
|
pub struct IndexedVariableReference {
|
||||||
pub kind: IndexedVariableReferenceKind,
|
pub kind: IndexedVariableReferenceKind,
|
||||||
pub meta: Metadata,
|
pub meta: Metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum IndexedVariableReferenceKind {
|
pub enum IndexedVariableReferenceKind {
|
||||||
Named(NamedVariableRef),
|
Named(NamedVariableRef),
|
||||||
Index(Box<IndexedVariableReference>, u64),
|
Index(Box<IndexedVariableReference>, u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum StmtKind {
|
pub enum StmtKind {
|
||||||
/// Variable name++mutability+type, evaluation
|
/// Variable name++mutability+type, evaluation
|
||||||
Let(NamedVariableRef, bool, Expression),
|
Let(NamedVariableRef, bool, Expression),
|
||||||
@ -301,14 +304,16 @@ pub enum StmtKind {
|
|||||||
Expression(Expression),
|
Expression(Expression),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub imports: Vec<Import>,
|
pub imports: Vec<Import>,
|
||||||
pub functions: Vec<FunctionDefinition>,
|
pub functions: Vec<FunctionDefinition>,
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
pub modules: Vec<Module>,
|
pub modules: Vec<Module>,
|
||||||
|
pub base: PathBuf,
|
||||||
}
|
}
|
||||||
|
@ -191,6 +191,7 @@ impl<'st, 'sc, TError: STDError + Clone> PassState<'st, 'sc, TError> {
|
|||||||
pub trait Pass {
|
pub trait Pass {
|
||||||
type TError: STDError + Clone;
|
type TError: STDError + Clone;
|
||||||
|
|
||||||
|
fn context(&mut self, _context: &mut Context, mut _state: PassState<Self::TError>) {}
|
||||||
fn module(&mut self, _module: &mut Module, mut _state: PassState<Self::TError>) {}
|
fn module(&mut self, _module: &mut Module, mut _state: PassState<Self::TError>) {}
|
||||||
fn function(
|
fn function(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -207,6 +208,7 @@ impl Context {
|
|||||||
pub fn pass<T: Pass>(&mut self, pass: &mut T) -> State<T::TError> {
|
pub fn pass<T: Pass>(&mut self, pass: &mut T) -> State<T::TError> {
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let mut scope = Scope::default();
|
let mut scope = Scope::default();
|
||||||
|
pass.context(self, PassState::from(&mut state, &mut scope));
|
||||||
for module in &mut self.modules {
|
for module in &mut self.modules {
|
||||||
module.pass(pass, &mut state, &mut scope);
|
module.pass(pass, &mut state, &mut scope);
|
||||||
}
|
}
|
||||||
|
@ -180,3 +180,34 @@ impl IndexedVariableReference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
||||||
|
pub enum EqualsIssue {
|
||||||
|
#[error("Function is already defined locally at {:?}", (.0).range)]
|
||||||
|
ExistsLocally(Metadata),
|
||||||
|
#[error("asd")]
|
||||||
|
Equals,
|
||||||
|
#[error("asd")]
|
||||||
|
ConflictingImports,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionDefinition {
|
||||||
|
pub fn equals_as_imported(&self, other: &FunctionDefinition) -> Result<(), EqualsIssue> {
|
||||||
|
match &self.kind {
|
||||||
|
FunctionDefinitionKind::Local(_, metadata) => {
|
||||||
|
Err(EqualsIssue::ExistsLocally(*metadata))
|
||||||
|
}
|
||||||
|
FunctionDefinitionKind::Extern => {
|
||||||
|
if self.is_pub == other.is_pub
|
||||||
|
&& self.name == other.name
|
||||||
|
&& self.parameters == other.parameters
|
||||||
|
&& self.return_type == other.return_type
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(EqualsIssue::ConflictingImports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -205,7 +205,7 @@ impl std::iter::Sum for TokenRange {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug, Clone)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Expected {} at Ln {}, Col {}, got {:?}", .0, (.2).1, (.2).0, .1)]
|
#[error("Expected {} at Ln {}, Col {}, got {:?}", .0, (.2).1, (.2).0, .1)]
|
||||||
Expected(String, Token, Position),
|
Expected(String, Token, Position),
|
||||||
|
Loading…
Reference in New Issue
Block a user