Add rudamentary LLVM lib stuff, make a fully compiling executable
This commit is contained in:
		
							parent
							
								
									6d3d0fd03e
								
							
						
					
					
						commit
						5f93b7c9c2
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -3,3 +3,5 @@ src/old_llvm | |||||||
| /target | /target | ||||||
| /.vscode | /.vscode | ||||||
| .env | .env | ||||||
|  | hello.* | ||||||
|  | main | ||||||
							
								
								
									
										10
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| # This file is automatically @generated by Cargo. | # This file is automatically @generated by Cargo. | ||||||
| # It is not intended for manual editing. | # It is not intended for manual editing. | ||||||
| version = 3 | version = 4 | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "aho-corasick" | name = "aho-corasick" | ||||||
| @ -103,6 +103,14 @@ dependencies = [ | |||||||
|  "thiserror", |  "thiserror", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "reid-lib" | ||||||
|  | version = "0.1.0" | ||||||
|  | dependencies = [ | ||||||
|  |  "llvm-sys", | ||||||
|  |  "thiserror", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "semver" | name = "semver" | ||||||
| version = "1.0.18" | version = "1.0.18" | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| [workspace] | [workspace] | ||||||
| members = [ | members = [ | ||||||
|     "reid" |     "reid", | ||||||
|  |     "reid-llvm-lib" | ||||||
| ] | ] | ||||||
							
								
								
									
										11
									
								
								libtest.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								libtest.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | #!/bin/sh | ||||||
|  | 
 | ||||||
|  | # Compiles example libtest, which produces hello.o and hello.asm, which is then | ||||||
|  | # compiled with main.cpp and executed for final result | ||||||
|  | # | ||||||
|  | # Do note this file is extremely simply for my own personal convenience | ||||||
|  | 
 | ||||||
|  | export .env | ||||||
|  | cargo run --example libtest && \ | ||||||
|  | clang++ main.cpp hello.o -o main && \ | ||||||
|  | ./main | ||||||
							
								
								
									
										7
									
								
								main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								main.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #include <iostream> | ||||||
|  | 
 | ||||||
|  | extern "C" { | ||||||
|  | int mainfunc(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int main() { std::cout << "Return value of test: " << mainfunc() << std::endl; } | ||||||
							
								
								
									
										12
									
								
								reid-llvm-lib/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								reid-llvm-lib/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | [package] | ||||||
|  | name = "reid-lib" | ||||||
|  | version = "0.1.0" | ||||||
|  | edition = "2024" | ||||||
|  | 
 | ||||||
|  | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
|  | 
 | ||||||
|  | [dependencies] | ||||||
|  | ## LLVM Bindings | ||||||
|  | llvm-sys = "160" | ||||||
|  | ## Make it easier to generate errors | ||||||
|  | thiserror = "1.0.44" | ||||||
							
								
								
									
										25
									
								
								reid-llvm-lib/examples/libtest.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								reid-llvm-lib/examples/libtest.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | use reid_lib::*; | ||||||
|  | 
 | ||||||
|  | pub fn main() { | ||||||
|  |     let context = IRContext::new(); | ||||||
|  |     let module = IRModule::new(&context, &"hello".to_owned()); | ||||||
|  | 
 | ||||||
|  |     let mainfunc = IRFunction::new(&module, &"mainfunc".to_owned()); | ||||||
|  | 
 | ||||||
|  |     let block = IRBlock::new(&context, &"mainblock".to_owned()); | ||||||
|  | 
 | ||||||
|  |     let lhs_cmp = IRValue::from_const(&context, 100); | ||||||
|  |     let rhs_cmp = IRValue::from_const(&context, 200); | ||||||
|  | 
 | ||||||
|  |     let compare = block.less_than(lhs_cmp.into(), rhs_cmp.into()).unwrap(); | ||||||
|  | 
 | ||||||
|  |     let (lhs, rhs) = block.cond_br(&mainfunc, compare); | ||||||
|  | 
 | ||||||
|  |     lhs.ret(&mainfunc, IRValue::from_const(&context, 123).into()); | ||||||
|  |     rhs.ret(&mainfunc, IRValue::from_const(&context, 456).into()); | ||||||
|  | 
 | ||||||
|  |     match module.print_to_string() { | ||||||
|  |         Ok(v) => println!("{}", v), | ||||||
|  |         Err(e) => println!("Err: {:?}", e), | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										319
									
								
								reid-llvm-lib/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								reid-llvm-lib/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,319 @@ | |||||||
|  | use std::ffi::{CStr, CString, c_char}; | ||||||
|  | use std::marker::PhantomData; | ||||||
|  | use std::mem; | ||||||
|  | use std::ptr::{null, null_mut}; | ||||||
|  | 
 | ||||||
|  | use llvm_sys::analysis::LLVMVerifyModule; | ||||||
|  | use llvm_sys::target::{ | ||||||
|  |     LLVM_InitializeAllAsmParsers, LLVM_InitializeAllAsmPrinters, LLVM_InitializeAllTargetInfos, | ||||||
|  |     LLVM_InitializeAllTargetMCs, LLVM_InitializeAllTargets, LLVM_InitializeNativeAsmParser, | ||||||
|  |     LLVM_InitializeNativeTarget, LLVMInitializeAMDGPUAsmPrinter, LLVMInitializeX86Target, | ||||||
|  |     LLVMInitializeX86TargetInfo, LLVMInitializeX86TargetMC, LLVMSetModuleDataLayout, | ||||||
|  | }; | ||||||
|  | use llvm_sys::target_machine::{ | ||||||
|  |     LLVMCodeGenFileType, LLVMCreateTargetDataLayout, LLVMCreateTargetMachine, | ||||||
|  |     LLVMGetDefaultTargetTriple, LLVMGetFirstTarget, LLVMGetHostCPUFeatures, LLVMGetHostCPUName, | ||||||
|  |     LLVMGetTargetFromTriple, LLVMTargetMachineEmitToFile, LLVMTargetMachineEmitToMemoryBuffer, | ||||||
|  | }; | ||||||
|  | use llvm_sys::transforms::pass_manager_builder::{ | ||||||
|  |     LLVMPassManagerBuilderCreate, LLVMPassManagerBuilderPopulateModulePassManager, | ||||||
|  |     LLVMPassManagerBuilderSetOptLevel, | ||||||
|  | }; | ||||||
|  | use llvm_sys::{ | ||||||
|  |     LLVMBasicBlock, LLVMBuilder, LLVMContext, LLVMModule, LLVMType, LLVMValue, core::*, prelude::*, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | fn into_cstring<T: Into<String>>(value: T) -> CString { | ||||||
|  |     let string = value.into(); | ||||||
|  |     unsafe { CString::from_vec_with_nul_unchecked((string + "\0").into_bytes()) } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn from_cstring(value: *mut c_char) -> Option<String> { | ||||||
|  |     if value.is_null() { | ||||||
|  |         None | ||||||
|  |     } else { | ||||||
|  |         unsafe { CString::from_raw(value).into_string().ok() } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn cstring_to_err(value: *mut c_char) -> Result<(), String> { | ||||||
|  |     from_cstring(value) | ||||||
|  |         .filter(|s| !s.is_empty()) | ||||||
|  |         .map_or(Ok(()), |s| Err(s)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub trait IRType { | ||||||
|  |     const SIGNED: LLVMBool; | ||||||
|  |     unsafe fn llvm_type(context: &IRContext) -> LLVMTypeRef; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl IRType for bool { | ||||||
|  |     const SIGNED: LLVMBool = 0; | ||||||
|  |     unsafe fn llvm_type(context: &IRContext) -> LLVMTypeRef { | ||||||
|  |         unsafe { LLVMInt1TypeInContext(context.context) } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl IRType for i32 { | ||||||
|  |     const SIGNED: LLVMBool = 1; | ||||||
|  |     unsafe fn llvm_type(context: &IRContext) -> LLVMTypeRef { | ||||||
|  |         unsafe { LLVMInt32TypeInContext(context.context) } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct IRValue<T: IRType>(PhantomData<T>, IROpaqueValue); | ||||||
|  | 
 | ||||||
|  | impl<T: IRType> IRValue<T> { | ||||||
|  |     unsafe fn from_runtime(t: LLVMTypeRef, value: LLVMValueRef) -> IRValue<T> { | ||||||
|  |         IRValue(PhantomData, IROpaqueValue(t, value)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: IRType + Into<i64>> IRValue<T> { | ||||||
|  |     pub fn from_const(context: &IRContext, value: T) -> Self { | ||||||
|  |         unsafe { | ||||||
|  |             let t = T::llvm_type(context); | ||||||
|  |             let value = LLVMConstInt(t, value.into() as u64, T::SIGNED); | ||||||
|  |             IRValue(PhantomData, IROpaqueValue(t, value)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: IRType> From<IRValue<T>> for IROpaqueValue { | ||||||
|  |     fn from(value: IRValue<T>) -> Self { | ||||||
|  |         value.1 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct IROpaqueValue(LLVMTypeRef, LLVMValueRef); | ||||||
|  | 
 | ||||||
|  | pub struct IRContext { | ||||||
|  |     context: *mut LLVMContext, | ||||||
|  |     builder: *mut LLVMBuilder, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl IRContext { | ||||||
|  |     pub fn new() -> IRContext { | ||||||
|  |         unsafe { | ||||||
|  |             // Set up a context, module and builder in that context.
 | ||||||
|  |             let context = LLVMContextCreate(); | ||||||
|  |             let builder = LLVMCreateBuilderInContext(context); | ||||||
|  | 
 | ||||||
|  |             IRContext { context, builder } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Drop for IRContext { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         // Clean up. Values created in the context mostly get cleaned up there.
 | ||||||
|  |         unsafe { | ||||||
|  |             LLVMDisposeBuilder(self.builder); | ||||||
|  |             LLVMContextDispose(self.context); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct IRModule<'a> { | ||||||
|  |     context: &'a IRContext, | ||||||
|  |     module: *mut LLVMModule, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> IRModule<'a> { | ||||||
|  |     pub fn new(context: &'a IRContext, name: &String) -> IRModule<'a> { | ||||||
|  |         unsafe { | ||||||
|  |             let module = | ||||||
|  |                 LLVMModuleCreateWithNameInContext(into_cstring(name).as_ptr(), context.context); | ||||||
|  | 
 | ||||||
|  |             IRModule { context, module } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn print_to_string(&self) -> Result<&str, String> { | ||||||
|  |         unsafe { | ||||||
|  |             // let pmb = LLVMPassManagerBuilderCreate();
 | ||||||
|  |             // LLVMPassManagerBuilderSetOptLevel(pmb, 0);
 | ||||||
|  |             // let pm = LLVMCreatePassManager();
 | ||||||
|  |             // LLVMPassManagerBuilderPopulateModulePassManager(pmb, pm);
 | ||||||
|  |             // println!("{}", LLVMRunPassManager(pm, self.module));
 | ||||||
|  | 
 | ||||||
|  |             LLVM_InitializeAllTargets(); | ||||||
|  |             LLVM_InitializeAllTargetInfos(); | ||||||
|  |             LLVM_InitializeAllTargetMCs(); | ||||||
|  |             LLVM_InitializeAllAsmParsers(); | ||||||
|  |             LLVM_InitializeAllAsmPrinters(); | ||||||
|  | 
 | ||||||
|  |             let triple = LLVMGetDefaultTargetTriple(); | ||||||
|  | 
 | ||||||
|  |             let mut target: _ = null_mut(); | ||||||
|  |             let mut err: _ = null_mut(); | ||||||
|  |             LLVMGetTargetFromTriple(c"x86_64-unknown-linux-gnu".as_ptr(), &mut target, &mut err); | ||||||
|  |             println!("{:?}, {:?}", from_cstring(triple), target); | ||||||
|  |             cstring_to_err(err).unwrap(); | ||||||
|  | 
 | ||||||
|  |             let target_machine = LLVMCreateTargetMachine( | ||||||
|  |                 target, | ||||||
|  |                 triple, | ||||||
|  |                 c"generic".as_ptr(), | ||||||
|  |                 c"".as_ptr(), | ||||||
|  |                 llvm_sys::target_machine::LLVMCodeGenOptLevel::LLVMCodeGenLevelNone, | ||||||
|  |                 llvm_sys::target_machine::LLVMRelocMode::LLVMRelocDefault, | ||||||
|  |                 llvm_sys::target_machine::LLVMCodeModel::LLVMCodeModelDefault, | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             let data_layout = LLVMCreateTargetDataLayout(target_machine); | ||||||
|  |             LLVMSetModuleDataLayout(self.module, data_layout); | ||||||
|  | 
 | ||||||
|  |             let mut err = null_mut(); | ||||||
|  |             LLVMVerifyModule( | ||||||
|  |                 self.module, | ||||||
|  |                 llvm_sys::analysis::LLVMVerifierFailureAction::LLVMPrintMessageAction, | ||||||
|  |                 &mut err, | ||||||
|  |             ); | ||||||
|  |             cstring_to_err(err).unwrap(); | ||||||
|  | 
 | ||||||
|  |             let mut err = null_mut(); | ||||||
|  |             LLVMTargetMachineEmitToFile( | ||||||
|  |                 target_machine, | ||||||
|  |                 self.module, | ||||||
|  |                 CString::new("hello.asm").unwrap().into_raw(), | ||||||
|  |                 LLVMCodeGenFileType::LLVMAssemblyFile, | ||||||
|  |                 &mut err, | ||||||
|  |             ); | ||||||
|  |             cstring_to_err(err).unwrap(); | ||||||
|  | 
 | ||||||
|  |             let mut err = null_mut(); | ||||||
|  |             LLVMTargetMachineEmitToFile( | ||||||
|  |                 target_machine, | ||||||
|  |                 self.module, | ||||||
|  |                 CString::new("hello.o").unwrap().into_raw(), | ||||||
|  |                 LLVMCodeGenFileType::LLVMObjectFile, | ||||||
|  |                 &mut err, | ||||||
|  |             ); | ||||||
|  |             cstring_to_err(err).unwrap(); | ||||||
|  | 
 | ||||||
|  |             Ok(CStr::from_ptr(LLVMPrintModuleToString(self.module)) | ||||||
|  |                 .to_str() | ||||||
|  |                 .expect("UTF8-err")) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> Drop for IRModule<'a> { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         // Clean up. Values created in the context mostly get cleaned up there.
 | ||||||
|  |         unsafe { | ||||||
|  |             LLVMDisposeModule(self.module); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct IRFunction<'a> { | ||||||
|  |     pub module: &'a IRModule<'a>, | ||||||
|  |     pub functionref: *mut LLVMValue, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> IRFunction<'a> { | ||||||
|  |     pub fn new(module: &'a IRModule<'a>, name: &String) -> IRFunction<'a> { | ||||||
|  |         unsafe { | ||||||
|  |             // TODO, fix later!
 | ||||||
|  |             let return_type = LLVMInt32TypeInContext(module.context.context); | ||||||
|  |             let mut argts = []; | ||||||
|  |             let func_type = | ||||||
|  |                 LLVMFunctionType(return_type, argts.as_mut_ptr(), argts.len() as u32, 0); | ||||||
|  | 
 | ||||||
|  |             let functionref = | ||||||
|  |                 LLVMAddFunction(module.module, into_cstring(name).as_ptr(), func_type); | ||||||
|  | 
 | ||||||
|  |             IRFunction { | ||||||
|  |                 module, | ||||||
|  |                 functionref, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct IRBlock<'a> { | ||||||
|  |     context: &'a IRContext, | ||||||
|  |     blockref: *mut LLVMBasicBlock, | ||||||
|  |     inserted: bool, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> IRBlock<'a> { | ||||||
|  |     pub fn new(context: &'a IRContext, name: &String) -> IRBlock<'a> { | ||||||
|  |         unsafe { | ||||||
|  |             let blockref = | ||||||
|  |                 LLVMCreateBasicBlockInContext(context.context, into_cstring(name).as_ptr()); | ||||||
|  | 
 | ||||||
|  |             IRBlock { | ||||||
|  |                 context, | ||||||
|  |                 blockref, | ||||||
|  |                 inserted: false, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn less_than(&self, lhs: IROpaqueValue, rhs: IROpaqueValue) -> Result<IRValue<bool>, ()> { | ||||||
|  |         let IROpaqueValue(t1, lhs) = lhs; | ||||||
|  |         let IROpaqueValue(t2, rhs) = rhs; | ||||||
|  | 
 | ||||||
|  |         if t1 != t2 { | ||||||
|  |             Err(()) | ||||||
|  |         } else { | ||||||
|  |             unsafe { | ||||||
|  |                 let builder = self.context.builder; | ||||||
|  |                 LLVMPositionBuilderAtEnd(builder, self.blockref); | ||||||
|  |                 let value = LLVMBuildICmp( | ||||||
|  |                     builder, | ||||||
|  |                     llvm_sys::LLVMIntPredicate::LLVMIntSLT, | ||||||
|  |                     lhs, | ||||||
|  |                     rhs, | ||||||
|  |                     c"asd".as_ptr(), | ||||||
|  |                 ); | ||||||
|  |                 Ok(IRValue::from_runtime(bool::llvm_type(&self.context), value)) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn cond_br( | ||||||
|  |         self, | ||||||
|  |         function: &IRFunction, | ||||||
|  |         value: IRValue<bool>, | ||||||
|  |     ) -> (IRBlock<'a>, IRBlock<'a>) { | ||||||
|  |         let lhs = IRBlock::new(self.context, &"lhs".to_owned()); | ||||||
|  |         let rhs = IRBlock::new(self.context, &"rhs".to_owned()); | ||||||
|  |         unsafe { | ||||||
|  |             let builder = self.context.builder; | ||||||
|  |             LLVMPositionBuilderAtEnd(builder, self.blockref); | ||||||
|  |             LLVMBuildCondBr(builder, value.1.1, lhs.blockref, rhs.blockref); | ||||||
|  |             self.append(function); | ||||||
|  |             (lhs, rhs) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn ret(self, function: &IRFunction, value: IROpaqueValue) { | ||||||
|  |         unsafe { | ||||||
|  |             let builder = self.context.builder; | ||||||
|  |             LLVMPositionBuilderAtEnd(builder, self.blockref); | ||||||
|  |             LLVMBuildRet(builder, value.1); | ||||||
|  |             self.append(function); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     unsafe fn append(mut self, function: &IRFunction<'a>) { | ||||||
|  |         unsafe { | ||||||
|  |             LLVMAppendExistingBasicBlock(function.functionref, self.blockref); | ||||||
|  |             self.inserted = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> Drop for IRBlock<'a> { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         unsafe { | ||||||
|  |             if !self.inserted { | ||||||
|  |                 LLVMDeleteBasicBlock(self.blockref); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -4,8 +4,12 @@ use std::mem; | |||||||
| use std::ptr::null_mut; | use std::ptr::null_mut; | ||||||
| 
 | 
 | ||||||
| use llvm_sys::analysis::LLVMVerifyModule; | use llvm_sys::analysis::LLVMVerifyModule; | ||||||
|  | use llvm_sys::transforms::pass_manager_builder::{ | ||||||
|  |     self, LLVMOpaquePassManagerBuilder, LLVMPassManagerBuilderCreate, | ||||||
|  |     LLVMPassManagerBuilderSetOptLevel, | ||||||
|  | }; | ||||||
| use llvm_sys::{ | use llvm_sys::{ | ||||||
|     core::*, prelude::*, LLVMBasicBlock, LLVMBuilder, LLVMContext, LLVMModule, LLVMType, LLVMValue, |     LLVMBasicBlock, LLVMBuilder, LLVMContext, LLVMModule, LLVMType, LLVMValue, core::*, prelude::*, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use crate::ast; | use crate::ast; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user