forked from AbleOS/holey-bytes
some stuff
This commit is contained in:
parent
aef9951bc5
commit
e494785f93
|
@ -7,6 +7,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
let mut generated = String::new();
|
let mut generated = String::new();
|
||||||
|
|
||||||
|
writeln!(generated, "#![allow(dead_code)]")?;
|
||||||
gen_max_size(&mut generated)?;
|
gen_max_size(&mut generated)?;
|
||||||
gen_encodes(&mut generated)?;
|
gen_encodes(&mut generated)?;
|
||||||
gen_structs(&mut generated)?;
|
gen_structs(&mut generated)?;
|
||||||
|
|
17
hblang/examples/generic_types.hb
Normal file
17
hblang/examples/generic_types.hb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
Vec := fn(Elem: type): type {
|
||||||
|
return struct {
|
||||||
|
data: ^Elem,
|
||||||
|
len: uint,
|
||||||
|
cap: uint,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
main := fn(): int {
|
||||||
|
i := 69;
|
||||||
|
vec := Vec(int).{
|
||||||
|
data: &i,
|
||||||
|
len: 1,
|
||||||
|
cap: 1,
|
||||||
|
};
|
||||||
|
return *vec.data;
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ main := fn(): int {
|
||||||
return *b - 2;
|
return *b - 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
modify := fn(a: *int): void {
|
modify := fn(a: ^int): void {
|
||||||
*a = 2;
|
*a = 2;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ main := fn(): int {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pass := fn(t: *Ty): int {
|
pass := fn(t: ^Ty): int {
|
||||||
return t.a - t.b;
|
return t.a - t.b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,10 +11,6 @@ impl Token {
|
||||||
pub fn range(&self) -> std::ops::Range<usize> {
|
pub fn range(&self) -> std::ops::Range<usize> {
|
||||||
self.start as usize..self.end as usize
|
self.start as usize..self.end as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> u32 {
|
|
||||||
self.end - self.start
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! gen_token_kind {
|
macro_rules! gen_token_kind {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![feature(if_let_guard)]
|
#![feature(if_let_guard)]
|
||||||
|
#![feature(slice_partition_dedup)]
|
||||||
#![feature(noop_waker)]
|
#![feature(noop_waker)]
|
||||||
#![feature(portable_simd)]
|
#![feature(portable_simd)]
|
||||||
#![feature(iter_collect_into)]
|
#![feature(iter_collect_into)]
|
||||||
|
@ -7,9 +8,17 @@
|
||||||
#![feature(ptr_metadata)]
|
#![feature(ptr_metadata)]
|
||||||
#![feature(const_mut_refs)]
|
#![feature(const_mut_refs)]
|
||||||
#![feature(slice_ptr_get)]
|
#![feature(slice_ptr_get)]
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use std::{collections::VecDeque, sync::Mutex};
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
io::{self, Read},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use parser::Ast;
|
||||||
|
|
||||||
|
use crate::parser::FileId;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! run_tests {
|
macro_rules! run_tests {
|
||||||
|
@ -128,6 +137,288 @@ impl<T> TaskQueueInner<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_all(threads: usize) -> io::Result<Vec<Ast>> {
|
||||||
|
const GIT_DEPS_DIR: &str = "git-deps";
|
||||||
|
|
||||||
|
enum ImportPath<'a> {
|
||||||
|
Root {
|
||||||
|
path: &'a str,
|
||||||
|
},
|
||||||
|
Rel {
|
||||||
|
path: &'a str,
|
||||||
|
},
|
||||||
|
Git {
|
||||||
|
link: &'a str,
|
||||||
|
path: &'a str,
|
||||||
|
branch: Option<&'a str>,
|
||||||
|
tag: Option<&'a str>,
|
||||||
|
rev: Option<&'a str>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for ImportPath<'a> {
|
||||||
|
type Error = ParseImportError;
|
||||||
|
|
||||||
|
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||||
|
let (prefix, path) = value.split_once(':').unwrap_or(("", value));
|
||||||
|
|
||||||
|
match prefix {
|
||||||
|
"" => Ok(Self::Root { path }),
|
||||||
|
"rel" => Ok(Self::Rel { path }),
|
||||||
|
"git" => {
|
||||||
|
let (link, path) =
|
||||||
|
path.split_once(':').ok_or(ParseImportError::ExpectedPath)?;
|
||||||
|
let (link, params) = link.split_once('?').unwrap_or((link, ""));
|
||||||
|
let [mut branch, mut tag, mut rev] = [None; 3];
|
||||||
|
for (key, value) in params.split('&').filter_map(|s| s.split_once('=')) {
|
||||||
|
match key {
|
||||||
|
"branch" => branch = Some(value),
|
||||||
|
"tag" => tag = Some(value),
|
||||||
|
"rev" => rev = Some(value),
|
||||||
|
_ => return Err(ParseImportError::UnexpectedParam),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self::Git {
|
||||||
|
link,
|
||||||
|
path,
|
||||||
|
branch,
|
||||||
|
tag,
|
||||||
|
rev,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => Err(ParseImportError::InvalidPrefix),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preprocess_git(link: &str) -> &str {
|
||||||
|
let link = link.strip_prefix("https://").unwrap_or(link);
|
||||||
|
link.strip_suffix(".git").unwrap_or(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ImportPath<'a> {
|
||||||
|
fn resolve(&self, from: &str) -> Result<PathBuf, CantLoadFile> {
|
||||||
|
match self {
|
||||||
|
Self::Root { path } => Ok(Path::new(path).to_owned()),
|
||||||
|
Self::Rel { path } => {
|
||||||
|
let path = PathBuf::from_iter([from, path]);
|
||||||
|
match path.canonicalize() {
|
||||||
|
Ok(path) => Ok(path),
|
||||||
|
Err(e) => Err(CantLoadFile(path, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Git { path, link, .. } => {
|
||||||
|
let link = preprocess_git(link);
|
||||||
|
Ok(PathBuf::from_iter([GIT_DEPS_DIR, link, path]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ParseImportError {
|
||||||
|
ExpectedPath,
|
||||||
|
InvalidPrefix,
|
||||||
|
UnexpectedParam,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ParseImportError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::ExpectedPath => write!(f, "expected path"),
|
||||||
|
Self::InvalidPrefix => write!(
|
||||||
|
f,
|
||||||
|
"invalid prefix, expected one of rel, \
|
||||||
|
git or none followed by colon"
|
||||||
|
),
|
||||||
|
Self::UnexpectedParam => {
|
||||||
|
write!(f, "unexpected git param, expected branch, tag or rev")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ParseImportError {}
|
||||||
|
|
||||||
|
impl From<ParseImportError> for io::Error {
|
||||||
|
fn from(e: ParseImportError) -> Self {
|
||||||
|
io::Error::new(io::ErrorKind::InvalidInput, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CantLoadFile(PathBuf, io::Error);
|
||||||
|
|
||||||
|
impl std::fmt::Display for CantLoadFile {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "can't load file: {}", self.0.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for CantLoadFile {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
Some(&self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CantLoadFile> for io::Error {
|
||||||
|
fn from(e: CantLoadFile) -> Self {
|
||||||
|
io::Error::new(io::ErrorKind::InvalidData, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct InvalidFileData(std::str::Utf8Error);
|
||||||
|
|
||||||
|
impl std::fmt::Display for InvalidFileData {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "invalid file data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for InvalidFileData {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
Some(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InvalidFileData> for io::Error {
|
||||||
|
fn from(e: InvalidFileData) -> Self {
|
||||||
|
io::Error::new(io::ErrorKind::InvalidData, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Task = (FileId, PathBuf, Option<std::process::Command>);
|
||||||
|
|
||||||
|
let seen = Mutex::new(HashMap::<PathBuf, FileId>::default());
|
||||||
|
let tasks = TaskQueue::<Task>::new(threads);
|
||||||
|
let ast = Mutex::new(Vec::<io::Result<Ast>>::new());
|
||||||
|
|
||||||
|
let loader = |path: &str, from: &str| {
|
||||||
|
let path = ImportPath::try_from(path)?;
|
||||||
|
|
||||||
|
let physiscal_path = path.resolve(from)?;
|
||||||
|
|
||||||
|
let id = {
|
||||||
|
let mut seen = seen.lock().unwrap();
|
||||||
|
let len = seen.len();
|
||||||
|
match seen.entry(physiscal_path.clone()) {
|
||||||
|
std::collections::hash_map::Entry::Occupied(entry) => {
|
||||||
|
return Ok(*entry.get());
|
||||||
|
}
|
||||||
|
std::collections::hash_map::Entry::Vacant(entry) => {
|
||||||
|
entry.insert(len as _);
|
||||||
|
len as FileId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let command = if !physiscal_path.exists() {
|
||||||
|
let ImportPath::Git {
|
||||||
|
link,
|
||||||
|
branch,
|
||||||
|
rev,
|
||||||
|
tag,
|
||||||
|
..
|
||||||
|
} = path
|
||||||
|
else {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::NotFound,
|
||||||
|
format!("can't find file: {}", physiscal_path.display()),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let root = PathBuf::from_iter([GIT_DEPS_DIR, preprocess_git(link)]);
|
||||||
|
|
||||||
|
let mut command = std::process::Command::new("git");
|
||||||
|
command
|
||||||
|
.args(["clone", "--depth", "1"])
|
||||||
|
.args(branch.map(|b| ["--branch", b]).into_iter().flatten())
|
||||||
|
.args(tag.map(|t| ["--tag", t]).into_iter().flatten())
|
||||||
|
.args(rev.map(|r| ["--rev", r]).into_iter().flatten())
|
||||||
|
.arg(link)
|
||||||
|
.arg(root);
|
||||||
|
Some(command)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
tasks.push((id, physiscal_path, command));
|
||||||
|
Ok(id)
|
||||||
|
};
|
||||||
|
|
||||||
|
let execute_task = |(_, path, command): Task, buffer: &mut Vec<u8>| {
|
||||||
|
if let Some(mut command) = command {
|
||||||
|
let output = command.output()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
let msg = format!(
|
||||||
|
"git command failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = path.to_str().ok_or_else(|| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!("path contains invalid characters: {}", path.display()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut file = std::fs::File::open(path)?;
|
||||||
|
file.read_to_end(buffer)?;
|
||||||
|
let src = std::str::from_utf8(buffer).map_err(InvalidFileData)?;
|
||||||
|
Ok(Ast::new(path, src, &loader))
|
||||||
|
};
|
||||||
|
|
||||||
|
let thread = || {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
while let Some(task @ (indx, ..)) = tasks.pop() {
|
||||||
|
let res = execute_task(task, &mut buffer);
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
let mut ast = ast.lock().unwrap();
|
||||||
|
let len = ast.len().max(indx as usize + 1);
|
||||||
|
ast.resize_with(len, || Err(io::ErrorKind::InvalidData.into()));
|
||||||
|
ast[indx as usize] = res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread::scope(|s| (0..threads).for_each(|_| _ = s.spawn(thread)));
|
||||||
|
|
||||||
|
ast.into_inner()
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<io::Result<Vec<_>>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
type HashMap<K, V> = std::collections::HashMap<K, V, FnvBuildHash>;
|
||||||
|
|
||||||
|
type FnvBuildHash = std::hash::BuildHasherDefault<FnvHasher>;
|
||||||
|
|
||||||
|
struct FnvHasher(u64);
|
||||||
|
|
||||||
|
impl std::hash::Hasher for FnvHasher {
|
||||||
|
fn finish(&self) -> u64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, bytes: &[u8]) {
|
||||||
|
self.0 = bytes.iter().fold(self.0, |hash, &byte| {
|
||||||
|
let mut hash = hash;
|
||||||
|
hash ^= byte as u64;
|
||||||
|
hash = hash.wrapping_mul(0x100000001B3);
|
||||||
|
hash
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FnvHasher {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0xCBF29CE484222325)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, UnsafeCell},
|
cell::{Cell, UnsafeCell},
|
||||||
collections::HashMap,
|
io,
|
||||||
io::{self, Read},
|
|
||||||
ops::{Deref, Not},
|
ops::{Deref, Not},
|
||||||
path::{Path, PathBuf},
|
|
||||||
ptr::NonNull,
|
ptr::NonNull,
|
||||||
sync::{atomic::AtomicUsize, Mutex},
|
sync::atomic::AtomicUsize,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
codegen::bt,
|
codegen::bt,
|
||||||
ident::{self, Ident},
|
ident::{self, Ident},
|
||||||
lexer::{Lexer, LineMap, Token, TokenKind},
|
lexer::{Lexer, LineMap, Token, TokenKind},
|
||||||
TaskQueue,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type Pos = u32;
|
pub type Pos = u32;
|
||||||
|
@ -21,267 +18,25 @@ pub type Symbols = Vec<Symbol>;
|
||||||
pub type FileId = u32;
|
pub type FileId = u32;
|
||||||
pub type Loader<'a> = &'a (dyn Fn(&str, &str) -> io::Result<FileId> + 'a);
|
pub type Loader<'a> = &'a (dyn Fn(&str, &str) -> io::Result<FileId> + 'a);
|
||||||
|
|
||||||
pub const MUTABLE: IdentFlags = 1 << (std::mem::size_of::<IdentFlags>() * 8 - 1);
|
pub mod idfl {
|
||||||
pub const REFERENCED: IdentFlags = 1 << (std::mem::size_of::<IdentFlags>() * 8 - 2);
|
use super::*;
|
||||||
const GIT_DEPS_DIR: &str = "git-deps";
|
|
||||||
|
|
||||||
pub fn parse_all(threads: usize) -> io::Result<Vec<Ast>> {
|
macro_rules! flags {
|
||||||
enum ImportPath<'a> {
|
($($name:ident,)*) => {
|
||||||
Root {
|
$(pub const $name: IdentFlags = 1 << (std::mem::size_of::<IdentFlags>() * 8 - 1 - ${index(0)});)*
|
||||||
path: &'a str,
|
pub const ALL: IdentFlags = 0 $(| $name)*;
|
||||||
},
|
|
||||||
Rel {
|
|
||||||
path: &'a str,
|
|
||||||
},
|
|
||||||
Git {
|
|
||||||
link: &'a str,
|
|
||||||
path: &'a str,
|
|
||||||
branch: Option<&'a str>,
|
|
||||||
tag: Option<&'a str>,
|
|
||||||
rev: Option<&'a str>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for ImportPath<'a> {
|
|
||||||
type Error = ParseImportError;
|
|
||||||
|
|
||||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
|
||||||
let (prefix, path) = value.split_once(':').unwrap_or(("", value));
|
|
||||||
|
|
||||||
match prefix {
|
|
||||||
"" => Ok(Self::Root { path }),
|
|
||||||
"rel" => Ok(Self::Rel { path }),
|
|
||||||
"git" => {
|
|
||||||
let (link, path) =
|
|
||||||
path.split_once(':').ok_or(ParseImportError::ExpectedPath)?;
|
|
||||||
let (link, params) = link.split_once('?').unwrap_or((link, ""));
|
|
||||||
let [mut branch, mut tag, mut rev] = [None; 3];
|
|
||||||
for (key, value) in params.split('&').filter_map(|s| s.split_once('=')) {
|
|
||||||
match key {
|
|
||||||
"branch" => branch = Some(value),
|
|
||||||
"tag" => tag = Some(value),
|
|
||||||
"rev" => rev = Some(value),
|
|
||||||
_ => return Err(ParseImportError::UnexpectedParam),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Self::Git {
|
|
||||||
link,
|
|
||||||
path,
|
|
||||||
branch,
|
|
||||||
tag,
|
|
||||||
rev,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => Err(ParseImportError::InvalidPrefix),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn preprocess_git(link: &str) -> &str {
|
|
||||||
let link = link.strip_prefix("https://").unwrap_or(link);
|
|
||||||
link.strip_suffix(".git").unwrap_or(link)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ImportPath<'a> {
|
|
||||||
fn resolve(&self, from: &str) -> Result<PathBuf, CantLoadFile> {
|
|
||||||
match self {
|
|
||||||
Self::Root { path } => Ok(Path::new(path).to_owned()),
|
|
||||||
Self::Rel { path } => {
|
|
||||||
let path = PathBuf::from_iter([from, path]);
|
|
||||||
match path.canonicalize() {
|
|
||||||
Ok(path) => Ok(path),
|
|
||||||
Err(e) => Err(CantLoadFile(path, e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Git { path, link, .. } => {
|
|
||||||
let link = preprocess_git(link);
|
|
||||||
Ok(PathBuf::from_iter([GIT_DEPS_DIR, link, path]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum ParseImportError {
|
|
||||||
ExpectedPath,
|
|
||||||
InvalidPrefix,
|
|
||||||
ExpectedGitAlias,
|
|
||||||
UnexpectedParam,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for ParseImportError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::ExpectedPath => write!(f, "expected path"),
|
|
||||||
Self::InvalidPrefix => write!(
|
|
||||||
f,
|
|
||||||
"invalid prefix, expected one of rel, \
|
|
||||||
git or none followed by colon"
|
|
||||||
),
|
|
||||||
Self::ExpectedGitAlias => write!(f, "expected git alias as ':<alias>$'"),
|
|
||||||
Self::UnexpectedParam => {
|
|
||||||
write!(f, "unexpected git param, expected branch, tag or rev")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for ParseImportError {}
|
|
||||||
|
|
||||||
impl From<ParseImportError> for io::Error {
|
|
||||||
fn from(e: ParseImportError) -> Self {
|
|
||||||
io::Error::new(io::ErrorKind::InvalidInput, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct CantLoadFile(PathBuf, io::Error);
|
|
||||||
|
|
||||||
impl std::fmt::Display for CantLoadFile {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "can't load file: {}", self.0.display())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for CantLoadFile {
|
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
||||||
Some(&self.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CantLoadFile> for io::Error {
|
|
||||||
fn from(e: CantLoadFile) -> Self {
|
|
||||||
io::Error::new(io::ErrorKind::InvalidData, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct InvalidFileData(std::str::Utf8Error);
|
|
||||||
|
|
||||||
impl std::fmt::Display for InvalidFileData {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "invalid file data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for InvalidFileData {
|
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
||||||
Some(&self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<InvalidFileData> for io::Error {
|
|
||||||
fn from(e: InvalidFileData) -> Self {
|
|
||||||
io::Error::new(io::ErrorKind::InvalidData, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Task = (FileId, PathBuf, Option<std::process::Command>);
|
|
||||||
|
|
||||||
let seen = Mutex::new(HashMap::<PathBuf, FileId>::new());
|
|
||||||
let tasks = TaskQueue::<Task>::new(threads);
|
|
||||||
let ast = Mutex::new(Vec::<io::Result<Ast>>::new());
|
|
||||||
|
|
||||||
let loader = |path: &str, from: &str| {
|
|
||||||
let path = ImportPath::try_from(path)?;
|
|
||||||
|
|
||||||
let physiscal_path = path.resolve(from)?;
|
|
||||||
|
|
||||||
let id = {
|
|
||||||
let mut seen = seen.lock().unwrap();
|
|
||||||
let len = seen.len();
|
|
||||||
match seen.entry(physiscal_path.clone()) {
|
|
||||||
std::collections::hash_map::Entry::Occupied(entry) => {
|
|
||||||
return Ok(*entry.get());
|
|
||||||
}
|
|
||||||
std::collections::hash_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert(len as _);
|
|
||||||
len as FileId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let command = if !physiscal_path.exists() {
|
flags! {
|
||||||
let ImportPath::Git {
|
MUTABLE,
|
||||||
link,
|
REFERENCED,
|
||||||
branch,
|
CAPTURED,
|
||||||
rev,
|
}
|
||||||
tag,
|
|
||||||
..
|
|
||||||
} = path
|
|
||||||
else {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::NotFound,
|
|
||||||
format!("can't find file: {}", physiscal_path.display()),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let root = PathBuf::from_iter([GIT_DEPS_DIR, preprocess_git(link)]);
|
pub fn index(i: IdentFlags) -> u32 {
|
||||||
|
i & !ALL
|
||||||
let mut command = std::process::Command::new("git");
|
}
|
||||||
command
|
|
||||||
.args(["clone", "--depth", "1"])
|
|
||||||
.args(branch.map(|b| ["--branch", b]).into_iter().flatten())
|
|
||||||
.args(tag.map(|t| ["--tag", t]).into_iter().flatten())
|
|
||||||
.args(rev.map(|r| ["--rev", r]).into_iter().flatten())
|
|
||||||
.arg(link)
|
|
||||||
.arg(root);
|
|
||||||
Some(command)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
tasks.push((id, physiscal_path, command));
|
|
||||||
Ok(id)
|
|
||||||
};
|
|
||||||
|
|
||||||
let execute_task = |(_, path, command): Task, buffer: &mut Vec<u8>| {
|
|
||||||
if let Some(mut command) = command {
|
|
||||||
let output = command.output()?;
|
|
||||||
if !output.status.success() {
|
|
||||||
let msg = format!(
|
|
||||||
"git command failed: {}",
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, msg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = path.to_str().ok_or_else(|| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!("path contains invalid characters: {}", path.display()),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let mut file = std::fs::File::open(path)?;
|
|
||||||
file.read_to_end(buffer)?;
|
|
||||||
let src = std::str::from_utf8(buffer).map_err(InvalidFileData)?;
|
|
||||||
Ok(Ast::new(path, src, &loader))
|
|
||||||
};
|
|
||||||
|
|
||||||
let thread = || {
|
|
||||||
let mut buffer = Vec::new();
|
|
||||||
while let Some(task @ (indx, ..)) = tasks.pop() {
|
|
||||||
let res = execute_task(task, &mut buffer);
|
|
||||||
buffer.clear();
|
|
||||||
|
|
||||||
let mut ast = ast.lock().unwrap();
|
|
||||||
let len = ast.len().max(indx as usize + 1);
|
|
||||||
ast.resize_with(len, || Err(io::ErrorKind::InvalidData.into()));
|
|
||||||
ast[indx as usize] = res;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::thread::scope(|s| (0..threads).for_each(|_| _ = s.spawn(thread)));
|
|
||||||
|
|
||||||
ast.into_inner()
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<io::Result<Vec<_>>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ident_flag_index(flag: IdentFlags) -> u32 {
|
|
||||||
flag & !(MUTABLE | REFERENCED)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_loader(_: &str, _: &str) -> io::Result<FileId> {
|
pub fn no_loader(_: &str, _: &str) -> io::Result<FileId> {
|
||||||
|
@ -301,13 +56,15 @@ struct ScopeIdent {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Parser<'a, 'b> {
|
pub struct Parser<'a, 'b> {
|
||||||
path: &'b str,
|
path: &'b str,
|
||||||
loader: Loader<'b>,
|
loader: Loader<'b>,
|
||||||
lexer: Lexer<'b>,
|
lexer: Lexer<'b>,
|
||||||
arena: &'b Arena<'a>,
|
arena: &'b Arena<'a>,
|
||||||
token: Token,
|
token: Token,
|
||||||
idents: Vec<ScopeIdent>,
|
idents: Vec<ScopeIdent>,
|
||||||
symbols: &'b mut Symbols,
|
symbols: &'b mut Symbols,
|
||||||
|
ns_bound: usize,
|
||||||
|
captured: Vec<Ident>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Parser<'a, 'b> {
|
impl<'a, 'b> Parser<'a, 'b> {
|
||||||
|
@ -321,6 +78,8 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||||
arena,
|
arena,
|
||||||
idents: Vec::new(),
|
idents: Vec::new(),
|
||||||
symbols,
|
symbols,
|
||||||
|
ns_bound: 0,
|
||||||
|
captured: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +141,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||||
let left = &*self.arena.alloc(fold);
|
let left = &*self.arena.alloc(fold);
|
||||||
|
|
||||||
if let Some(op) = op.assign_op() {
|
if let Some(op) = op.assign_op() {
|
||||||
self.flag_idents(*left, MUTABLE);
|
self.flag_idents(*left, idfl::MUTABLE);
|
||||||
let right = Expr::BinOp { left, op, right };
|
let right = Expr::BinOp { left, op, right };
|
||||||
fold = Expr::BinOp {
|
fold = Expr::BinOp {
|
||||||
left,
|
left,
|
||||||
|
@ -392,7 +151,7 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||||
} else {
|
} else {
|
||||||
fold = Expr::BinOp { left, right, op };
|
fold = Expr::BinOp { left, right, op };
|
||||||
if op == TokenKind::Assign {
|
if op == TokenKind::Assign {
|
||||||
self.flag_idents(*left, MUTABLE);
|
self.flag_idents(*left, idfl::MUTABLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -400,41 +159,25 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||||
fold
|
fold
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_resolve_builtin(name: &str) -> Option<Ident> {
|
|
||||||
// FIXME: we actually do this the second time in the codegen
|
|
||||||
Some(match name {
|
|
||||||
"int" | "i64" => bt::INT,
|
|
||||||
"i8" => bt::I8,
|
|
||||||
"i16" => bt::I16,
|
|
||||||
"i32" => bt::I32,
|
|
||||||
"u8" => bt::U8,
|
|
||||||
"u16" => bt::U16,
|
|
||||||
"uint" | "u32" => bt::U32,
|
|
||||||
"bool" => bt::BOOL,
|
|
||||||
"void" => bt::VOID,
|
|
||||||
"never" => bt::NEVER,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_ident(&mut self, token: Token, decl: bool) -> (Ident, u32) {
|
fn resolve_ident(&mut self, token: Token, decl: bool) -> (Ident, u32) {
|
||||||
let name = self.lexer.slice(token.range());
|
let name = self.lexer.slice(token.range());
|
||||||
|
|
||||||
if let Some(builtin) = Self::try_resolve_builtin(name) {
|
if let Some(builtin) = bt::from_str(name) {
|
||||||
return (builtin, 0);
|
return (builtin, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = match self
|
let (i, id) = match self
|
||||||
.idents
|
.idents
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rfind(|elem| self.lexer.slice(ident::range(elem.ident)) == name)
|
.enumerate()
|
||||||
|
.rfind(|(_, elem)| self.lexer.slice(ident::range(elem.ident)) == name)
|
||||||
{
|
{
|
||||||
Some(elem) if decl && elem.declared => {
|
Some((_, elem)) if decl && elem.declared => {
|
||||||
self.report(format_args!("redeclaration of identifier: {name}"))
|
self.report(format_args!("redeclaration of identifier: {name}"))
|
||||||
}
|
}
|
||||||
Some(elem) => {
|
Some((i, elem)) => {
|
||||||
elem.flags += 1;
|
elem.flags += 1;
|
||||||
elem
|
(i, elem)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let id = ident::new(token.start, name.len() as _);
|
let id = ident::new(token.start, name.len() as _);
|
||||||
|
@ -443,13 +186,17 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||||
declared: false,
|
declared: false,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
});
|
});
|
||||||
self.idents.last_mut().unwrap()
|
(self.idents.len() - 1, self.idents.last_mut().unwrap())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
id.declared |= decl;
|
id.declared |= decl;
|
||||||
|
if self.ns_bound > i && id.declared {
|
||||||
|
id.flags |= idfl::CAPTURED;
|
||||||
|
self.captured.push(id.ident);
|
||||||
|
}
|
||||||
|
|
||||||
(id.ident, ident_flag_index(id.flags))
|
(id.ident, idfl::index(id.flags))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_str(&mut self, range: Token) -> &'a str {
|
fn move_str(&mut self, range: Token) -> &'a str {
|
||||||
|
@ -460,6 +207,8 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||||
use {Expr as E, TokenKind as T};
|
use {Expr as E, TokenKind as T};
|
||||||
let frame = self.idents.len();
|
let frame = self.idents.len();
|
||||||
let token = self.next();
|
let token = self.next();
|
||||||
|
let prev_boundary = self.ns_bound;
|
||||||
|
let prev_captured = self.captured.len();
|
||||||
let mut expr = match token.kind {
|
let mut expr = match token.kind {
|
||||||
T::Driective if self.lexer.slice(token.range()) == "use" => {
|
T::Driective if self.lexer.slice(token.range()) == "use" => {
|
||||||
self.expect_advance(TokenKind::LParen);
|
self.expect_advance(TokenKind::LParen);
|
||||||
|
@ -489,8 +238,8 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||||
value: true,
|
value: true,
|
||||||
},
|
},
|
||||||
T::Struct => E::Struct {
|
T::Struct => E::Struct {
|
||||||
pos: token.start,
|
fields: {
|
||||||
fields: {
|
self.ns_bound = self.idents.len();
|
||||||
self.expect_advance(T::LBrace);
|
self.expect_advance(T::LBrace);
|
||||||
self.collect_list(T::Comma, T::RBrace, |s| {
|
self.collect_list(T::Comma, T::RBrace, |s| {
|
||||||
let name = s.expect_advance(T::Ident);
|
let name = s.expect_advance(T::Ident);
|
||||||
|
@ -499,6 +248,20 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||||
(s.move_str(name), ty)
|
(s.move_str(name), ty)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
captured: {
|
||||||
|
self.ns_bound = prev_boundary;
|
||||||
|
self.captured[prev_captured..].sort_unstable();
|
||||||
|
let preserved = self.captured[prev_captured..].partition_dedup().0.len();
|
||||||
|
self.captured.truncate(prev_captured + preserved);
|
||||||
|
self.arena.alloc_slice(&self.captured[prev_captured..])
|
||||||
|
},
|
||||||
|
pos: {
|
||||||
|
if self.ns_bound == 0 {
|
||||||
|
// we might save some memory
|
||||||
|
self.captured.clear();
|
||||||
|
}
|
||||||
|
token.start
|
||||||
|
},
|
||||||
},
|
},
|
||||||
T::Ident => {
|
T::Ident => {
|
||||||
let (id, index) = self.resolve_ident(token, self.token.kind == T::Decl);
|
let (id, index) = self.resolve_ident(token, self.token.kind == T::Decl);
|
||||||
|
@ -543,13 +306,13 @@ impl<'a, 'b> Parser<'a, 'b> {
|
||||||
},
|
},
|
||||||
body: self.ptr_expr(),
|
body: self.ptr_expr(),
|
||||||
},
|
},
|
||||||
T::Band | T::Mul => E::UnOp {
|
T::Band | T::Mul | T::Xor => E::UnOp {
|
||||||
pos: token.start,
|
pos: token.start,
|
||||||
op: token.kind,
|
op: token.kind,
|
||||||
val: {
|
val: {
|
||||||
let expr = self.ptr_unit_expr();
|
let expr = self.ptr_unit_expr();
|
||||||
if token.kind == T::Band {
|
if token.kind == T::Band {
|
||||||
self.flag_idents(*expr, REFERENCED);
|
self.flag_idents(*expr, idfl::REFERENCED);
|
||||||
}
|
}
|
||||||
expr
|
expr
|
||||||
},
|
},
|
||||||
|
@ -775,8 +538,9 @@ pub enum Expr<'a> {
|
||||||
val: &'a Self,
|
val: &'a Self,
|
||||||
},
|
},
|
||||||
Struct {
|
Struct {
|
||||||
pos: Pos,
|
pos: Pos,
|
||||||
fields: &'a [(&'a str, Self)],
|
fields: &'a [(&'a str, Self)],
|
||||||
|
captured: &'a [Ident],
|
||||||
},
|
},
|
||||||
Ctor {
|
Ctor {
|
||||||
pos: Pos,
|
pos: Pos,
|
||||||
|
@ -1041,13 +805,13 @@ impl Ast {
|
||||||
unsafe { self.0.as_ref() }
|
unsafe { self.0.as_ref() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_decl(&self, id: Result<Ident, &str>) -> Option<&Expr> {
|
pub fn find_decl(&self, id: Result<Ident, &str>) -> Option<(&Expr, Ident)> {
|
||||||
self.exprs().iter().find_map(|expr| match expr {
|
self.exprs().iter().find_map(|expr| match expr {
|
||||||
Expr::BinOp {
|
Expr::BinOp {
|
||||||
left: &Expr::Ident { id: iden, name, .. },
|
left: &Expr::Ident { id: iden, name, .. },
|
||||||
op: TokenKind::Decl,
|
op: TokenKind::Decl,
|
||||||
..
|
..
|
||||||
} if Ok(iden) == id || Err(name) == id => Some(expr),
|
} if Ok(iden) == id || Err(name) == id => Some((expr, iden)),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1207,17 +971,6 @@ impl ArenaChunk {
|
||||||
unsafe { std::ptr::read(curr.add(Self::NEXT_OFFSET) as *mut _) }
|
unsafe { std::ptr::read(curr.add(Self::NEXT_OFFSET) as *mut _) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(prev: *mut u8) -> Option<Self> {
|
|
||||||
if prev.is_null() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Self {
|
|
||||||
base: prev,
|
|
||||||
end: unsafe { prev.add(Self::CHUNK_SIZE) },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc(&mut self, layout: std::alloc::Layout) -> Option<NonNull<u8>> {
|
fn alloc(&mut self, layout: std::alloc::Layout) -> Option<NonNull<u8>> {
|
||||||
let padding = self.end as usize - (self.end as usize & !(layout.align() - 1));
|
let padding = self.end as usize - (self.end as usize & !(layout.align() - 1));
|
||||||
let size = layout.size() + padding;
|
let size = layout.size() + padding;
|
||||||
|
|
|
@ -11,7 +11,10 @@ pub fn run_test(name: &'static str, input: &'static str, test: fn(&'static str,
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
test(input, &mut output);
|
test(input, &mut output);
|
||||||
|
|
||||||
let mut root = PathBuf::from(std::env::var("PT_TEST_ROOT").unwrap_or("tests".to_string()));
|
let mut root = PathBuf::from(
|
||||||
|
std::env::var("PT_TEST_ROOT")
|
||||||
|
.unwrap_or(concat!(env!("CARGO_MANIFEST_DIR"), "/tests").to_string()),
|
||||||
|
);
|
||||||
root.push(
|
root.push(
|
||||||
name.replace("::", "_")
|
name.replace("::", "_")
|
||||||
.replace(concat!(env!("CARGO_PKG_NAME"), "_"), ""),
|
.replace(concat!(env!("CARGO_PKG_NAME"), "_"), ""),
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 200
|
code size: 189
|
||||||
ret: 1
|
ret: 1
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 499
|
code size: 477
|
||||||
ret: 512
|
ret: 512
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
ev: Ecall
|
ev: Ecall
|
||||||
code size: 204
|
code size: 182
|
||||||
ret: 0
|
ret: 0
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 96
|
code size: 85
|
||||||
ret: 1
|
ret: 1
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 569
|
code size: 525
|
||||||
ret: 0
|
ret: 0
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 314
|
code size: 281
|
||||||
ret: 33
|
ret: 33
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
3
hblang/tests/codegen_tests_geneic_types.txt
Normal file
3
hblang/tests/codegen_tests_geneic_types.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
code size: 359
|
||||||
|
ret: 69
|
||||||
|
status: Ok(())
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 305
|
code size: 283
|
||||||
ret: 50
|
ret: 50
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 287
|
code size: 265
|
||||||
ret: 55
|
ret: 55
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 287
|
code size: 265
|
||||||
ret: 55
|
ret: 55
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 366
|
code size: 322
|
||||||
ret: 0
|
ret: 0
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 800
|
code size: 778
|
||||||
ret: 10
|
ret: 10
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 464
|
code size: 420
|
||||||
ret: 3
|
ret: 3
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
code size: 121
|
code size: 110
|
||||||
ret: 0
|
ret: 0
|
||||||
status: Ok(())
|
status: Ok(())
|
||||||
|
|
Loading…
Reference in a new issue