Add a GC (#21)
This commit is contained in:
parent
75da2808d3
commit
73024d7db0
|
@ -6,8 +6,6 @@ edition = "2021"
|
|||
[dependencies]
|
||||
logos = "0.12.0"
|
||||
ariadne = { git = "https://github.com/zesterer/ariadne", rev = "689782a3531c3d4a3e53af998b059c733729c42e" }
|
||||
either = "1.6.1"
|
||||
hexponent = "0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "1.10.0"
|
||||
|
|
201
LICENSE
Normal file
201
LICENSE
Normal file
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -23,3 +23,8 @@ We do not support the following Lua 5.4 features:
|
|||
- `goto` statements and labels
|
||||
- `\z` string literal escapes
|
||||
- function calls without parentheses
|
||||
|
||||
## License
|
||||
|
||||
Zaia is licensed under the Apache v2.0 license.
|
||||
See LICENSE for more information.
|
||||
|
|
45
src/engine/gc/handle.rs
Normal file
45
src/engine/gc/handle.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use std::{cmp, hash};
|
||||
|
||||
pub struct Handle<T> {
|
||||
ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<T> Handle<T> {
|
||||
pub fn new(ptr: *mut T) -> Self {
|
||||
Handle { ptr }
|
||||
}
|
||||
|
||||
pub unsafe fn get_unchecked<'a>(self) -> &'a T {
|
||||
&*self.ptr
|
||||
}
|
||||
|
||||
pub unsafe fn get_unchecked_mut<'a>(self) -> &'a mut T {
|
||||
&mut *self.ptr
|
||||
}
|
||||
|
||||
pub unsafe fn destroy(self) {
|
||||
Box::from_raw(self.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Handle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Handle { ptr: self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for Handle<T> {}
|
||||
|
||||
impl<T> cmp::PartialEq for Handle<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.ptr == other.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> cmp::Eq for Handle<T> {}
|
||||
|
||||
impl<T> hash::Hash for Handle<T> {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
self.ptr.hash(state);
|
||||
}
|
||||
}
|
49
src/engine/gc/heuristics.rs
Normal file
49
src/engine/gc/heuristics.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use std::cell::Cell;
|
||||
|
||||
use super::{trace::Trace, Heap};
|
||||
|
||||
const INITIAL_THRESHOLD: usize = 128 * 1024;
|
||||
const THRESHOLD_FACTOR: f32 = 1.75;
|
||||
|
||||
pub struct Heuristics {
|
||||
allocated: Cell<usize>,
|
||||
threshold: Cell<usize>,
|
||||
in_cycle: Cell<bool>,
|
||||
}
|
||||
|
||||
impl Heuristics {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
allocated: Cell::new(0),
|
||||
threshold: Cell::new(INITIAL_THRESHOLD),
|
||||
in_cycle: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn threshold(&self) -> usize {
|
||||
(self.threshold.get() as f32 * THRESHOLD_FACTOR) as usize
|
||||
}
|
||||
|
||||
fn check_collect<T, B>(&self, heap: &Heap<T, B>)
|
||||
where
|
||||
B: Trace<T>,
|
||||
{
|
||||
if !self.in_cycle.get() && self.allocated >= self.threshold {
|
||||
self.in_cycle.set(true);
|
||||
heap.collect();
|
||||
self.in_cycle.set(false);
|
||||
|
||||
let new_threshold = self.threshold();
|
||||
self.threshold.set(new_threshold);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_allocated<T, B, F>(&self, heap: &Heap<T, B>, f: F)
|
||||
where
|
||||
B: Trace<T>,
|
||||
F: FnOnce(usize) -> usize,
|
||||
{
|
||||
self.allocated.update(f);
|
||||
self.check_collect(heap);
|
||||
}
|
||||
}
|
141
src/engine/gc/mod.rs
Normal file
141
src/engine/gc/mod.rs
Normal file
|
@ -0,0 +1,141 @@
|
|||
mod handle;
|
||||
mod heuristics;
|
||||
mod trace;
|
||||
|
||||
use std::{alloc, cell::RefCell, collections::HashSet, ptr, rc::Rc};
|
||||
|
||||
pub use handle::Handle;
|
||||
use heuristics::Heuristics;
|
||||
pub use trace::{Trace, Visitor};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Heap<T, B> {
|
||||
internal: Rc<HeapInternal<T, B>>,
|
||||
}
|
||||
|
||||
impl<T, B> Heap<T, B>
|
||||
where
|
||||
B: Trace<T>,
|
||||
{
|
||||
pub fn new(base: B) -> Self {
|
||||
Heap {
|
||||
internal: Rc::new(HeapInternal::new(base)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&self, value: T) -> Handle<T> {
|
||||
self.internal.insert(value)
|
||||
}
|
||||
|
||||
pub fn collect(&self) {
|
||||
self.internal.collect();
|
||||
}
|
||||
}
|
||||
|
||||
struct Tree<T> {
|
||||
objects: HashSet<Handle<T>>,
|
||||
visitor: Visitor<T>,
|
||||
}
|
||||
|
||||
struct HeapInternal<T, B> {
|
||||
heuristics: Heuristics,
|
||||
tree: RefCell<Tree<T>>,
|
||||
base: B,
|
||||
}
|
||||
|
||||
impl<T, B> HeapInternal<T, B>
|
||||
where
|
||||
B: Trace<T>,
|
||||
{
|
||||
fn new(base: B) -> Self {
|
||||
let tree = RefCell::new(Tree {
|
||||
objects: HashSet::new(),
|
||||
visitor: Visitor::new(),
|
||||
});
|
||||
|
||||
Self {
|
||||
heuristics: Heuristics::new(),
|
||||
tree,
|
||||
base,
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&self, value: T) -> Handle<T> {
|
||||
let ptr = Box::into_raw(Box::new(value));
|
||||
let handle = Handle::new(ptr);
|
||||
self.tree.borrow_mut().objects.insert(handle);
|
||||
handle
|
||||
}
|
||||
|
||||
fn collect(&self) {
|
||||
let mut tree = self.tree.borrow_mut();
|
||||
tree.visitor.run(&self.base);
|
||||
for object in tree.visitor.unmarked(&tree.objects) {
|
||||
unsafe {
|
||||
object.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
tree.visitor.reset();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T, B> alloc::Allocator for Heap<T, B>
|
||||
where
|
||||
B: Trace<T>,
|
||||
{
|
||||
fn allocate(&self, layout: alloc::Layout) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
|
||||
self.internal
|
||||
.heuristics
|
||||
.update_allocated(self, |x| x + layout.size());
|
||||
|
||||
alloc::Global.allocate(layout)
|
||||
}
|
||||
|
||||
unsafe fn deallocate(&self, ptr: ptr::NonNull<u8>, layout: alloc::Layout) {
|
||||
self.internal
|
||||
.heuristics
|
||||
.update_allocated(self, |x| x - layout.size());
|
||||
|
||||
alloc::Global.deallocate(ptr, layout)
|
||||
}
|
||||
|
||||
unsafe fn grow(
|
||||
&self,
|
||||
ptr: ptr::NonNull<u8>,
|
||||
old_layout: alloc::Layout,
|
||||
new_layout: alloc::Layout,
|
||||
) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
|
||||
self.internal
|
||||
.heuristics
|
||||
.update_allocated(self, |x| x + new_layout.size() - old_layout.size());
|
||||
|
||||
alloc::Global.grow(ptr, old_layout, new_layout)
|
||||
}
|
||||
|
||||
unsafe fn grow_zeroed(
|
||||
&self,
|
||||
ptr: ptr::NonNull<u8>,
|
||||
old_layout: alloc::Layout,
|
||||
new_layout: alloc::Layout,
|
||||
) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
|
||||
self.internal
|
||||
.heuristics
|
||||
.update_allocated(self, |x| x + new_layout.size() - old_layout.size());
|
||||
|
||||
alloc::Global.grow_zeroed(ptr, old_layout, new_layout)
|
||||
}
|
||||
|
||||
unsafe fn shrink(
|
||||
&self,
|
||||
ptr: ptr::NonNull<u8>,
|
||||
old_layout: alloc::Layout,
|
||||
new_layout: alloc::Layout,
|
||||
) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
|
||||
self.internal
|
||||
.heuristics
|
||||
.update_allocated(self, |x| x + new_layout.size() - old_layout.size());
|
||||
|
||||
alloc::Global.shrink(ptr, old_layout, new_layout)
|
||||
}
|
||||
}
|
38
src/engine/gc/trace.rs
Normal file
38
src/engine/gc/trace.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use super::handle::Handle;
|
||||
|
||||
pub trait Trace<T> {
|
||||
fn visit(&self, visitor: &mut Visitor<T>);
|
||||
}
|
||||
|
||||
pub struct Visitor<T> {
|
||||
marked: HashSet<Handle<T>>,
|
||||
}
|
||||
|
||||
impl<T> Visitor<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
marked: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark(&mut self, handle: Handle<T>) {
|
||||
self.marked.insert(handle);
|
||||
}
|
||||
|
||||
pub fn run(&mut self, root: &dyn Trace<T>) {
|
||||
root.visit(self);
|
||||
}
|
||||
|
||||
pub fn unmarked<'a>(
|
||||
&'a self,
|
||||
objects: &'a HashSet<Handle<T>>,
|
||||
) -> impl Iterator<Item = Handle<T>> + 'a {
|
||||
objects.difference(&self.marked).copied()
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.marked.clear();
|
||||
}
|
||||
}
|
8
src/engine/mod.rs
Normal file
8
src/engine/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
mod gc;
|
||||
mod value;
|
||||
|
||||
use value::Table;
|
||||
|
||||
pub struct Engine {
|
||||
environment: Table,
|
||||
}
|
92
src/engine/value.rs
Normal file
92
src/engine/value.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use std::{cmp, collections::HashMap, hash};
|
||||
|
||||
use super::gc::Handle;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Value {
|
||||
Boolean(bool),
|
||||
Integer(i32),
|
||||
Float(f32),
|
||||
String(Vec<u8>),
|
||||
Ref(Handle<RefValue>),
|
||||
}
|
||||
|
||||
impl cmp::PartialEq for Value {
|
||||
fn eq(&self, other: &Value) -> bool {
|
||||
match (self, other) {
|
||||
(Value::Boolean(a), Value::Boolean(b)) => a == b,
|
||||
(Value::Integer(a), Value::Integer(b)) => a == b,
|
||||
(Value::Float(a), Value::Float(b)) => a == b,
|
||||
(Value::String(a), Value::String(b)) => a == b,
|
||||
(Value::Ref(a), Value::Ref(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl cmp::Eq for Value {}
|
||||
|
||||
impl cmp::PartialOrd for Value {
|
||||
fn partial_cmp(&self, other: &Value) -> Option<cmp::Ordering> {
|
||||
match (self, other) {
|
||||
(Value::Boolean(a), Value::Boolean(b)) => a.partial_cmp(b),
|
||||
(Value::Integer(a), Value::Integer(b)) => a.partial_cmp(b),
|
||||
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
|
||||
(Value::String(a), Value::String(b)) => a.partial_cmp(b),
|
||||
(Value::Ref(_), Value::Ref(_)) => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl cmp::Ord for Value {
|
||||
fn cmp(&self, other: &Value) -> cmp::Ordering {
|
||||
match (self, other) {
|
||||
(Value::Boolean(a), Value::Boolean(b)) => a.cmp(b),
|
||||
(Value::Integer(a), Value::Integer(b)) => a.cmp(b),
|
||||
(Value::Float(a), Value::Float(b)) => float_cmp(*a, *b),
|
||||
(Value::String(a), Value::String(b)) => a.cmp(b),
|
||||
(Value::Ref(_), Value::Ref(_)) => cmp::Ordering::Equal,
|
||||
_ => cmp::Ordering::Equal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl hash::Hash for Value {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
Value::Boolean(a) => a.hash(state),
|
||||
Value::Integer(a) => a.hash(state),
|
||||
Value::Float(a) => a.to_ne_bytes().hash(state),
|
||||
Value::String(ref a) => a.hash(state),
|
||||
Value::Ref(ref a) => a.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn float_cmp(a: f32, b: f32) -> cmp::Ordering {
|
||||
let convert = |f: f32| {
|
||||
let i = f.to_bits();
|
||||
let bit = 1 << (32 - 1);
|
||||
if i & bit == 0 {
|
||||
i | bit
|
||||
} else {
|
||||
!i
|
||||
}
|
||||
};
|
||||
|
||||
convert(a).cmp(&convert(b))
|
||||
}
|
||||
|
||||
pub enum RefValue {
|
||||
Function(Function),
|
||||
Table(Table),
|
||||
}
|
||||
|
||||
pub struct Function {
|
||||
scope: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
pub struct Table {
|
||||
inner: HashMap<Value, Value>,
|
||||
}
|
|
@ -1,3 +1,8 @@
|
|||
#![feature(allocator_api)]
|
||||
#![feature(cell_update)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod engine;
|
||||
pub mod parser;
|
||||
pub mod syntax_tree;
|
||||
mod utf8;
|
||||
|
|
398
src/parser/hex_float.rs
Normal file
398
src/parser/hex_float.rs
Normal file
|
@ -0,0 +1,398 @@
|
|||
// This module is a giant mess. Hexadecimal floats suck to parse and everyone
|
||||
// seems to do it differently. I'm not sure if this is the best way to do it,
|
||||
// but it works.
|
||||
//
|
||||
// Thanks to Julia Scheaffer for coming up with the code this is based on.
|
||||
// You will be remembered for your contribution to society.
|
||||
|
||||
use core::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Indicates the preicsision of a conversion
|
||||
pub enum ConversionResult<T> {
|
||||
/// The conversion was precise and the result represents the original
|
||||
/// exactly.
|
||||
Precise(T),
|
||||
|
||||
/// The conversion was imprecise and the result is as close to the original
|
||||
/// as possible.
|
||||
Imprecise(T),
|
||||
}
|
||||
|
||||
impl<T> ConversionResult<T> {
|
||||
/// Convert the result to it's contained type.
|
||||
pub fn inner(self) -> T {
|
||||
match self {
|
||||
ConversionResult::Precise(f) => f,
|
||||
ConversionResult::Imprecise(f) => f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type for parsing hexadecimal literals.
|
||||
///
|
||||
/// See the [`ParseErrorKind`](enum.ParseErrorKind.html) documentation for more
|
||||
/// details about the kinds of errors and examples.
|
||||
///
|
||||
/// `ParseError` only implements `std::error::Error` when the `std` feature is
|
||||
/// enabled.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct ParseError {
|
||||
/// Kind of error
|
||||
pub kind: ParseErrorKind,
|
||||
/// Approximate index of the error in the source data. This will always be
|
||||
/// an index to the source, except for when something is expected and
|
||||
/// nothing is found, in this case, `index` will be the length of the input.
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
/// Kind of parsing error.
|
||||
///
|
||||
/// Used in [`ParseError`](struct.ParseError.html)
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum ParseErrorKind {
|
||||
/// No prefix was found. Hexadecimal literals must start with a "0x" or "0X"
|
||||
/// prefix.
|
||||
///
|
||||
/// Example: `0.F`
|
||||
MissingPrefix,
|
||||
/// No digits were found. Hexadecimals literals must have digits before or
|
||||
/// after the decimal point.
|
||||
///
|
||||
/// Example: `0x.` `0x.p1`
|
||||
MissingDigits,
|
||||
/// Hexadecimal literals with a "p" or "P" to indicate an float must have
|
||||
/// an exponent.
|
||||
///
|
||||
/// Example: `0xb.0p` `0x1p-`
|
||||
MissingExponent,
|
||||
/// The exponent of a hexidecimal literal must fit into a signed 32-bit
|
||||
/// integer.
|
||||
///
|
||||
/// Example: `0x1p3000000000`
|
||||
ExponentOverflow,
|
||||
/// The end of the literal was expected, but more bytes were found.
|
||||
///
|
||||
/// Example: `0x1.g`
|
||||
MissingEnd,
|
||||
}
|
||||
|
||||
impl ParseErrorKind {
|
||||
fn at(self, index: usize) -> ParseError {
|
||||
ParseError { kind: self, index }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.kind {
|
||||
ParseErrorKind::MissingPrefix => write!(f, "literal must have hex prefix"),
|
||||
ParseErrorKind::MissingDigits => write!(f, "literal must have digits"),
|
||||
ParseErrorKind::MissingExponent => write!(f, "exponent not present"),
|
||||
ParseErrorKind::ExponentOverflow => write!(f, "exponent too large to fit in integer"),
|
||||
ParseErrorKind::MissingEnd => {
|
||||
write!(f, "extra bytes were found at the end of float literal")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ParseError {}
|
||||
|
||||
/// Represents a floating point literal
|
||||
///
|
||||
/// This struct is a representation of the text, that can be used to convert to
|
||||
/// both single- and double-precision floats.
|
||||
///
|
||||
/// `FloatLiteral` is not `Copy`-able because it contains a vector of the
|
||||
/// digits from the source data.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatLiteral {
|
||||
is_positive: bool,
|
||||
// These are the values of the digits, not the digits in ascii form.
|
||||
digits: Vec<u8>,
|
||||
decimal_offset: i32,
|
||||
exponent: i32,
|
||||
}
|
||||
|
||||
/// Get the byte index of the start of `sub_slice` in `master_slice`
|
||||
fn get_cursed_index(master_slice: &[u8], sub_slice: &[u8]) -> usize {
|
||||
(sub_slice.as_ptr() as usize).saturating_sub(master_slice.as_ptr() as usize)
|
||||
}
|
||||
|
||||
impl FloatLiteral {
|
||||
/// Convert the `self` to an `f32` or `f64` and return the precision of the
|
||||
/// conversion.
|
||||
pub fn convert<F: FPFormat>(self) -> ConversionResult<F> {
|
||||
F::from_literal(self)
|
||||
}
|
||||
|
||||
/// Parse a slice of bytes into a `FloatLiteral`.
|
||||
///
|
||||
/// This is based on hexadecimal floating constants in the C11
|
||||
/// specification, section [6.4.4.2](http://port70.net/~nsz/c/c11/n1570.html#6.4.4.2).
|
||||
pub fn from_bytes(data: &[u8]) -> Result<FloatLiteral, ParseError> {
|
||||
let original_data = data;
|
||||
|
||||
let (is_positive, data) = match data.get(0) {
|
||||
Some(b'+') => (true, &data[1..]),
|
||||
Some(b'-') => (false, &data[1..]),
|
||||
_ => (true, data),
|
||||
};
|
||||
|
||||
let data = match data.get(0..2) {
|
||||
Some(b"0X") | Some(b"0x") => &data[2..],
|
||||
_ => return Err(ParseErrorKind::MissingPrefix.at(0)),
|
||||
};
|
||||
|
||||
let (ipart, data) = consume_hex_digits(data);
|
||||
|
||||
let (fpart, data): (&[_], _) = if data.get(0) == Some(&b'.') {
|
||||
let (fpart, data) = consume_hex_digits(&data[1..]);
|
||||
(fpart, data)
|
||||
} else {
|
||||
(b"", data)
|
||||
};
|
||||
|
||||
// Must have digits before or after the decimal point.
|
||||
if fpart.is_empty() && ipart.is_empty() {
|
||||
return Err(ParseErrorKind::MissingDigits.at(get_cursed_index(original_data, data)));
|
||||
}
|
||||
|
||||
let (exponent, data) = match data.get(0) {
|
||||
Some(b'P') | Some(b'p') => {
|
||||
let data = &data[1..];
|
||||
|
||||
let sign_offset = match data.get(0) {
|
||||
Some(b'+') | Some(b'-') => 1,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
let exponent_digits_offset = data[sign_offset..]
|
||||
.iter()
|
||||
.position(|&b| !matches!(b, b'0'..=b'9'))
|
||||
.unwrap_or_else(|| data[sign_offset..].len());
|
||||
|
||||
if exponent_digits_offset == 0 {
|
||||
return Err(
|
||||
ParseErrorKind::MissingExponent.at(get_cursed_index(original_data, data))
|
||||
);
|
||||
}
|
||||
|
||||
// The exponent should always contain valid utf-8 beacuse it
|
||||
// consumes a sign, and base-10 digits.
|
||||
let exponent: i32 =
|
||||
core::str::from_utf8(&data[..sign_offset + exponent_digits_offset])
|
||||
.expect("exponent did not contain valid utf-8")
|
||||
.parse()
|
||||
.map_err(|_| {
|
||||
ParseErrorKind::ExponentOverflow
|
||||
.at(get_cursed_index(original_data, data))
|
||||
})?;
|
||||
|
||||
(exponent, &data[sign_offset + exponent_digits_offset..])
|
||||
},
|
||||
_ => (0, data),
|
||||
};
|
||||
|
||||
if !data.is_empty() {
|
||||
return Err(ParseErrorKind::MissingEnd.at(get_cursed_index(original_data, data)));
|
||||
}
|
||||
|
||||
let mut raw_digits = ipart.to_vec();
|
||||
raw_digits.extend_from_slice(fpart);
|
||||
|
||||
let first_digit = raw_digits.iter().position(|&d| d != b'0');
|
||||
|
||||
let (digits, decimal_offset) = if let Some(first_digit) = first_digit {
|
||||
// Unwrap is safe because there is at least one digit.
|
||||
let last_digit = raw_digits.iter().rposition(|&d| d != b'0').unwrap();
|
||||
let decimal_offset = (ipart.len() as i32) - (first_digit as i32);
|
||||
|
||||
// Trim off the leading zeros
|
||||
raw_digits.truncate(last_digit + 1);
|
||||
// Trim off the trailing zeros
|
||||
raw_digits.drain(..first_digit);
|
||||
|
||||
// Convert all the digits from ascii to their values.
|
||||
for item in raw_digits.iter_mut() {
|
||||
*item = hex_digit_to_int(*item).unwrap();
|
||||
}
|
||||
|
||||
(raw_digits, decimal_offset)
|
||||
} else {
|
||||
(Vec::new(), 0)
|
||||
};
|
||||
|
||||
Ok(FloatLiteral {
|
||||
is_positive,
|
||||
digits,
|
||||
decimal_offset,
|
||||
exponent,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl core::str::FromStr for FloatLiteral {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<FloatLiteral, ParseError> {
|
||||
FloatLiteral::from_bytes(s.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FloatLiteral> for f32 {
|
||||
fn from(literal: FloatLiteral) -> f32 {
|
||||
literal.convert().inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FloatLiteral> for f64 {
|
||||
fn from(literal: FloatLiteral) -> f64 {
|
||||
literal.convert().inner()
|
||||
}
|
||||
}
|
||||
|
||||
fn hex_digit_to_int(digit: u8) -> Option<u8> {
|
||||
match digit {
|
||||
b'0' => Some(0x0),
|
||||
b'1' => Some(0x1),
|
||||
b'2' => Some(0x2),
|
||||
b'3' => Some(0x3),
|
||||
b'4' => Some(0x4),
|
||||
b'5' => Some(0x5),
|
||||
b'6' => Some(0x6),
|
||||
b'7' => Some(0x7),
|
||||
b'8' => Some(0x8),
|
||||
b'9' => Some(0x9),
|
||||
b'a' | b'A' => Some(0xa),
|
||||
b'b' | b'B' => Some(0xb),
|
||||
b'c' | b'C' => Some(0xc),
|
||||
b'd' | b'D' => Some(0xd),
|
||||
b'e' | b'E' => Some(0xe),
|
||||
b'f' | b'F' => Some(0xf),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn consume_hex_digits(data: &[u8]) -> (&[u8], &[u8]) {
|
||||
let i = data
|
||||
.iter()
|
||||
.position(|&b| hex_digit_to_int(b).is_none())
|
||||
.unwrap_or(data.len());
|
||||
|
||||
data.split_at(i)
|
||||
}
|
||||
|
||||
use core::ops;
|
||||
|
||||
macro_rules! impl_fpformat {
|
||||
($fp_type:ty, $bits_type:ty, $exponent_bits: literal, $mantissa_bits: literal, $from_bits: expr, $infinity: expr, $max_exp: expr, $min_exp: expr) => {
|
||||
impl FPFormat for $fp_type {
|
||||
fn from_literal(literal: FloatLiteral) -> ConversionResult<$fp_type> {
|
||||
const EXPONENT_BITS: u32 = $exponent_bits;
|
||||
const MANTISSA_BITS: u32 = $mantissa_bits;
|
||||
|
||||
const TOTAL_BITS: u32 = 1 + EXPONENT_BITS + MANTISSA_BITS;
|
||||
|
||||
// The spec always gives an exponent bias that follows this formula.
|
||||
const EXPONENT_BIAS: u32 = (1 << (EXPONENT_BITS - 1)) - 1;
|
||||
|
||||
// 4 bits for each hexadecimal offset
|
||||
let mut exponent_offset: i32 = literal.decimal_offset * 4;
|
||||
|
||||
// If there were all
|
||||
if literal.digits.is_empty() {
|
||||
return ConversionResult::Precise(0.0);
|
||||
}
|
||||
|
||||
// This code is a work of art.
|
||||
let mut was_truncated = false;
|
||||
let mut mantissa_result: $bits_type = 0;
|
||||
for (index, digit) in literal.digits.iter().enumerate() {
|
||||
if index as u32 * 4 > MANTISSA_BITS {
|
||||
was_truncated = true;
|
||||
break;
|
||||
}
|
||||
let mut digit_value = *digit as $bits_type;
|
||||
digit_value <<= TOTAL_BITS - (index as u32 + 1) * 4;
|
||||
mantissa_result |= digit_value;
|
||||
}
|
||||
let leading_zeros = mantissa_result.leading_zeros();
|
||||
exponent_offset -= leading_zeros as i32 + 1;
|
||||
mantissa_result <<= leading_zeros + 1;
|
||||
mantissa_result >>= TOTAL_BITS - MANTISSA_BITS;
|
||||
|
||||
let final_exponent = exponent_offset + literal.exponent;
|
||||
|
||||
// Check for underflows
|
||||
if final_exponent < $min_exp - 1 {
|
||||
if literal.is_positive {
|
||||
return ConversionResult::Imprecise(0.0);
|
||||
} else {
|
||||
return ConversionResult::Imprecise(-0.0);
|
||||
};
|
||||
}
|
||||
|
||||
// Check for overflows
|
||||
if final_exponent > $max_exp - 1 {
|
||||
if literal.is_positive {
|
||||
return ConversionResult::Imprecise($infinity);
|
||||
} else {
|
||||
return ConversionResult::Imprecise(-$infinity);
|
||||
};
|
||||
}
|
||||
|
||||
let exponent_result: $bits_type =
|
||||
((final_exponent + EXPONENT_BIAS as i32) as $bits_type) << MANTISSA_BITS;
|
||||
|
||||
let sign_result: $bits_type =
|
||||
(!literal.is_positive as $bits_type) << (MANTISSA_BITS + EXPONENT_BITS);
|
||||
|
||||
let float_value = $from_bits(sign_result | exponent_result | mantissa_result);
|
||||
|
||||
if was_truncated {
|
||||
ConversionResult::Imprecise(float_value)
|
||||
} else {
|
||||
ConversionResult::Precise(float_value)
|
||||
}
|
||||
|
||||
// // This might be a bit faster.
|
||||
// let mut final_result = !literal.is_positive as $bits_type;
|
||||
// final_result <<= EXPONENT_BITS;
|
||||
// final_result |= (final_exponent + EXPONENT_BIAS as i32) as
|
||||
// $bits_type; final_result <<= MANTISSA_BITS;
|
||||
// final_result |= mantissa_result;
|
||||
// ConversionResult::Precise($from_bits(final_result))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Trait to describe conversion to floating point formats.
|
||||
pub trait FPFormat: ops::Neg<Output = Self> + Sized + Copy {
|
||||
/// Convert a literal to this format. This is a hack so that we can use
|
||||
/// a macro to implement conversions.
|
||||
fn from_literal(literal: FloatLiteral) -> ConversionResult<Self>;
|
||||
}
|
||||
|
||||
impl_fpformat!(
|
||||
f32,
|
||||
u32,
|
||||
8,
|
||||
23,
|
||||
f32::from_bits,
|
||||
core::f32::INFINITY,
|
||||
core::f32::MAX_EXP,
|
||||
core::f32::MIN_EXP
|
||||
);
|
||||
impl_fpformat!(
|
||||
f64,
|
||||
u64,
|
||||
11,
|
||||
52,
|
||||
f64::from_bits,
|
||||
core::f64::INFINITY,
|
||||
core::f64::MAX_EXP,
|
||||
core::f64::MIN_EXP
|
||||
);
|
|
@ -1,5 +1,6 @@
|
|||
mod binding_power;
|
||||
mod classifiers;
|
||||
mod hex_float;
|
||||
mod state;
|
||||
mod token;
|
||||
|
||||
|
@ -12,7 +13,6 @@ use binding_power::{
|
|||
INDEX_BINDING_POWER,
|
||||
};
|
||||
use classifiers::{token_is_expr_start, token_is_literal, token_to_binary_op, token_to_unary_op};
|
||||
use either::Either;
|
||||
use state::State;
|
||||
|
||||
use crate::{
|
||||
|
@ -103,8 +103,8 @@ fn parse_stmt(state: &mut State) -> Stmt {
|
|||
Stmt::If(item)
|
||||
},
|
||||
T![for] => match parse_for(state) {
|
||||
Either::Left(numeric) => Stmt::ForNumeric(numeric),
|
||||
Either::Right(generic) => Stmt::ForGeneric(generic),
|
||||
For::Numeric(numeric) => Stmt::ForNumeric(numeric),
|
||||
For::Generic(generic) => Stmt::ForGeneric(generic),
|
||||
},
|
||||
T![return] => {
|
||||
let item = parse_return(state);
|
||||
|
@ -459,16 +459,21 @@ fn parse_if(state: &mut State) -> If {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_for(state: &mut State) -> Either<ForNumeric, ForGeneric> {
|
||||
enum For {
|
||||
Numeric(ForNumeric),
|
||||
Generic(ForGeneric),
|
||||
}
|
||||
|
||||
fn parse_for(state: &mut State) -> For {
|
||||
state.eat(T![for]);
|
||||
let first_var = parse_ident(state);
|
||||
|
||||
if state.peek() == T![=] {
|
||||
let item = parse_for_numeric(state, first_var);
|
||||
Either::Left(item)
|
||||
For::Numeric(item)
|
||||
} else {
|
||||
let item = parse_for_generic(state, first_var);
|
||||
Either::Right(item)
|
||||
For::Generic(item)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -765,27 +770,27 @@ fn parse_long_string(state: &mut State) -> Vec<u8> {
|
|||
.into_bytes()
|
||||
}
|
||||
|
||||
fn parse_int(state: &mut State) -> i64 {
|
||||
fn parse_int(state: &mut State) -> i32 {
|
||||
state.eat(T![int]);
|
||||
state.slice().parse().unwrap()
|
||||
}
|
||||
|
||||
fn parse_hex_int(state: &mut State) -> i64 {
|
||||
fn parse_hex_int(state: &mut State) -> i32 {
|
||||
state.eat(T![hex_int]);
|
||||
let raw = &state.slice()[2..];
|
||||
i64::from_str_radix(raw, 16).unwrap()
|
||||
i32::from_str_radix(raw, 16).unwrap()
|
||||
}
|
||||
|
||||
fn parse_float(state: &mut State) -> f64 {
|
||||
fn parse_float(state: &mut State) -> f32 {
|
||||
state.eat(T![float]);
|
||||
state.slice().parse().unwrap()
|
||||
}
|
||||
|
||||
fn parse_hex_float(state: &mut State) -> f64 {
|
||||
fn parse_hex_float(state: &mut State) -> f32 {
|
||||
state.eat(T![hex_float]);
|
||||
|
||||
let raw = state.slice();
|
||||
hexponent::FloatLiteral::from_str(raw)
|
||||
hex_float::FloatLiteral::from_str(raw)
|
||||
.unwrap()
|
||||
.convert()
|
||||
.inner()
|
||||
|
|
|
@ -220,6 +220,6 @@ pub enum Literal {
|
|||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum NumLiteral {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Int(i32),
|
||||
Float(f32),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue