Added: Stdme as dependency
This commit is contained in:
parent
e1659508c6
commit
78f0707574
11 changed files with 11 additions and 447 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
|
@ -1,3 +1,3 @@
|
||||||
[submodule "stdme"]
|
[submodule "shcat_c/stdme"]
|
||||||
path = stdme
|
path = shcat_c/stdme
|
||||||
url = git@github.com:maix0/stdme.git
|
url = git@github.com:Maix0/stdme.git
|
||||||
|
|
|
||||||
1
rust/parser/.gitignore
vendored
1
rust/parser/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
/target
|
|
||||||
7
rust/parser/Cargo.lock
generated
7
rust/parser/Cargo.lock
generated
|
|
@ -1,7 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parser-rs"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "parser-rs"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
/* ************************************************************************** */
|
|
||||||
/* */
|
|
||||||
/* ::: :::::::: */
|
|
||||||
/* enum.h :+: :+: :+: */
|
|
||||||
/* +:+ +:+ +:+ */
|
|
||||||
/* By: rparodi <rparodi@student.42.fr> +#+ +:+ +#+ */
|
|
||||||
/* +#+#+#+#+#+ +#+ */
|
|
||||||
/* Created: 2024/03/28 12:44:05 by rparodi #+# #+# */
|
|
||||||
/* Updated: 2024/03/28 13:09:06 by rparodi ### ########.fr */
|
|
||||||
/* */
|
|
||||||
/* ************************************************************************** */
|
|
||||||
|
|
||||||
#ifndef ENUM_H
|
|
||||||
#define ENUM_H
|
|
||||||
|
|
||||||
#include "./rust_type.h"
|
|
||||||
|
|
||||||
enum e_separator_op
|
|
||||||
{
|
|
||||||
Semi,
|
|
||||||
Fork,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum e_pipeline_kind
|
|
||||||
{
|
|
||||||
Or,
|
|
||||||
And,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
/* ************************************************************************** */
|
|
||||||
/* */
|
|
||||||
/* ::: :::::::: */
|
|
||||||
/* rust_type.h :+: :+: :+: */
|
|
||||||
/* +:+ +:+ +:+ */
|
|
||||||
/* By: rparodi <rparodi@student.42.fr> +#+ +:+ +#+ */
|
|
||||||
/* +#+#+#+#+#+ +#+ */
|
|
||||||
/* Created: 2024/03/28 12:50:51 by rparodi #+# #+# */
|
|
||||||
/* Updated: 2024/03/28 12:52:53 by rparodi ### ########.fr */
|
|
||||||
/* */
|
|
||||||
/* ************************************************************************** */
|
|
||||||
|
|
||||||
#ifndef RUST_TYPE_H
|
|
||||||
#define RUST_TYPE_H
|
|
||||||
|
|
||||||
# define bool int
|
|
||||||
# define true 1
|
|
||||||
# define false 0
|
|
||||||
# define u8 unsigned char
|
|
||||||
# define u16 unsigned short
|
|
||||||
# define u32 unsigned int
|
|
||||||
# define u64 unsigned long long
|
|
||||||
# define i8 char
|
|
||||||
# define i16 short
|
|
||||||
# define i32 int
|
|
||||||
# define i64 long long
|
|
||||||
# define usize size_t
|
|
||||||
# define isize ssize_t
|
|
||||||
# define f32 float
|
|
||||||
# define f64 double
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
/* ************************************************************************** */
|
|
||||||
/* */
|
|
||||||
/* ::: :::::::: */
|
|
||||||
/* shcat.h :+: :+: :+: */
|
|
||||||
/* +:+ +:+ +:+ */
|
|
||||||
/* By: rparodi <rparodi@student.42.fr> +#+ +:+ +#+ */
|
|
||||||
/* +#+#+#+#+#+ +#+ */
|
|
||||||
/* Created: 2024/03/28 12:42:55 by rparodi #+# #+# */
|
|
||||||
/* Updated: 2024/03/28 12:53:50 by rparodi ### ########.fr */
|
|
||||||
/* */
|
|
||||||
/* ************************************************************************** */
|
|
||||||
|
|
||||||
#ifndef SHCAT_H
|
|
||||||
#define SHCAT_H
|
|
||||||
|
|
||||||
# include <unistd.h>
|
|
||||||
# include <fcntl.h>
|
|
||||||
# include <stdlib.h>
|
|
||||||
# include <stdio.h>
|
|
||||||
# include <string.h>
|
|
||||||
# include <sys/types.h>
|
|
||||||
# include <sys/wait.h>
|
|
||||||
# include <stdlib.h>
|
|
||||||
# include <stdarg.h>
|
|
||||||
# include <stddef.h>
|
|
||||||
|
|
||||||
# include "./enum.h"
|
|
||||||
# include "./rust_type.h"
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,331 +0,0 @@
|
||||||
#![allow(dead_code)]
|
|
||||||
use std::{borrow::Cow, collections::VecDeque, convert::Infallible as Never, pin::Pin};
|
|
||||||
|
|
||||||
type WORD = String;
|
|
||||||
type Rule2 = String;
|
|
||||||
type Rule3 = String;
|
|
||||||
|
|
||||||
struct SourceFile {
|
|
||||||
cmds: Vec<CompleteCommands>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CompleteCommands {
|
|
||||||
// each Pipeline Must have a Separator, but the
|
|
||||||
// last Pipeline is optional
|
|
||||||
list: Vec<(Pipeline, Option<SeparatorOp>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SeparatorOp {
|
|
||||||
Semi, /* ; */
|
|
||||||
Fork, /* & */
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Pipeline {
|
|
||||||
bang: bool, // placed before the seqence
|
|
||||||
kind: Option<PipelineKind>, // placed after the seqence
|
|
||||||
seq: Vec<Commands>, // each cmd are piped into the next
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PipelineKind {
|
|
||||||
Or, /* || */
|
|
||||||
And, /* && */
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Commands {
|
|
||||||
Simple(SimpleCommand),
|
|
||||||
Compound(Never),
|
|
||||||
FuncDef(Never),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SimpleCommand {
|
|
||||||
prefix: Vec<CmdPrefix>,
|
|
||||||
cmd: Vec<WORD>, // First is rule 7a, then 7b
|
|
||||||
suffix: Vec<CmdSuffix>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CmdPrefix {
|
|
||||||
IoRedirect(IoRedirect),
|
|
||||||
Assigment(Assigment),
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CmdSuffix {
|
|
||||||
IoRedirect(IoRedirect),
|
|
||||||
Word(WORD),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IoRedirect {
|
|
||||||
io_file: Option<usize>,
|
|
||||||
io_kind: IoKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum IoKind {
|
|
||||||
IoFile(IoFile),
|
|
||||||
IoHere(IoHere),
|
|
||||||
}
|
|
||||||
|
|
||||||
enum IoFile {
|
|
||||||
Less { filename: Rule2 }, /* < */
|
|
||||||
Greater { filename: Rule2 }, /* > */
|
|
||||||
LessAnd { filename: Rule2 }, /* <& */
|
|
||||||
GreaterAnd { filename: Rule2 }, /* >& */
|
|
||||||
DoubleGreater { filename: Rule2 }, /* >> */
|
|
||||||
LessGreater { filename: Rule2 }, /* <> */
|
|
||||||
Clobber { filename: Rule2 }, /* >| */
|
|
||||||
}
|
|
||||||
enum IoHere {
|
|
||||||
Dless { here_end: Rule3 }, /* << */
|
|
||||||
DlessDash { here_end: Rule3 }, /* <<- */
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rule 7
|
|
||||||
struct Assigment {
|
|
||||||
key: WORD,
|
|
||||||
value: WORD,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct TokenizerState<'input> {
|
|
||||||
current_pos: usize,
|
|
||||||
input: &'input str,
|
|
||||||
remaining: &'input str,
|
|
||||||
invalid_quote: bool,
|
|
||||||
_marker: std::marker::PhantomPinned,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'input> TokenizerState<'input> {
|
|
||||||
fn new(input: &'input str) -> Self {
|
|
||||||
Self {
|
|
||||||
current_pos: 0,
|
|
||||||
remaining: input,
|
|
||||||
invalid_quote: false,
|
|
||||||
input,
|
|
||||||
_marker: std::marker::PhantomPinned,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cow<'input, str> is either a owned string (so it'll have to be free'd, basically it is an `String`
|
|
||||||
// or it is an borrow string, so an &'input str, which doesn't need to be free (or at least by us)
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
enum Token<'input> {
|
|
||||||
SingleQuote {
|
|
||||||
val: Cow<'input, str>,
|
|
||||||
start_pos: usize,
|
|
||||||
},
|
|
||||||
DoubleQuote {
|
|
||||||
val: Cow<'input, str>,
|
|
||||||
start_pos: usize,
|
|
||||||
},
|
|
||||||
WhiteSpace {
|
|
||||||
val: Cow<'input, str>,
|
|
||||||
start_pos: usize,
|
|
||||||
},
|
|
||||||
Word {
|
|
||||||
val: Cow<'input, str>,
|
|
||||||
start_pos: usize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tokenizer<'state, 'input: 'state>(
|
|
||||||
state: &'state mut TokenizerState<'input>,
|
|
||||||
) -> impl Iterator<Item = Token<'input>> + 'state {
|
|
||||||
state.current_pos = 0;
|
|
||||||
std::iter::from_fn(move || {
|
|
||||||
let state = &mut *state;
|
|
||||||
let mut chars = state.remaining.chars().peekable();
|
|
||||||
let mut len = 1;
|
|
||||||
let mut escaped = false;
|
|
||||||
|
|
||||||
let Some(chr) = chars.next() else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
match chr {
|
|
||||||
'\'' => {
|
|
||||||
while let Some(s) = chars.peek().copied() {
|
|
||||||
if s == '\'' {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
len += 1;
|
|
||||||
chars.next();
|
|
||||||
}
|
|
||||||
let skip = chars.peek() == Some(&'\'');
|
|
||||||
state.invalid_quote |= !skip;
|
|
||||||
let old_current = state.current_pos;
|
|
||||||
state.current_pos += len;
|
|
||||||
let old_remaining = state.remaining;
|
|
||||||
state.remaining = &state.remaining[(len + skip as usize)..];
|
|
||||||
return Some(Token::SingleQuote {
|
|
||||||
val: old_remaining[1..len].into(),
|
|
||||||
start_pos: old_current,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
'"' => {
|
|
||||||
while let Some(s) = chars.peek().copied() {
|
|
||||||
if !escaped && s == '\"' {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
len += 1;
|
|
||||||
escaped = chars.next() == Some('\\');
|
|
||||||
}
|
|
||||||
let skip = chars.peek() == Some(&'\"');
|
|
||||||
state.invalid_quote |= !skip;
|
|
||||||
let old_current = state.current_pos;
|
|
||||||
state.current_pos += len;
|
|
||||||
let old_remaining = state.remaining;
|
|
||||||
state.remaining = &state.remaining[(len + skip as usize)..];
|
|
||||||
return Some(Token::DoubleQuote {
|
|
||||||
val: old_remaining[1..len].into(),
|
|
||||||
start_pos: old_current,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
let was_whitespace = chr.is_ascii_whitespace();
|
|
||||||
while let Some(&chr) = chars.peek() {
|
|
||||||
if chr.is_ascii_whitespace() && !escaped && !was_whitespace {
|
|
||||||
let old_current = state.current_pos;
|
|
||||||
state.current_pos += len;
|
|
||||||
let old_remaining = state.remaining;
|
|
||||||
state.remaining = &state.remaining[len..];
|
|
||||||
return Some(Token::Word {
|
|
||||||
val: old_remaining[..len].into(),
|
|
||||||
start_pos: old_current,
|
|
||||||
});
|
|
||||||
} else if !chr.is_ascii_whitespace() && was_whitespace {
|
|
||||||
let old_current = state.current_pos;
|
|
||||||
state.current_pos += len;
|
|
||||||
let old_remaining = state.remaining;
|
|
||||||
state.remaining = &state.remaining[len..];
|
|
||||||
return Some(Token::WhiteSpace {
|
|
||||||
val: old_remaining[..len].into(),
|
|
||||||
start_pos: old_current,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
len += 1;
|
|
||||||
escaped = chars.next() == Some('\\');
|
|
||||||
}
|
|
||||||
let old_current = state.current_pos;
|
|
||||||
state.current_pos += len;
|
|
||||||
let old_remaining = state.remaining;
|
|
||||||
state.remaining = &state.remaining[len..];
|
|
||||||
Some(if was_whitespace {
|
|
||||||
Token::WhiteSpace {
|
|
||||||
val: old_remaining[..len].into(),
|
|
||||||
start_pos: old_current,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Token::Word {
|
|
||||||
val: old_remaining[..len].into(),
|
|
||||||
start_pos: old_current,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This isn't a C thingy, it is just needed to make rust happy
|
|
||||||
|
|
||||||
struct TokenizerWrapper<'input> {
|
|
||||||
_marker: std::marker::PhantomPinned,
|
|
||||||
first_pass: TokenizerState<'input>,
|
|
||||||
iter: Box<dyn Iterator<Item = Token<'input>> + 'input>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'input> TokenizerWrapper<'input> {
|
|
||||||
fn new(s: TokenizerState<'input>) -> Pin<Box<Self>> {
|
|
||||||
let mut value = Box::new(std::mem::MaybeUninit::<Self>::uninit());
|
|
||||||
unsafe {
|
|
||||||
let ptr = value.as_mut_ptr();
|
|
||||||
std::ptr::write(std::ptr::addr_of_mut!((*ptr).first_pass), s);
|
|
||||||
std::ptr::write(
|
|
||||||
std::ptr::addr_of_mut!((*ptr).iter),
|
|
||||||
Box::new(tokenizer(&mut *std::ptr::addr_of_mut!((*ptr).first_pass))),
|
|
||||||
);
|
|
||||||
std::mem::transmute(Pin::new_unchecked(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'input> TokenizerWrapper<'input> {
|
|
||||||
fn next(self: &mut Pin<Box<Self>>) -> Option<Token<'input>> {
|
|
||||||
unsafe { Pin::into_inner_unchecked(self.as_mut()).iter.next() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// end of rust thingy
|
|
||||||
|
|
||||||
struct ExpenderState<'input> {
|
|
||||||
// These will be inserted when a substitution is made, like $HOME
|
|
||||||
// if it is "$HOME", then no splitting should be done, so if there is any stuff that needs to
|
|
||||||
// be pushed, then push it, otherwise get the next token from `iter`, expend if needed and
|
|
||||||
// voila
|
|
||||||
need_push: VecDeque<ExpendedToken<'input>>,
|
|
||||||
// This is because of the way I wrote the rust
|
|
||||||
// stuff, returning iterator instead of an token everytime I call a function and stuff, it
|
|
||||||
// shouldn't be reflected into the C code, as we will just call 'get_next_token(&state)' and it
|
|
||||||
// will give us the next token (or EOF if no more token are present)
|
|
||||||
tokenizer: Pin<Box<TokenizerWrapper<'input>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
enum ExpendedToken<'input> {
|
|
||||||
SingleQuote {
|
|
||||||
val: Cow<'input, str>,
|
|
||||||
start_pos: usize,
|
|
||||||
},
|
|
||||||
DoubleQuote {
|
|
||||||
val: Cow<'input, str>,
|
|
||||||
start_pos: usize,
|
|
||||||
},
|
|
||||||
WhiteSpace {
|
|
||||||
val: Cow<'input, str>,
|
|
||||||
start_pos: usize,
|
|
||||||
},
|
|
||||||
Word {
|
|
||||||
val: Cow<'input, str>,
|
|
||||||
start_pos: usize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'input> ExpenderState<'input> {
|
|
||||||
fn new(input: &'input str) -> Self {
|
|
||||||
let wrapper = TokenizerWrapper::new(TokenizerState::new(input));
|
|
||||||
|
|
||||||
Self {
|
|
||||||
need_push: VecDeque::new(),
|
|
||||||
tokenizer: wrapper,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expend<'state, 'input: 'state>(
|
|
||||||
input: &'state mut ExpenderState<'input>,
|
|
||||||
) -> impl Iterator<Item = ExpendedToken<'input>> + 'state {
|
|
||||||
std::iter::from_fn(|| {
|
|
||||||
if !input.need_push.is_empty() {
|
|
||||||
input.need_push.pop_front()
|
|
||||||
} else {
|
|
||||||
input.tokenizer.next().map(|t| match t {
|
|
||||||
Token::Word { val, start_pos } => ExpendedToken::Word { val, start_pos },
|
|
||||||
Token::DoubleQuote { val, start_pos } => {
|
|
||||||
ExpendedToken::DoubleQuote { val, start_pos }
|
|
||||||
}
|
|
||||||
Token::SingleQuote { val, start_pos } => {
|
|
||||||
ExpendedToken::SingleQuote { val, start_pos }
|
|
||||||
}
|
|
||||||
Token::WhiteSpace { val, start_pos } => {
|
|
||||||
ExpendedToken::WhiteSpace { val, start_pos }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
for line in std::io::stdin().lines() {
|
|
||||||
let line = line.unwrap();
|
|
||||||
let mut state = ExpenderState::new(&line);
|
|
||||||
println!("line is = '{line}'");
|
|
||||||
println!("token are = {:?}", expend(&mut state).collect::<Vec<_>>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
# By: rparodi <rparodi@student.42.fr> +#+ +:+ +#+ #
|
# By: rparodi <rparodi@student.42.fr> +#+ +:+ +#+ #
|
||||||
# +#+#+#+#+#+ +#+ #
|
# +#+#+#+#+#+ +#+ #
|
||||||
# Created: 2023/11/12 11:05:05 by rparodi #+# #+# #
|
# Created: 2023/11/12 11:05:05 by rparodi #+# #+# #
|
||||||
# Updated: 2024/04/13 20:18:33 by rparodi ### ########.fr #
|
# Updated: 2024/04/28 17:05:10 by maiboyer ### ########.fr #
|
||||||
# #
|
# #
|
||||||
# **************************************************************************** #
|
# **************************************************************************** #
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ CC = cc
|
||||||
RM = rm -rf
|
RM = rm -rf
|
||||||
|
|
||||||
# Flags
|
# Flags
|
||||||
CFLAGS = -Werror -Wextra -Wall -Wno-unused-command-line-argument -g3 -MMD -lreadline
|
CFLAGS = -Werror -Wextra -Wall -Wno-unused-command-line-argument -g3 -MMD -lreadline -I./libft
|
||||||
|
|
||||||
# Sources
|
# Sources
|
||||||
LIB = ./libft/ft_bzero.c \
|
LIB = ./libft/ft_bzero.c \
|
||||||
|
|
@ -84,7 +84,7 @@ fclean: clean
|
||||||
re: header fclean all
|
re: header fclean all
|
||||||
|
|
||||||
# Dependences for all
|
# Dependences for all
|
||||||
$(NAME): $(OBJ) $(LIB_OBJ)
|
$(NAME): $(OBJ) $(LIB_OBJ) $(OBJDIRNAME)/libme.a
|
||||||
@mkdir -p $(OBJDIRNAME)
|
@mkdir -p $(OBJDIRNAME)
|
||||||
@mkdir -p $(OBJDIRNAME)/$(LIBDIRNAME)
|
@mkdir -p $(OBJDIRNAME)/$(LIBDIRNAME)
|
||||||
@mkdir -p $(OBJDIRNAME)/$(SRCDIRNAME)
|
@mkdir -p $(OBJDIRNAME)/$(SRCDIRNAME)
|
||||||
|
|
@ -128,6 +128,10 @@ footer:
|
||||||
@printf "$(GOLD) '\"' '\"'$(END)\n"
|
@printf "$(GOLD) '\"' '\"'$(END)\n"
|
||||||
@printf ' $(GREY)The compilation is$(END) $(GOLD)finish$(END)\n $(GREY)Have a good $(END)$(GOLD)correction !$(END)\n'
|
@printf ' $(GREY)The compilation is$(END) $(GOLD)finish$(END)\n $(GREY)Have a good $(END)$(GOLD)correction !$(END)\n'
|
||||||
|
|
||||||
|
$(OBJDIRNAME)/libme.a:
|
||||||
|
@$(MAKE) --no-print-directory -C ./stdme/ LIB_NAME="$(realpath ./$(stdme))/" "BUILD_DIR=$(shell realpath ./$(OBJDIRNAME))"
|
||||||
|
|
||||||
|
|
||||||
pull:
|
pull:
|
||||||
@printf "$(GREEN)Pulling Submodules$(END)\n"
|
@printf "$(GREEN)Pulling Submodules$(END)\n"
|
||||||
@git submodule init
|
@git submodule init
|
||||||
|
|
|
||||||
1
shcat_c/stdme
Submodule
1
shcat_c/stdme
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit dc3c8ff9dc6f396a8fefd4ad89883e5285171b23
|
||||||
1
stdme
1
stdme
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit b3ec36536db39e871e06e6073c05bf2f965adf99
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue