fixed variable store
This commit is contained in:
parent
2d7cf80101
commit
ee1418c673
37
src/main.rs
37
src/main.rs
|
@ -23,38 +23,15 @@ where
|
|||
|
||||
fn main() {
|
||||
let source = r"
|
||||
# surely this is pi
|
||||
pi = rat 5.1415926535
|
||||
|
||||
foo(x:int, y:rat) = bool {
|
||||
x:int = 5 + 6
|
||||
|
||||
unless (x < 6) {
|
||||
yield true
|
||||
|
||||
please {
|
||||
|
||||
}
|
||||
|
||||
unless(x < 6) {
|
||||
yield true
|
||||
}
|
||||
}
|
||||
|
||||
-- comment
|
||||
yield true
|
||||
foo(x:int) = int {
|
||||
x / 2
|
||||
}
|
||||
|
||||
main() = int {
|
||||
a = foo(4)
|
||||
|
||||
a = 4
|
||||
b = pi
|
||||
c = true
|
||||
r = foo(3, 4.0)
|
||||
h = foo(3,5.0)
|
||||
b:int = 4
|
||||
|
||||
9
|
||||
a
|
||||
}
|
||||
";
|
||||
|
||||
|
@ -64,8 +41,10 @@ main() = int {
|
|||
|
||||
if let Ok(mut tokens) = tokenize(source, &mut diagnostics) {
|
||||
if let Ok((fs, ds)) = parse(&mut tokens, &mut diagnostics, &settings) {
|
||||
if let Ok(prog) = vmrt::compile(&fs, &ds) {
|
||||
vmrt::execute(&prog);
|
||||
if let Ok(prog) = vmrt::compile(&fs, &ds, &settings) {
|
||||
if let Ok(exit_code) = vmrt::execute(&prog) {
|
||||
crate::message(MessageType::Info, format!("Program exited with {}", exit_code));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
322
src/vmrt/mod.rs
322
src/vmrt/mod.rs
|
@ -1,6 +1,11 @@
|
|||
use std::collections::{VecDeque, HashMap};
|
||||
mod output;
|
||||
|
||||
use crate::{parser::data::*, token::{Token, NumHint, Prim}};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use crate::{
|
||||
parser::data::*,
|
||||
token::{NumHint, Prim, Token},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Data {
|
||||
|
@ -10,6 +15,29 @@ enum Data {
|
|||
Off(u64),
|
||||
}
|
||||
|
||||
impl Data {
|
||||
fn to_int(&self) -> Result<i64, ()> {
|
||||
return match self {
|
||||
Data::Int(v) => Ok(*v),
|
||||
_ => Err(()),
|
||||
};
|
||||
}
|
||||
|
||||
fn to_float(&self) -> Result<f64, ()> {
|
||||
return match self {
|
||||
Data::Rat(v) => Ok(*v),
|
||||
_ => Err(()),
|
||||
};
|
||||
}
|
||||
|
||||
fn to_bool(&self) -> Result<bool, ()> {
|
||||
return match self {
|
||||
Data::Bool(v) => Ok(*v),
|
||||
_ => Err(()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum IntOp {
|
||||
CmpEq,
|
||||
|
@ -22,7 +50,7 @@ enum IntOp {
|
|||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div
|
||||
Div,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -37,7 +65,7 @@ enum RatOp {
|
|||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div
|
||||
Div,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -47,14 +75,14 @@ enum BoolOp {
|
|||
|
||||
And,
|
||||
Or,
|
||||
Xor
|
||||
Xor,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Operation {
|
||||
Int(IntOp),
|
||||
Rat(RatOp),
|
||||
Bool(BoolOp)
|
||||
Bool(BoolOp),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -75,14 +103,11 @@ enum Instr {
|
|||
|
||||
Operation(Operation),
|
||||
|
||||
Exit
|
||||
Exit,
|
||||
}
|
||||
|
||||
|
||||
/// function stack layout
|
||||
/// +----------------------------------+
|
||||
/// | Return address |
|
||||
/// +----------------------------------+
|
||||
/// | Parameter (0) |
|
||||
/// +----------------------------------+
|
||||
/// | Parameter (1) |
|
||||
|
@ -92,14 +117,17 @@ enum Instr {
|
|||
/// | Parameter (n) |
|
||||
/// +----------------------------------+
|
||||
struct Proc {
|
||||
// executable code
|
||||
code: Vec<Instr>,
|
||||
// number of expected arguments
|
||||
args: usize,
|
||||
// hashed declaration is used as "address"
|
||||
addr: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Program {
|
||||
code: HashMap<u64, Proc>
|
||||
procs: HashMap<u64, Proc>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -108,35 +136,40 @@ struct Compiletime<'a> {
|
|||
stacksize: usize,
|
||||
}
|
||||
|
||||
fn parse_term<'a>(term: &VecDeque<Token<'a>>, x: usize, declr: &Vec<Declr<'a>>, ct: &mut Compiletime<'a>, code: &mut Vec<Instr>) -> Result<(), ()>{
|
||||
fn parse_term<'a>(
|
||||
term: &VecDeque<Token<'a>>,
|
||||
x: usize,
|
||||
declr: &Vec<Declr<'a>>,
|
||||
ct: &mut Compiletime<'a>,
|
||||
code: &mut Vec<Instr>,
|
||||
) -> Result<(), ()> {
|
||||
for token in term.iter() {
|
||||
let instr = match token {
|
||||
Token::Number(value, hint, _) => {
|
||||
code.push(Instr::Push(match hint {
|
||||
NumHint::Int => Data::Int(value.parse::<i64>().unwrap()),
|
||||
NumHint::Rat => Data::Rat(value.parse::<f64>().unwrap()),
|
||||
}));
|
||||
NumHint::Int => Data::Int(value.parse::<i64>().unwrap()),
|
||||
NumHint::Rat => Data::Rat(value.parse::<f64>().unwrap()),
|
||||
}));
|
||||
ct.stacksize += 1;
|
||||
},
|
||||
Token::Bool(value, _) => {
|
||||
}
|
||||
Token::Bool(value, _) => {
|
||||
code.push(Instr::Push(Data::Bool(*value)));
|
||||
ct.stacksize += 1;
|
||||
},
|
||||
}
|
||||
Token::Arg(name, _) => {
|
||||
let off = declr[x].get_arg_ord(name);
|
||||
|
||||
code.push(Instr::Load(off + 1));
|
||||
code.push(Instr::Load(off));
|
||||
ct.stacksize += 1;
|
||||
},
|
||||
}
|
||||
Token::Assign(name, _, _) => {
|
||||
ct.vartable.insert(name.clone(), ct.stacksize - 1);
|
||||
},
|
||||
}
|
||||
Token::Var(name, _) => {
|
||||
code.push(Instr::Load(*ct.vartable.get(name).unwrap()));
|
||||
ct.stacksize += 1;
|
||||
}
|
||||
Token::Operator(op, hint, _) => {
|
||||
|
||||
code.push(match op {
|
||||
crate::token::Operator::Or => Instr::Operation(Operation::Bool(BoolOp::Or)),
|
||||
crate::token::Operator::And => Instr::Operation(Operation::Bool(BoolOp::And)),
|
||||
|
@ -145,96 +178,112 @@ fn parse_term<'a>(term: &VecDeque<Token<'a>>, x: usize, declr: &Vec<Declr<'a>>,
|
|||
crate::token::Operator::Add => match hint.unwrap() {
|
||||
Prim::Int => Instr::Operation(Operation::Int(IntOp::Add)),
|
||||
Prim::Rat => Instr::Operation(Operation::Rat(RatOp::Add)),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
},
|
||||
crate::token::Operator::Sub => match hint.unwrap() {
|
||||
Prim::Int => Instr::Operation(Operation::Int(IntOp::Sub)),
|
||||
Prim::Rat => Instr::Operation(Operation::Rat(RatOp::Sub)),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
},
|
||||
crate::token::Operator::Mul => match hint.unwrap() {
|
||||
Prim::Int => Instr::Operation(Operation::Int(IntOp::Mul)),
|
||||
Prim::Rat => Instr::Operation(Operation::Rat(RatOp::Mul)),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
},
|
||||
crate::token::Operator::Div => match hint.unwrap() {
|
||||
Prim::Int => Instr::Operation(Operation::Int(IntOp::Div)),
|
||||
Prim::Rat => Instr::Operation(Operation::Rat(RatOp::Div)),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
},
|
||||
|
||||
crate::token::Operator::Eq => match hint.unwrap() {
|
||||
Prim::Int => Instr::Operation(Operation::Int(IntOp::CmpEq)),
|
||||
Prim::Rat => Instr::Operation(Operation::Rat(RatOp::CmpEq)),
|
||||
Prim::Bool => Instr::Operation(Operation::Bool(BoolOp::CmpEq)),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
},
|
||||
crate::token::Operator::NotEq => match hint.unwrap() {
|
||||
Prim::Int => Instr::Operation(Operation::Int(IntOp::CmpNEq)),
|
||||
Prim::Rat => Instr::Operation(Operation::Rat(RatOp::CmpNEq)),
|
||||
Prim::Bool => Instr::Operation(Operation::Bool(BoolOp::CmpNEq)),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
},
|
||||
crate::token::Operator::Lt => match hint.unwrap() {
|
||||
Prim::Int => Instr::Operation(Operation::Int(IntOp::CmpLt)),
|
||||
Prim::Rat => Instr::Operation(Operation::Rat(RatOp::CmpLt)),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
},
|
||||
crate::token::Operator::Gt => match hint.unwrap() {
|
||||
Prim::Int => Instr::Operation(Operation::Int(IntOp::CmpGt)),
|
||||
Prim::Rat => Instr::Operation(Operation::Rat(RatOp::CmpGt)),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
},
|
||||
crate::token::Operator::GtEq => match hint.unwrap() {
|
||||
Prim::Int => Instr::Operation(Operation::Int(IntOp::CmpGtEq)),
|
||||
Prim::Rat => Instr::Operation(Operation::Rat(RatOp::CmpGtEq)),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
},
|
||||
crate::token::Operator::LtEq => match hint.unwrap() {
|
||||
Prim::Int => Instr::Operation(Operation::Int(IntOp::CmpLtEq)),
|
||||
Prim::Rat => Instr::Operation(Operation::Rat(RatOp::CmpLtEq)),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
},
|
||||
|
||||
crate::token::Operator::Assign => {
|
||||
crate::message(crate::token::MessageType::Critical, format!("Invalid operator: {:?}", op));
|
||||
crate::message(
|
||||
crate::token::MessageType::Critical,
|
||||
format!("Invalid operator: {:?}", op),
|
||||
);
|
||||
return Err(());
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: operatiors
|
||||
// we pop 2 values as arguments and push 1 as result (-2 + 1 = 1)
|
||||
ct.stacksize -= 1;
|
||||
},
|
||||
}
|
||||
Token::Func(name, _) => {
|
||||
for decl in declr.iter() {
|
||||
if decl.name.as_ref().unwrap() == name {
|
||||
code.push(Instr::Call(decl.uuid()))
|
||||
code.push(Instr::Call(decl.uuid()));
|
||||
|
||||
if decl.results {
|
||||
ct.stacksize += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Token::Keyword(keyword, _) => {
|
||||
match keyword {
|
||||
crate::token::Keyword::Yield | crate::token::Keyword::Return => code.push(Instr::Return),
|
||||
_ => ()
|
||||
Token::Keyword(keyword, _) => match keyword {
|
||||
crate::token::Keyword::Yield | crate::token::Keyword::Return => {
|
||||
code.push(Instr::Return)
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
|
||||
|
||||
_ => ()
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_block<'a>(block: &VecDeque<Expr<'a>>, x: usize, declr: &Vec<Declr<'a>>, ct: &mut Compiletime<'a>, prog: &mut Vec<Instr>) -> Result<(), ()> {
|
||||
fn parse_block<'a>(
|
||||
block: &VecDeque<Expr<'a>>,
|
||||
x: usize,
|
||||
declr: &Vec<Declr<'a>>,
|
||||
ct: &mut Compiletime<'a>,
|
||||
prog: &mut Vec<Instr>,
|
||||
) -> Result<(), ()> {
|
||||
for expr in block.iter() {
|
||||
compile_expr(expr, x, declr, ct, prog)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_expr<'a>(expr: &Expr<'a>, x: usize, declr: &Vec<Declr<'a>>, ct: &mut Compiletime<'a>, prog: &mut Vec<Instr>) -> Result<(), ()> {
|
||||
fn compile_expr<'a>(
|
||||
expr: &Expr<'a>,
|
||||
x: usize,
|
||||
declr: &Vec<Declr<'a>>,
|
||||
ct: &mut Compiletime<'a>,
|
||||
prog: &mut Vec<Instr>,
|
||||
) -> Result<(), ()> {
|
||||
match expr {
|
||||
Expr::Block(block) => parse_block(block, x, declr, ct, prog)?,
|
||||
Expr::Term(term) => parse_term(term, x, declr, ct, prog)?,
|
||||
|
@ -243,34 +292,197 @@ fn compile_expr<'a>(expr: &Expr<'a>, x: usize, declr: &Vec<Declr<'a>>, ct: &mut
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile<'a>(funcs: &Vec<Func<'a>>, declrs: &Vec<Declr<'a>>) -> Result<Program, ()> {
|
||||
fn create_proc(declr: &Declr, code: Vec<Instr>) -> Proc {
|
||||
Proc {
|
||||
code,
|
||||
args: if let Some(args) = &declr.args {
|
||||
args.len()
|
||||
} else {
|
||||
0
|
||||
},
|
||||
addr: declr.uuid(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile<'a>(funcs: &Vec<Func<'a>>, declrs: &Vec<Declr<'a>>, settings: &crate::conf::Settings) -> Result<Program, ()> {
|
||||
let mut prog = Program::default();
|
||||
|
||||
for (x, func) in funcs.iter().enumerate() {
|
||||
let mut code = vec![];
|
||||
let mut ct = Compiletime::default();
|
||||
|
||||
// at beginn the is the return address and the parameter on the stack
|
||||
ct.stacksize = declrs[x].args.as_ref().unwrap_or(&vec![]).len() + 1;
|
||||
//at the beginning there are all parameters on the stack
|
||||
ct.stacksize = declrs[x].args.as_ref().unwrap_or(&vec![]).len();
|
||||
|
||||
compile_expr(func.expr.as_ref().unwrap(), x, declrs, &mut ct, &mut code)?;
|
||||
|
||||
println!("{:#?}", code);
|
||||
|
||||
prog.code.insert(declrs[x].uuid(), code);
|
||||
prog.procs
|
||||
.insert(declrs[x].uuid(), create_proc(&declrs[x], code));
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ok(prog)
|
||||
}
|
||||
|
||||
struct Runtimestack {
|
||||
stack: Vec<Data>,
|
||||
}
|
||||
|
||||
impl Runtimestack {
|
||||
pub fn new() -> Runtimestack {
|
||||
Self { stack: vec![] }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, data: Data) {
|
||||
self.stack.push(data);
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<Data> {
|
||||
self.stack.pop()
|
||||
}
|
||||
|
||||
pub fn peek(&mut self, index: usize) -> Data {
|
||||
self.stack[index]
|
||||
}
|
||||
}
|
||||
|
||||
fn call_fn(prog: &Program, proc: &Proc, superstack: &[Data]) -> Result<Option<Data>, ()> {
|
||||
let mut stack = Runtimestack::new();
|
||||
|
||||
// build local procedure stack
|
||||
for i in 0..proc.args {
|
||||
stack.push(superstack[superstack.len() - i - 1].clone());
|
||||
}
|
||||
|
||||
for instr in proc.code.iter() {
|
||||
match instr {
|
||||
Instr::Pop => {
|
||||
stack.pop();
|
||||
}
|
||||
Instr::Return => {
|
||||
return Ok(stack.pop());
|
||||
}
|
||||
Instr::Push(data) => stack.push(*data),
|
||||
Instr::Load(offset) => {
|
||||
let v = stack.peek(*offset);
|
||||
stack.push(v);
|
||||
},
|
||||
Instr::Call(addr) => {
|
||||
if let Some(value) = call_fn(prog, prog.procs.get(addr).unwrap(), &stack.stack)? {
|
||||
stack.push(value);
|
||||
}
|
||||
},
|
||||
Instr::Operation(op) => {
|
||||
let op0:Data = if let Some(data) = stack.pop() {
|
||||
data
|
||||
} else {
|
||||
return Err(());
|
||||
};
|
||||
let op1:Data = if let Some(data) = stack.pop() {
|
||||
data
|
||||
} else {
|
||||
return Err(());
|
||||
};
|
||||
|
||||
match op {
|
||||
Operation::Int(op) => match op {
|
||||
IntOp::Add => stack.push(Data::Int(op1.to_int()? + op0.to_int()?)),
|
||||
IntOp::Sub => stack.push(Data::Int(op1.to_int()? - op0.to_int()?)),
|
||||
IntOp::Mul => stack.push(Data::Int(op1.to_int()? * op0.to_int()?)),
|
||||
IntOp::Div => stack.push(Data::Int(op1.to_int()? / op0.to_int()?)),
|
||||
|
||||
IntOp::CmpEq => {
|
||||
stack.push(Data::Bool(op1.to_int()? == op0.to_int()?))
|
||||
}
|
||||
IntOp::CmpNEq => {
|
||||
stack.push(Data::Bool(op1.to_int()? != op0.to_int()?))
|
||||
}
|
||||
IntOp::CmpLt => stack.push(Data::Bool(op1.to_int()? < op0.to_int()?)),
|
||||
IntOp::CmpGt => stack.push(Data::Bool(op1.to_int()? > op0.to_int()?)),
|
||||
IntOp::CmpGtEq => {
|
||||
stack.push(Data::Bool(op1.to_int()? <= op0.to_int()?))
|
||||
}
|
||||
IntOp::CmpLtEq => {
|
||||
stack.push(Data::Bool(op1.to_int()? >= op0.to_int()?))
|
||||
}
|
||||
},
|
||||
Operation::Rat(op) => match op {
|
||||
RatOp::Add => {
|
||||
stack.push(Data::Rat(op1.to_float()? + op0.to_float()?))
|
||||
}
|
||||
RatOp::Sub => {
|
||||
stack.push(Data::Rat(op1.to_float()? - op0.to_float()?))
|
||||
}
|
||||
RatOp::Mul => {
|
||||
stack.push(Data::Rat(op1.to_float()? * op0.to_float()?))
|
||||
}
|
||||
RatOp::Div => {
|
||||
stack.push(Data::Rat(op1.to_float()? / op0.to_float()?))
|
||||
}
|
||||
|
||||
RatOp::CmpEq => {
|
||||
stack.push(Data::Bool(op1.to_float()? == op0.to_float()?))
|
||||
}
|
||||
RatOp::CmpNEq => {
|
||||
stack.push(Data::Bool(op1.to_float()? != op0.to_float()?))
|
||||
}
|
||||
RatOp::CmpLt => {
|
||||
stack.push(Data::Bool(op1.to_float()? < op0.to_float()?))
|
||||
}
|
||||
RatOp::CmpGt => {
|
||||
stack.push(Data::Bool(op1.to_float()? > op0.to_float()?))
|
||||
}
|
||||
RatOp::CmpGtEq => {
|
||||
stack.push(Data::Bool(op1.to_float()? <= op0.to_float()?))
|
||||
}
|
||||
RatOp::CmpLtEq => {
|
||||
stack.push(Data::Bool(op1.to_float()? >= op0.to_float()?))
|
||||
}
|
||||
},
|
||||
Operation::Bool(op) => match op {
|
||||
BoolOp::Or => {
|
||||
stack.push(Data::Bool(op1.to_bool()? || op0.to_bool()?))
|
||||
}
|
||||
BoolOp::And => {
|
||||
stack.push(Data::Bool(op1.to_bool()? && op0.to_bool()?))
|
||||
}
|
||||
BoolOp::Xor => {
|
||||
stack.push(Data::Bool(op1.to_bool()? ^ op0.to_bool()?))
|
||||
}
|
||||
BoolOp::CmpEq => {
|
||||
stack.push(Data::Bool(op1.to_bool()? == op0.to_bool()?))
|
||||
}
|
||||
BoolOp::CmpNEq => {
|
||||
stack.push(Data::Bool(op1.to_bool()? != op0.to_bool()?))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(stack.pop())
|
||||
}
|
||||
|
||||
pub fn execute(prog: &Program) -> Result<i64, ()> {
|
||||
// declaration of entry function
|
||||
let main_fn_declr = crate::parser::data::Declr::main();
|
||||
|
||||
if let Some(main_fn) = prog.code.get(&main_fn_declr.uuid()) {
|
||||
if let Some(main_fn) = prog.procs.get(&main_fn_declr.uuid()) {
|
||||
|
||||
Ok(0)
|
||||
if let Some(exit_code) = call_fn(prog, main_fn, &[])? {
|
||||
return Ok(exit_code.to_int()?);
|
||||
}
|
||||
|
||||
crate::message(crate::token::MessageType::Critical, "main procedure did not return exit code");
|
||||
return Err(());
|
||||
} else {
|
||||
crate::message(crate::token::MessageType::Critical, "Program has no main() = int function");
|
||||
crate::message(
|
||||
crate::token::MessageType::Critical,
|
||||
"Program has no main() = int function",
|
||||
);
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
use std::io::Write;
|
||||
|
||||
fn print_to_file(file: &mut std::fs::File, prog: &crate::vmrt::Program) -> std::io::Result<()> {
|
||||
for proc in prog.procs.iter() {
|
||||
writeln!(file, "\n0x{:#x}:", proc.0)?;
|
||||
|
||||
for instr in proc.1.code.iter() {
|
||||
writeln!(file, "{}", instr)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn dump_program(prog: &crate::vmrt::Program) {
|
||||
let mut file = std::fs::OpenOptions::new().write(true).create(true).truncate(true).open("prog.vsasm").unwrap();
|
||||
|
||||
if print_to_file(&mut file, prog).is_err() {
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue