use std::fs::File;
use crate::lex::{Lex, Token};
use crate::bytecode::ByteCode;
use crate::value::{self, Value};
// ANCHOR: proto
#[derive(Debug)]
pub struct ParseProto {
pub constants: Vec::<Value>,
pub byte_codes: Vec::<ByteCode>,
locals: Vec::<String>,
lex: Lex,
}
// ANCHOR_END: proto
impl ParseProto {
pub fn load(input: File) -> ParseProto {
let mut proto = ParseProto {
constants: Vec::new(),
byte_codes: Vec::new(),
locals: Vec::new(),
lex: Lex::new(input),
};
proto.chunk();
//println!("constants: {:?}", &proto.constants);
//println!("byte_codes:");
//for c in proto.byte_codes.iter() {
// println!(" {:?}", c);
//}
proto
}
fn chunk(&mut self) {
loop {
match self.lex.next() {
Token::Name(name) if name != "PENDOWN" && name != "MAKE"=> {
//println!("Function check this name: {:?}", name);
self.function_call(name);
}
Token::Name(name) if name == "PENDOWN" || name == "PENUP" || name == "XCOR" ||
name == "YCOR" || name == "HEADING" || name == "COLOR" => {
//println!("Function check this name: {:?}", name);
self.single_function_call(name);
}
Token::Name(name) if name == "MAKE"=> {
//println!("Function check this name: {:?}", name);
self.single_function_call(name);
match self.lex.next() {
Token::String(var_name) => {
//println!("This should be a variable name: {:?}", var_name);
match self.lex.next() {
Token::Integer(value) => {
//println!("This should be a variable value: {:?}", value);
self.make_variable(var_name, Value::Integer(value));
}
_ => panic!("expected Integer as a value"),
}
}
_ => panic!("expected variable name, plz give me a string"),
}
}
Token::Eos => break,
Token::Integer(s) => {
//println!("Function checks this Integer: {:?}", s);
self.function_call(s.to_string());
}
t => {
panic!("unexpected token {:?}", t)
}
}
}
}
fn function_call(&mut self, name: String) {
let ifunc = self.locals.len();
let iarg = ifunc + 1;
let code = self.load_var(ifunc, name);
self.byte_codes.push(code);
match self.lex.next() {
Token::Integer(s) => {
let code = self.load_const(iarg, Value::Integer(s));
self.byte_codes.push(code);
self.byte_codes.push(ByteCode::Call(ifunc as u8, 1));
//println!("Followed by {:?}", s);
}
Token::Name(s) => {
//println!("Followed by {:?}", s.clone());
let code = self.load_const(iarg, Value::String(s));
self.byte_codes.push(code);
self.byte_codes.push(ByteCode::Operate(ifunc as u8));
}
Token::Eos =>{
self.byte_codes.push(ByteCode::Call(ifunc as u8,1));
//print!("Followed by EOS\n");
}
_ => panic!("expected string"),
}
}
fn single_function_call(&mut self, name: String) {
let ifunc = self.locals.len();
let code = self.load_var(ifunc, name);
self.byte_codes.push(code);
self.byte_codes.push(ByteCode::Operate(ifunc as u8))
}
fn load_const(&mut self, dst: usize, c: Value) -> ByteCode {
ByteCode::LoadConst(dst as u8, self.add_const(c) as u16)
}
fn load_var(&mut self, dst: usize, name: String) -> ByteCode {
if let Some(i) = self.get_local(&name) {
// local variable
ByteCode::Move(dst as u8, i as u8)
} else {
// global variable
let ic = self.add_const(Value::String(name));
ByteCode::GetGlobal(dst as u8, ic as u8)
}
}
fn get_local(&self, name: &str) -> Option<usize> {
self.locals.iter().rposition(|v| v == name)
}
fn add_const(&mut self, c: Value) -> usize {
let constants = &mut self.constants;
constants.iter().position(|v| v == &c)
.unwrap_or_else(|| {
constants.push(c);
constants.len() - 1
})
}
fn make_variable(&mut self, name: String, value: Value) {
let name_index = self.add_const(Value::String(name));
let value_index = self.add_const(value);
self.byte_codes.push(ByteCode::SetGlobal(value_index as u8, name_index as u16));
}
}
This Rust code defines a ParseProto struct and its associated methods for parsing a language into bytecode.
The ParseProto struct has four fields:
constants: a vector ofValueobjects, representing the constants in the code.byte_codes: a vector ofByteCodeobjects, representing the bytecode instructions.locals: a vector of strings, representing the local variables in the code.lex: aLexobject, which is a lexer that tokenizes the input code.
The load function takes a File object as input, creates a new ParseProto object, and calls the chunk method on it to parse the input file.
The chunk method loops over the tokens produced by the lexer and calls different functions based on the type and value of each token. For example, if the token is a name that is not “PENDOWN” or “MAKE”, it calls the function_call method. If the token is a name that is “PENDOWN”, “PENUP”, “XCOR”, “YCOR”, “HEADING”, or “COLOR”, it calls the single_function_call method. If the token is a name that is “MAKE”, it calls the make_variable method.
The function_call and single_function_call methods generate bytecode for function calls. They first load the function name into the bytecode, then load the function arguments, and finally generate a Call or Operate bytecode instruction.
The load_const and load_var methods generate bytecode for loading a constant or a variable. They first check if the constant or variable is already in the constants or locals vector. If it is, they generate a LoadConst or Move bytecode instruction. If it’s not, they add it to the constants or locals vector and generate a GetGlobal bytecode instruction.
The get_local method returns the position of a local variable in the locals vector.
The add_const method adds a constant to the constants vector and returns its position.
The make_variable method generates bytecode for creating a new variable. It adds the variable name and value to the constants vector, and generates a SetGlobal bytecode instruction.
