[rlox] Implement Globals (and cleanup a...lot)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use crate::gc::{GcHandle, Object, ObjectType};
|
use crate::gc::{GcHandle, Object};
|
||||||
use std::collections::LinkedList;
|
use std::collections::LinkedList;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -22,7 +22,10 @@ pub enum Op {
|
|||||||
Less,
|
Less,
|
||||||
|
|
||||||
Print,
|
Print,
|
||||||
Pop
|
Pop,
|
||||||
|
|
||||||
|
DefineGlobal { offset: u8 },
|
||||||
|
GetGlobal { offset: u8 },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
@@ -87,11 +90,7 @@ impl Display for Value {
|
|||||||
None => write!(f, "{}", stringified),
|
None => write!(f, "{}", stringified),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Obj(object) => match object.get_otype() {
|
Value::Obj(object) => write!(f, "{}", object),
|
||||||
ObjectType::String => {
|
|
||||||
write!(f, "{}", object)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,6 +137,11 @@ impl Chunk {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_constant_value(&mut self, value: Value) -> &mut Self {
|
||||||
|
self.constants.push(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_constant(&mut self, value: Value, line: usize) -> &mut Self {
|
pub fn add_constant(&mut self, value: Value, line: usize) -> &mut Self {
|
||||||
self.constants.push(value);
|
self.constants.push(value);
|
||||||
self.add_op(
|
self.add_op(
|
||||||
@@ -216,18 +220,18 @@ impl fmt::Debug for TraceInfo<'_> {
|
|||||||
|
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_value_equality() {
|
fn string_value_equality() {
|
||||||
use crate::bc::Value;
|
use crate::bc::Value;
|
||||||
use crate::gc::allocate_string;
|
use crate::gc::GC;
|
||||||
|
|
||||||
let s1 = "bla5";
|
let s1 = "bla5";
|
||||||
let s2 = "bla6";
|
let s2 = "bla6";
|
||||||
|
|
||||||
unsafe {
|
let o1 = GC::new_string(s1);
|
||||||
let o1 = allocate_string(s1).unwrap();
|
let o2 = GC::new_string(s2);
|
||||||
let o2 = allocate_string(s2).unwrap();
|
let o3 = GC::new_string(s2);
|
||||||
let o3 = allocate_string(s2).unwrap();
|
|
||||||
let v1 = Value::from(o1.get_object());
|
let v1 = Value::from(o1.get_object());
|
||||||
let v2 = Value::from(o2.get_object());
|
let v2 = Value::from(o2.get_object());
|
||||||
let v3 = Value::from(o3.get_object());
|
let v3 = Value::from(o3.get_object());
|
||||||
@@ -237,5 +241,4 @@ mod tests {
|
|||||||
assert_eq!(v2, v3);
|
assert_eq!(v2, v3);
|
||||||
assert_eq!(v2, v4);
|
assert_eq!(v2, v4);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
234
rlox/src/gc.rs
234
rlox/src/gc.rs
@@ -1,46 +1,126 @@
|
|||||||
|
#![allow(unused, dead_code)]
|
||||||
|
|
||||||
|
use core::hash;
|
||||||
use std::{
|
use std::{
|
||||||
alloc::{alloc, dealloc, Layout, LayoutError},
|
alloc::{alloc, dealloc, Layout, LayoutError},
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Api
|
||||||
|
|
||||||
|
pub struct GC {}
|
||||||
|
|
||||||
|
impl GC {
|
||||||
|
pub fn new_string(content: &str) -> GcHandle {
|
||||||
|
unsafe { allocate_string(content) }.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_concat_string(first: ObjString, second: ObjString) -> GcHandle {
|
||||||
|
unsafe { concat_string(first, second) }.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free(handle: GcHandle) {
|
||||||
|
unsafe { deallocate_object(handle.object) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Markers
|
||||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
#[repr(usize)]
|
#[repr(usize)]
|
||||||
pub enum ObjectType {
|
pub enum ObjectType {
|
||||||
String,
|
String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
pub(crate) trait IsObject {
|
||||||
struct ObjectHeader {
|
fn otype() -> ObjectType;
|
||||||
otype: ObjectType,
|
fn from_object(object: Object) -> Self;
|
||||||
|
fn upcast(self) -> Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
struct ObjStringHeader {
|
/// Object Hierarchy / Layout stuff
|
||||||
object_header: ObjectHeader,
|
///
|
||||||
len: usize,
|
/// Object
|
||||||
|
/// |
|
||||||
|
/// ObjString
|
||||||
|
///
|
||||||
|
/// Object: --ptr-to--> [ [<otype>], .... data .... ]
|
||||||
|
/// ObjString: --ptr-to--> [[[<otype>], len], ...data... ]
|
||||||
|
/// ^-StringHeader-^
|
||||||
|
/// ^----------StringAlloc--------^
|
||||||
|
///
|
||||||
|
/// GcHandle owns the underlying memory and must not be dropped before the corresponding Objects are.
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct GcHandle {
|
||||||
|
object: Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
impl Drop for GcHandle {
|
||||||
struct ObjString {
|
fn drop(&mut self) {
|
||||||
header: ObjStringHeader,
|
unsafe { deallocate_object(self.object) };
|
||||||
data: [u8],
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn data_offset() -> usize {
|
impl GcHandle {
|
||||||
std::mem::size_of::<ObjStringHeader>()
|
pub fn get_object(&self) -> Object {
|
||||||
|
return self.object;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
ptr: *mut ObjectHeader,
|
ptr: *mut Header,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq)]
|
||||||
|
pub struct ObjString {
|
||||||
|
ptr: *mut StringHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsObject for ObjString {
|
||||||
|
fn otype() -> ObjectType {
|
||||||
|
ObjectType::String
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_object(object: Object) -> ObjString {
|
||||||
|
ObjString { ptr: object.ptr as *mut StringHeader }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upcast(self) -> Object {
|
||||||
|
Object { ptr: self.ptr as *mut Header }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Header {
|
||||||
|
otype: ObjectType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct StringAlloc {
|
||||||
|
header: StringHeader,
|
||||||
|
data: [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct StringHeader {
|
||||||
|
object_header: Header,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const fn data_offset() -> usize {
|
||||||
|
std::mem::size_of::<StringHeader>()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Pretty-print Object
|
||||||
impl Display for Object {
|
impl Display for Object {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self.get_otype() {
|
match self.get_otype() {
|
||||||
ObjectType::String => {
|
ObjectType::String =>
|
||||||
write!(f, "{}", ObjString::as_str(self.ptr as *mut ObjStringHeader))
|
fmt::Display::fmt(&self.downcast::<ObjString>().unwrap(), f)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,16 +129,22 @@ impl Object {
|
|||||||
pub fn get_otype(&self) -> ObjectType {
|
pub fn get_otype(&self) -> ObjectType {
|
||||||
unsafe { (*self.ptr).otype }
|
unsafe { (*self.ptr).otype }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn downcast<T: IsObject>(self) -> Option<T> {
|
||||||
|
if self.get_otype() == T::otype() {
|
||||||
|
Some(T::from_object(self))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Object {
|
impl fmt::Debug for Object {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self.get_otype() {
|
match self.get_otype() {
|
||||||
ObjectType::String => {
|
ObjectType::String => {
|
||||||
let string = self.ptr as *mut ObjStringHeader;
|
let string = self.downcast::<ObjString>().unwrap().as_str();
|
||||||
let data = ObjString::as_str(string);
|
write!(f, "STR {} {:?}", string.len(), &string[..8.min(string.len())])
|
||||||
|
|
||||||
write!(f, "STR {} {:?}", data.len(), &data[..8.min(data.len())],)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,27 +163,58 @@ impl PartialEq for Object {
|
|||||||
|
|
||||||
match (*self.ptr).otype {
|
match (*self.ptr).otype {
|
||||||
ObjectType::String => {
|
ObjectType::String => {
|
||||||
let header = self.ptr as *mut ObjStringHeader;
|
self.downcast::<ObjString>() == other.downcast::<ObjString>()
|
||||||
let other_header = other.ptr as *mut ObjStringHeader;
|
|
||||||
|
|
||||||
if (*header).len != (*other_header).len {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let slice = ObjString::as_str(header);
|
|
||||||
let other_slice = ObjString::as_str(other_header);
|
|
||||||
|
|
||||||
slice == other_slice
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ObjString {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
unsafe {
|
||||||
|
if (*self.ptr).len != (*other.ptr).len {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.as_slice() == other.as_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ObjString {
|
impl ObjString {
|
||||||
|
fn as_slice<'a>(&self) -> &'a [u8] {
|
||||||
|
let length = unsafe { (*self.ptr).len };
|
||||||
|
let (layout_, offset) = StringAlloc::layout(length).unwrap();
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts(
|
||||||
|
(self.ptr as *mut u8).offset(offset as isize),
|
||||||
|
length
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_str<'a>(&self) -> &'a str {
|
||||||
|
unsafe { std::str::from_utf8_unchecked(self.as_slice()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ObjString {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(self.as_str(), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for ObjString {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
std::hash::Hash::hash::<H>(self.as_str(), state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StringAlloc {
|
||||||
fn layout(length: usize) -> Result<(Layout, usize), LayoutError> {
|
fn layout(length: usize) -> Result<(Layout, usize), LayoutError> {
|
||||||
let (layout, offset) = Layout::for_value(&ObjStringHeader {
|
let (layout, offset) = Layout::for_value(&StringHeader {
|
||||||
object_header: ObjectHeader {
|
object_header: Header {
|
||||||
otype: ObjectType::String,
|
otype: ObjectType::String,
|
||||||
},
|
},
|
||||||
len: length,
|
len: length,
|
||||||
@@ -106,47 +223,34 @@ impl ObjString {
|
|||||||
|
|
||||||
Ok((layout.pad_to_align(), offset))
|
Ok((layout.pad_to_align(), offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_bytes<'a>(ptr: *const ObjStringHeader) -> &'a [u8] {
|
|
||||||
unsafe {
|
|
||||||
std::slice::from_raw_parts((ptr as *mut u8).offset(data_offset() as isize), (*ptr).len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_str<'a>(ptr: *const ObjStringHeader) -> &'a str {
|
|
||||||
unsafe { std::str::from_utf8_unchecked(ObjString::as_bytes(ptr)) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn allocate_string_obj<'a>(
|
unsafe fn allocate_string_obj<'a>(
|
||||||
length: usize,
|
length: usize,
|
||||||
) -> Result<(GcHandle, &'a mut [u8]), LayoutError> {
|
) -> Result<(GcHandle, &'a mut [u8]), LayoutError> {
|
||||||
let (layout, offset) = ObjString::layout(length)?;
|
let (layout, offset) = StringAlloc::layout(length)?;
|
||||||
let allocation = alloc(layout);
|
let allocation = alloc(layout);
|
||||||
let data_ptr = allocation.offset(offset as isize);
|
let data_ptr = allocation.offset(offset as isize);
|
||||||
let header = allocation as *mut ObjStringHeader;
|
let header = allocation as *mut StringHeader;
|
||||||
(*header).len = length;
|
(*header).len = length;
|
||||||
(*header).object_header.otype = ObjectType::String;
|
(*header).object_header.otype = ObjectType::String;
|
||||||
let object = Object {
|
let object = Object {
|
||||||
ptr: header as *mut ObjectHeader,
|
ptr: header as *mut Header,
|
||||||
};
|
};
|
||||||
let str = std::slice::from_raw_parts_mut(data_ptr, length);
|
let str = std::slice::from_raw_parts_mut(data_ptr, length);
|
||||||
Ok((GcHandle { object }, str))
|
Ok((GcHandle { object }, str))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn allocate_string(content: &str) -> Result<GcHandle, LayoutError> {
|
unsafe fn allocate_string(content: &str) -> Result<GcHandle, LayoutError> {
|
||||||
let (gc_handle, slice) = allocate_string_obj(content.len())?;
|
let (gc_handle, slice) = allocate_string_obj(content.len())?;
|
||||||
slice.copy_from_slice(content.as_bytes());
|
slice.copy_from_slice(content.as_bytes());
|
||||||
Ok(gc_handle)
|
Ok(gc_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn concat_string(a: Object, b: Object) -> Result<GcHandle, LayoutError> {
|
unsafe fn concat_string(a: ObjString, b: ObjString) -> Result<GcHandle, LayoutError> {
|
||||||
let a_head = a.ptr as *mut ObjStringHeader;
|
let (a_data,b_data) = (a.as_slice(), b.as_slice());
|
||||||
let b_head = b.ptr as *mut ObjStringHeader;
|
|
||||||
let a_data = ObjString::as_bytes(a_head);
|
|
||||||
let b_data = ObjString::as_bytes(b_head);
|
|
||||||
let new_len = a_data.len() + b_data.len();
|
|
||||||
|
|
||||||
|
let new_len = a_data.len() + b_data.len();
|
||||||
let (gc_handle, slice) = allocate_string_obj(new_len)?;
|
let (gc_handle, slice) = allocate_string_obj(new_len)?;
|
||||||
|
|
||||||
slice[..a_data.len()].copy_from_slice(a_data);
|
slice[..a_data.len()].copy_from_slice(a_data);
|
||||||
@@ -158,28 +262,12 @@ pub unsafe fn concat_string(a: Object, b: Object) -> Result<GcHandle, LayoutErro
|
|||||||
unsafe fn deallocate_object(object: Object) {
|
unsafe fn deallocate_object(object: Object) {
|
||||||
match object.get_otype() {
|
match object.get_otype() {
|
||||||
ObjectType::String => {
|
ObjectType::String => {
|
||||||
let header = object.ptr as *mut ObjStringHeader;
|
let header = object.ptr as *mut StringHeader;
|
||||||
dealloc(
|
dealloc(
|
||||||
object.ptr as *mut u8,
|
object.ptr as *mut u8,
|
||||||
ObjString::layout((*header).len).unwrap().0,
|
StringAlloc::layout((*header).len).unwrap().0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct GcHandle {
|
|
||||||
object: Object,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for GcHandle {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { deallocate_object(self.object) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GcHandle {
|
|
||||||
pub fn get_object(&self) -> Object {
|
|
||||||
return self.object;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
166
rlox/src/lc.rs
166
rlox/src/lc.rs
@@ -1,10 +1,12 @@
|
|||||||
use std::fmt;
|
#![allow(dead_code, unused)]
|
||||||
|
|
||||||
|
use std::{collections::hash_map, fmt};
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::str::CharIndices;
|
use std::str::CharIndices;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::bc::{Chunk, Op};
|
use crate::bc::Value;
|
||||||
use crate::gc::allocate_string;
|
use crate::{bc::{Chunk, Op}, gc::GC};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum ScanErrorKind {
|
enum ScanErrorKind {
|
||||||
@@ -285,24 +287,22 @@ struct Parser<'src> {
|
|||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ParseErrorKind {
|
pub enum ParseErrorKind {
|
||||||
InvalidNumber,
|
InvalidNumber,
|
||||||
UnexpectedEOF,
|
|
||||||
IncompleteExpression,
|
IncompleteExpression,
|
||||||
NoSemicolonAfterValue,
|
NoSemicolonAfterValue,
|
||||||
NoSemicolonAfterExpression,
|
NoSemicolonAfterExpression,
|
||||||
|
NoVariableName,
|
||||||
|
NoSemicolonAfterVarDecl,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ParseErrorKind {
|
impl fmt::Display for ParseErrorKind {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ParseErrorKind::InvalidNumber => todo!(),
|
ParseErrorKind::InvalidNumber => todo!(),
|
||||||
ParseErrorKind::UnexpectedEOF => todo!(),
|
ParseErrorKind::IncompleteExpression => write!(f, "Expect expression."),
|
||||||
ParseErrorKind::IncompleteExpression => {
|
ParseErrorKind::NoSemicolonAfterValue => write!(f, "Expect ';' after value."),
|
||||||
write!(f, "Expect expression.")
|
ParseErrorKind::NoSemicolonAfterExpression => write!(f, "Expect ';' after expression."),
|
||||||
},
|
ParseErrorKind::NoVariableName => write!(f, "Expect variable name."),
|
||||||
ParseErrorKind::NoSemicolonAfterValue => todo!(),
|
ParseErrorKind::NoSemicolonAfterVarDecl => write!(f, "Expect ';' after variable declaration."),
|
||||||
ParseErrorKind::NoSemicolonAfterExpression => {
|
|
||||||
write!(f, "Expect ';' after expression.")
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,7 @@ enum Associativity {
|
|||||||
NonAssoc,
|
NonAssoc,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
|
||||||
enum Precedence {
|
enum Precedence {
|
||||||
None,
|
None,
|
||||||
Assignment,
|
Assignment,
|
||||||
@@ -409,6 +409,21 @@ impl<'src> Parser<'src> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_string(&mut self, chunk: &mut Chunk, string: &'src str) -> u8 {
|
||||||
|
match self.intern_table.entry(string) {
|
||||||
|
hash_map::Entry::Occupied(entry) => {
|
||||||
|
entry.get().clone()
|
||||||
|
},
|
||||||
|
hash_map::Entry::Vacant(entry) => {
|
||||||
|
let handle = GC::new_string(string);
|
||||||
|
chunk.add_constant_value(Value::from(handle.get_object()));
|
||||||
|
chunk.allocations.push_front(handle);
|
||||||
|
let offset = chunk.constants.len() as u8 - 1;
|
||||||
|
entry.insert(offset).clone()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn _expression(&mut self, chunk: &mut Chunk, min_prec: Precedence) -> Result<'src, ()> {
|
fn _expression(&mut self, chunk: &mut Chunk, min_prec: Precedence) -> Result<'src, ()> {
|
||||||
match self.scanner.next() {
|
match self.scanner.next() {
|
||||||
None => return Err(self.error_end(ParseErrorKind::IncompleteExpression)),
|
None => return Err(self.error_end(ParseErrorKind::IncompleteExpression)),
|
||||||
@@ -430,18 +445,13 @@ impl<'src> Parser<'src> {
|
|||||||
}
|
}
|
||||||
TokenType::String => {
|
TokenType::String => {
|
||||||
let without_quotes = &token.span[1..(token.span.len() - 1)];
|
let without_quotes = &token.span[1..(token.span.len() - 1)];
|
||||||
match self.intern_table.get(without_quotes) {
|
let offset = self.add_string(chunk, without_quotes);
|
||||||
Some(&index) => {
|
chunk.add_op(
|
||||||
chunk.add_op(Op::Constant { offset: index }, token.line);
|
Op::Constant {
|
||||||
}
|
offset,
|
||||||
None => {
|
},
|
||||||
let object = unsafe { allocate_string(without_quotes) }.unwrap();
|
token.line
|
||||||
chunk.add_constant(object.get_object().into(), token.line);
|
);
|
||||||
self.intern_table
|
|
||||||
.insert(without_quotes, chunk.constants.len() as u8 - 1);
|
|
||||||
chunk.allocations.push_front(object);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
TokenType::LeftParen => {
|
TokenType::LeftParen => {
|
||||||
self._expression(chunk, Precedence::None)?;
|
self._expression(chunk, Precedence::None)?;
|
||||||
@@ -456,6 +466,10 @@ impl<'src> Parser<'src> {
|
|||||||
TokenType::False => {
|
TokenType::False => {
|
||||||
chunk.add_op(Op::False, token.line);
|
chunk.add_op(Op::False, token.line);
|
||||||
}
|
}
|
||||||
|
TokenType::Identifier => {
|
||||||
|
let offset = self.add_string(chunk, token.span);
|
||||||
|
chunk.add_op(Op::GetGlobal {offset}, token.line);
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(self.error_at(token, ParseErrorKind::IncompleteExpression));
|
return Err(self.error_at(token, ParseErrorKind::IncompleteExpression));
|
||||||
}
|
}
|
||||||
@@ -504,31 +518,31 @@ impl<'src> Parser<'src> {
|
|||||||
self._expression(chunk, Precedence::None)
|
self._expression(chunk, Precedence::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn must_consume(&mut self, expected: TokenType, error_kind: ParseErrorKind) -> Result<'src, Token<'src>> {
|
||||||
|
match self.scanner.peek().cloned() {
|
||||||
|
Some(token) if token.ttype == expected => Ok(self.scanner.next().unwrap()),
|
||||||
|
Some(token) => Err(self.error_at(token.clone(), error_kind)),
|
||||||
|
_ => Err(self.error_end(error_kind)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn print_statement(&mut self, print_token: Token<'src>, chunk: &mut Chunk) -> Result<'src, ()> {
|
pub fn print_statement(&mut self, print_token: Token<'src>, chunk: &mut Chunk) -> Result<'src, ()> {
|
||||||
self.expression(chunk)?;
|
self.expression(chunk)?;
|
||||||
chunk.add_op(Op::Print, print_token.line);
|
chunk.add_op(Op::Print, print_token.line);
|
||||||
match self.scanner.next_if(|t| t.ttype == TokenType::Semicolon) {
|
self.must_consume(TokenType::Semicolon, ParseErrorKind::NoSemicolonAfterValue).map(|_| ())
|
||||||
Some(_) => Ok(()),
|
|
||||||
None => {
|
|
||||||
let location = self.scanner.peek().cloned();
|
|
||||||
Err(self.error_at_or_end(location, ParseErrorKind::NoSemicolonAfterValue))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expr_statement(&mut self, chunk: &mut Chunk) -> Result<'src, ()> {
|
fn expr_statement(&mut self, chunk: &mut Chunk) -> Result<'src, ()> {
|
||||||
self.expression(chunk)?;
|
self.expression(chunk)?;
|
||||||
chunk.add_op(Op::Pop, 0);
|
let pop_line =
|
||||||
match self.scanner.next_if(|t| t.ttype == TokenType::Semicolon) {
|
self.must_consume(TokenType::Semicolon, ParseErrorKind::NoSemicolonAfterExpression)
|
||||||
Some(_) => Ok(()),
|
.map(|tok| tok.line)?;
|
||||||
None => {
|
chunk.add_op(Op::Pop, pop_line);
|
||||||
let location = self.scanner.peek().cloned();
|
|
||||||
Err(self.error_at_or_end(location, ParseErrorKind::NoSemicolonAfterExpression))
|
Ok(())
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn statement(&mut self, chunk: &mut Chunk) -> Result<'src, ()> {
|
fn statement(&mut self, chunk: &mut Chunk) -> Result<'src, ()> {
|
||||||
match self.scanner.peek().unwrap().ttype {
|
match self.scanner.peek().unwrap().ttype {
|
||||||
TokenType::Print => {
|
TokenType::Print => {
|
||||||
let print_token = self.scanner.next().unwrap();
|
let print_token = self.scanner.next().unwrap();
|
||||||
@@ -538,7 +552,7 @@ impl<'src> Parser<'src> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn synchronize(&mut self) {
|
fn synchronize(&mut self) {
|
||||||
use TokenType::*;
|
use TokenType::*;
|
||||||
|
|
||||||
while let Some(_token) = self.scanner.next_if(
|
while let Some(_token) = self.scanner.next_if(
|
||||||
@@ -546,7 +560,34 @@ impl<'src> Parser<'src> {
|
|||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn var_declaration(&mut self, var_token: Token<'src>, chunk: &mut Chunk) -> Result<'src, ()> {
|
||||||
|
let ident = self.must_consume(TokenType::Identifier, ParseErrorKind::NoVariableName)?;
|
||||||
|
let offset = self.add_string(chunk, ident.span);
|
||||||
|
|
||||||
|
match self.scanner.peek() {
|
||||||
|
Some(token) if token.ttype == TokenType::Equal => {
|
||||||
|
self.expression(chunk)?;
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
chunk.add_op(Op::Nil, ident.line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk.add_op(Op::DefineGlobal { offset }, ident.line);
|
||||||
|
|
||||||
|
self.must_consume(TokenType::Semicolon, ParseErrorKind::NoSemicolonAfterVarDecl)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn declaration(&mut self, chunk: &mut Chunk) {
|
pub fn declaration(&mut self, chunk: &mut Chunk) {
|
||||||
|
let peeked = self.scanner.peek().unwrap().clone();
|
||||||
|
match peeked.ttype {
|
||||||
|
TokenType::Var => {
|
||||||
|
self.scanner.next();
|
||||||
|
self.var_declaration(peeked, chunk);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
self.statement(chunk).unwrap_or_else(
|
self.statement(chunk).unwrap_or_else(
|
||||||
|err| {
|
|err| {
|
||||||
self.errors.push(err);
|
self.errors.push(err);
|
||||||
@@ -554,6 +595,8 @@ impl<'src> Parser<'src> {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compile(&mut self, chunk: &mut Chunk) {
|
pub fn compile(&mut self, chunk: &mut Chunk) {
|
||||||
while let Some(_) = self.scanner.peek() {
|
while let Some(_) = self.scanner.peek() {
|
||||||
@@ -799,7 +842,7 @@ mod tests {
|
|||||||
use crate::bc::Op::*;
|
use crate::bc::Op::*;
|
||||||
let expected = Chunk::new_with(
|
let expected = Chunk::new_with(
|
||||||
vec![Constant { offset: 0 }, Constant { offset: 1 }, Add, Print],
|
vec![Constant { offset: 0 }, Constant { offset: 1 }, Add, Print],
|
||||||
vec![],
|
vec![1, 1, 1, 1],
|
||||||
vec![Value::from(1.0), Value::from(1.0)],
|
vec![Value::from(1.0), Value::from(1.0)],
|
||||||
LinkedList::new(),
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
@@ -810,14 +853,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn basic_print_string_statement() {
|
fn basic_print_string_statement() {
|
||||||
let source = "print \"string\";";
|
let source = "print \"string\";";
|
||||||
let allocation = unsafe { allocate_string("string").unwrap() };
|
let allocation = GC::new_string("string");
|
||||||
let object = allocation.get_object();
|
let object = allocation.get_object();
|
||||||
let mut allocations = LinkedList::new();
|
let mut allocations = LinkedList::new();
|
||||||
allocations.push_front(allocation);
|
allocations.push_front(allocation);
|
||||||
use crate::bc::Op::*;
|
use crate::bc::Op::*;
|
||||||
let expected = Chunk::new_with(
|
let expected = Chunk::new_with(
|
||||||
vec![Constant { offset: 0 }, Print],
|
vec![Constant { offset: 0 }, Print],
|
||||||
vec![],
|
vec![1, 1],
|
||||||
vec![Value::from(object)],
|
vec![Value::from(object)],
|
||||||
allocations,
|
allocations,
|
||||||
);
|
);
|
||||||
@@ -831,11 +874,40 @@ mod tests {
|
|||||||
use crate::bc::Op::*;
|
use crate::bc::Op::*;
|
||||||
let expected = Chunk::new_with(
|
let expected = Chunk::new_with(
|
||||||
vec![Constant { offset: 0 }, Constant { offset: 1 }, Divide, Pop],
|
vec![Constant { offset: 0 }, Constant { offset: 1 }, Divide, Pop],
|
||||||
vec![],
|
vec![1, 1, 1, 1],
|
||||||
vec![Value::from(1.0), Value::from(1.0)],
|
vec![Value::from(1.0), Value::from(1.0)],
|
||||||
LinkedList::new(),
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_program(source, &expected);
|
test_parse_program(source, &expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_var_decl() {
|
||||||
|
let source = "var x;";
|
||||||
|
use crate::bc::Op::*;
|
||||||
|
let x = GC::new_string("x");
|
||||||
|
let expected = Chunk::new_with(
|
||||||
|
vec![Nil, DefineGlobal { offset: 0 }],
|
||||||
|
vec![1, 1],
|
||||||
|
vec![x.get_object().into()],
|
||||||
|
LinkedList::new(),
|
||||||
|
);
|
||||||
|
|
||||||
|
test_parse_program(source, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn basic_var_decl_with_initializer() {
|
||||||
|
let source = "var x = 1 + 1;";
|
||||||
|
use crate::bc::Op::*;
|
||||||
|
let x = GC::new_string("x");
|
||||||
|
let expected = Chunk::new_with(
|
||||||
|
vec![Constant {offset: 1}, Constant {offset: 2}, Add, DefineGlobal { offset: 0 }],
|
||||||
|
vec![],
|
||||||
|
vec![x.get_object().into(), Value::from(1.0), Value::from(1.0)],
|
||||||
|
LinkedList::new(),
|
||||||
|
);
|
||||||
|
|
||||||
|
test_parse_program(source, &expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::bc::{Chunk, Op, TraceInfo, Value};
|
use crate::bc::{Chunk, Op, TraceInfo, Value};
|
||||||
use crate::gc::{concat_string, GcHandle, ObjectType};
|
use crate::gc::{GcHandle, ObjString, ObjectType, GC};
|
||||||
use std::collections::LinkedList;
|
use std::collections::{HashMap, LinkedList};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -72,6 +72,7 @@ impl VM {
|
|||||||
output: &mut Output,
|
output: &mut Output,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut allocations: LinkedList<GcHandle> = LinkedList::new();
|
let mut allocations: LinkedList<GcHandle> = LinkedList::new();
|
||||||
|
let mut globals: HashMap<ObjString, Value> = HashMap::new();
|
||||||
|
|
||||||
while self.pc < chunk.code.len() {
|
while self.pc < chunk.code.len() {
|
||||||
let instr = chunk.code[self.pc];
|
let instr = chunk.code[self.pc];
|
||||||
@@ -126,7 +127,8 @@ impl VM {
|
|||||||
match a {
|
match a {
|
||||||
Value::Obj(a) => match a.get_otype() {
|
Value::Obj(a) => match a.get_otype() {
|
||||||
ObjectType::String => {
|
ObjectType::String => {
|
||||||
let new_obj = unsafe { concat_string(a, b).unwrap() };
|
let (a, b) = (a.downcast().unwrap(), b.downcast().unwrap());
|
||||||
|
let new_obj = GC::new_concat_string(a, b);
|
||||||
self.push(Value::from(new_obj.get_object()));
|
self.push(Value::from(new_obj.get_object()));
|
||||||
allocations.push_front(new_obj);
|
allocations.push_front(new_obj);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -178,6 +180,34 @@ impl VM {
|
|||||||
},
|
},
|
||||||
Op::Pop => {
|
Op::Pop => {
|
||||||
self.pop()?;
|
self.pop()?;
|
||||||
|
},
|
||||||
|
Op::DefineGlobal { offset } => {
|
||||||
|
let ident = chunk.constants[offset as usize].clone();
|
||||||
|
if let Value::Obj(name) = ident {
|
||||||
|
let name = name.downcast::<ObjString>().unwrap();
|
||||||
|
globals.entry(name).or_insert(self.pop()?);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}?
|
||||||
|
},
|
||||||
|
Op::GetGlobal { offset } => {
|
||||||
|
let ident = match chunk.constants[offset as usize] {
|
||||||
|
Value::Obj(object) => object.downcast::<ObjString>().unwrap(),
|
||||||
|
_ => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(value) = globals.get(&ident) {
|
||||||
|
self.push(value.clone());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(
|
||||||
|
VMError::Runtime(
|
||||||
|
format!("Undefined variable '{}'.", ident).into(),
|
||||||
|
self.pc,
|
||||||
|
))
|
||||||
|
}?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,6 +220,8 @@ impl VM {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::LinkedList;
|
use std::collections::LinkedList;
|
||||||
|
|
||||||
|
use crate::gc::GC;
|
||||||
|
|
||||||
use super::{Chunk, Op, VMError, Value, VM};
|
use super::{Chunk, Op, VMError, Value, VM};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -269,4 +301,29 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_write_globals() -> Result<(), VMError> {
|
||||||
|
let var = GC::new_string("global");
|
||||||
|
use Op::*;
|
||||||
|
let chunk = Chunk::new_with(
|
||||||
|
vec![
|
||||||
|
Constant { offset: 0 },
|
||||||
|
DefineGlobal { offset: 1 },
|
||||||
|
Constant { offset: 2 },
|
||||||
|
GetGlobal { offset: 1 },
|
||||||
|
Multiply,
|
||||||
|
],
|
||||||
|
vec![],
|
||||||
|
vec![Value::from(5.0), Value::from(var.get_object()), Value::from(6.0)],
|
||||||
|
LinkedList::new()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut vm = VM::new();
|
||||||
|
vm.stdrun(&chunk)?;
|
||||||
|
|
||||||
|
assert_eq!(vm.stack, vec![Value::Number(30.0)]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user