├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples └── repl.rs ├── rustfmt.toml └── src ├── digit_slice.rs ├── lib.rs └── nock.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes by Release 2 | 3 | ## 0.4.0 (2016-04-16) 4 | 5 | - Nock API is now based on the VM trait with user hooks in calls and 6 | hints for implementing a jet system. 7 | 8 | - Nouns cache mug hashes that match then ones used in current Urbit. 9 | 10 | - More conversion implementations between Rust types and nouns. 11 | 12 | - Default noun printer abbreviates large nouns. 13 | 14 | ## 0.3.0 (2016-03-21) 15 | 16 | - New Noun structure that supports hidden internal features. 17 | 18 | - Introduce conversion traits between Rust types and Nouns. 19 | 20 | ## 0.2.1 (2016-01-17) 21 | 22 | - Relicense to dual MIT or Apache-2.0 as per Rust project licensing 23 | guidelines. 24 | 25 | - Memoizing fold method allows quick computation over nouns which 26 | would be enormous if iterated naively. 27 | 28 | ## 0.2.0 (2016-01-10) 29 | 30 | - New Noun structure that handles arbitrary-sized atoms and can 31 | reuse memory for repeating subnouns. 32 | 33 | - Nocking API now takes separate subject and formula nouns. 34 | 35 | ## 0.1.0 (2015-10-25) 36 | 37 | - Initial limited toy version that handles basic Nock rules. 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nock" 3 | version = "0.4.0" 4 | authors = ["Risto Saarelma "] 5 | keywords = ["vm"] 6 | description = "A stateless virtual machine" 7 | repository = "https://github.com/rsaarelm/nock-rs" 8 | license = "MIT OR Apache-2.0" 9 | 10 | [dependencies] 11 | num = "0.1" 12 | fnv = "1.0" 13 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Risto Saarelma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Implementation of the Nock stateless virtual machine 2 | 3 | ## License 4 | 5 | Licensed under either of 6 | 7 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 8 | 9 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 10 | 11 | at your option. 12 | 13 | ### Contribution 14 | 15 | Unless you explicitly state otherwise, any contribution intentionally submitted 16 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 17 | additional terms or conditions. 18 | -------------------------------------------------------------------------------- /examples/repl.rs: -------------------------------------------------------------------------------- 1 | extern crate nock; 2 | 3 | use std::default::Default; 4 | use std::io::{self, Write}; 5 | use nock::{Nock, Noun}; 6 | 7 | struct VM; 8 | impl Nock for VM {} 9 | 10 | fn main() { 11 | let mut subject: Noun = Default::default(); 12 | // Default to identity formula. 13 | let mut formula = Noun::cell(Noun::from(0u32), Noun::from(1u32)); 14 | let mut vm = VM; 15 | 16 | println!("Welcome to nock-rs"); 17 | println!("Type a formula to nock on the subject"); 18 | loop { 19 | let mut input = String::new(); 20 | 21 | println!("{}", &subject); 22 | print!("> "); 23 | io::stdout().flush().expect("IO error"); 24 | match io::stdin().read_line(&mut input) { 25 | Ok(_) => { 26 | if input == "\n" { 27 | // Reuse previous valid formula on empty input. 28 | match vm.nock_on(subject.clone(), formula.clone()) { 29 | Ok(eval) => { 30 | subject = eval; 31 | } 32 | Err(_) => println!("Nock eval error"), 33 | } 34 | } else { 35 | match input.parse::() { 36 | Ok(f) => { 37 | match vm.nock_on(subject.clone(), f.clone()) { 38 | Ok(eval) => { 39 | subject = eval; 40 | formula = f; 41 | } 42 | Err(_) => println!("Nock eval error"), 43 | } 44 | } 45 | _ => println!("Not a Nock formula"), 46 | } 47 | } 48 | } 49 | 50 | Err(_) => break, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | -------------------------------------------------------------------------------- /src/digit_slice.rs: -------------------------------------------------------------------------------- 1 | use std::slice; 2 | use std::mem; 3 | use num::bigint::{BigUint, BigDigit}; 4 | 5 | /// Types that can be interpreted as arbitrary-length little-endian base-256 6 | /// integers. 7 | pub trait DigitSlice { 8 | /// Return a little-endian byte slice corresponding to an in-memory unsigned 9 | /// integer value. 10 | /// 11 | /// Will fail horribly if your hardware is not little-endian. 12 | fn as_digits(&self) -> &[u8]; 13 | } 14 | 15 | /// Types that can be constructed from base-256 integers. 16 | pub trait FromDigits: Sized { 17 | type Err; 18 | 19 | /// Construct an unsigned integer type from little-endian byte digits. 20 | fn from_digits(&[u8]) -> Result; 21 | } 22 | 23 | impl DigitSlice for BigUint { 24 | #[inline] 25 | fn as_digits(&self) -> &[u8] { 26 | let n_bytes = (self.bits() + 7) / 8; 27 | 28 | unsafe { 29 | let ptr = mem::transmute::<&BigUint, &Vec>(self) 30 | .as_ptr() as *const u8; 31 | slice::from_raw_parts(ptr, n_bytes) 32 | } 33 | } 34 | } 35 | 36 | impl FromDigits for BigUint { 37 | type Err = (); 38 | 39 | fn from_digits(digits: &[u8]) -> Result { 40 | let mut v = digits.to_vec(); 41 | while (v.len() % 4) != 0 { 42 | v.push(0); 43 | } 44 | 45 | unsafe { 46 | let ptr = v.as_mut_ptr() as *mut BigDigit; 47 | let len = v.len() / 4; 48 | // I hope possibly truncating capacity down by 1 or 3 bytes won't 49 | // be a problem. 50 | let cap = v.capacity() / 4; 51 | // We'll reuse the bits, don't collect the old one. 52 | mem::forget(v); 53 | 54 | let biguint_vec: Vec = Vec::from_raw_parts(ptr, len, cap); 55 | Ok(mem::transmute(biguint_vec)) 56 | } 57 | } 58 | } 59 | 60 | 61 | macro_rules! primitive_impl { 62 | ($t:ty) => { 63 | impl DigitSlice for $t { 64 | #[inline] 65 | fn as_digits(&self) -> &[u8] { 66 | let n_bytes = (mem::size_of::<$t>() * 8 - self.leading_zeros() as usize + 7) / 8; 67 | 68 | unsafe { 69 | let ptr: *const u8 = mem::transmute(self); 70 | slice::from_raw_parts(ptr, n_bytes) 71 | } 72 | } 73 | } 74 | 75 | impl FromDigits for $t { 76 | type Err = (); 77 | 78 | #[inline] 79 | fn from_digits(digits: &[u8]) -> Result<$t, Self::Err> { 80 | if digits.len() > mem::size_of::<$t>() { return Err(()); } 81 | let mut ret = 0 as $t; 82 | for (i, &d) in digits.iter().enumerate() { 83 | ret |= d as $t << (i * 8); 84 | } 85 | Ok(ret) 86 | } 87 | } 88 | } 89 | } 90 | 91 | primitive_impl!(u8); 92 | primitive_impl!(u16); 93 | primitive_impl!(u32); 94 | primitive_impl!(u64); 95 | primitive_impl!(usize); 96 | 97 | /// Return the bit position of the most significant bit. 98 | /// 99 | /// Interprets the data as a little-endian integer. 100 | /// Assumes that the data has no trailing zeroes. 101 | #[inline] 102 | pub fn msb(data: &[u8]) -> usize { 103 | if data.len() == 0 { 104 | return 0; 105 | } 106 | 107 | let mut last = data[data.len() - 1]; 108 | assert!(last != 0); 109 | let mut ret = (data.len() - 1) * 8; 110 | while last != 0 { 111 | ret += 1; 112 | last >>= 1; 113 | } 114 | 115 | ret 116 | } 117 | 118 | #[cfg(test)] 119 | mod tests { 120 | use num::{BigUint, FromPrimitive}; 121 | use super::{DigitSlice, FromDigits}; 122 | 123 | #[test] 124 | fn test_slice() { 125 | assert_eq!(0u8.as_digits(), &[]); 126 | assert_eq!(1u8.as_digits(), &[1]); 127 | 128 | assert_eq!(BigUint::from_u32(12345).unwrap().as_digits(), 129 | 12345u32.as_digits()); 130 | 131 | assert_eq!(255u32.as_digits(), &[255]); 132 | assert_eq!(256u32.as_digits(), &[0, 1]); 133 | 134 | assert_eq!(BigUint::parse_bytes(b"112233445566778899", 16) 135 | .unwrap() 136 | .as_digits(), 137 | &[0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11]); 138 | 139 | assert_eq!(u8::from_digits(&[0x44]), Ok(0x44)); 140 | assert_eq!(u8::from_digits(&[0x44, 0x33]), Err(())); 141 | assert_eq!(u32::from_digits(&[0x44, 0x33]), Ok(0x3344)); 142 | 143 | assert_eq!(BigUint::from_digits(&[0x99, 0x88, 0x77, 0x66, 0x55, 144 | 0x44, 0x33, 0x22, 0x11]) 145 | .unwrap(), 146 | BigUint::parse_bytes(b"112233445566778899", 16).unwrap()); 147 | } 148 | 149 | #[test] 150 | fn test_msb() { 151 | use super::msb; 152 | assert_eq!(0, msb(&vec![])); 153 | assert_eq!(1, msb(&vec![1])); 154 | assert_eq!(4, msb(&vec![15])); 155 | assert_eq!(5, msb(&vec![16])); 156 | assert_eq!(13, msb(&vec![123, 16])); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the Nock virtual machine. 2 | //! 3 | //! See http://urbit.org/docs/theory/whitepaper for more information on Nock. 4 | //! 5 | //! The Nock spec: 6 | //! 7 | //! ```notrust 8 | //! A noun is an atom or a cell. 9 | //! An atom is a natural number. 10 | //! A cell is an ordered pair of nouns. 11 | //! 12 | //! nock(a) *a 13 | //! [a b c] [a [b c]] 14 | //! 15 | //! ?[a b] 0 16 | //! ?a 1 17 | //! +[a b] +[a b] 18 | //! +a 1 + a 19 | //! =[a a] 0 20 | //! =[a b] 1 21 | //! =a =a 22 | //! 23 | //! /[1 a] a 24 | //! /[2 a b] a 25 | //! /[3 a b] b 26 | //! /[(a + a) b] /[2 /[a b]] 27 | //! /[(a + a + 1) b] /[3 /[a b]] 28 | //! /a /a 29 | //! 30 | //! *[a [b c] d] [*[a b c] *[a d]] 31 | //! 32 | //! *[a 0 b] /[b a] 33 | //! *[a 1 b] b 34 | //! *[a 2 b c] *[*[a b] *[a c]] 35 | //! *[a 3 b] ?*[a b] 36 | //! *[a 4 b] +*[a b] 37 | //! *[a 5 b] =*[a b] 38 | //! 39 | //! *[a 6 b c d] *[a 2 [0 1] 2 [1 c d] [1 0] 2 [1 2 3] [1 0] 4 4 b] 40 | //! *[a 7 b c] *[a 2 b 1 c] 41 | //! *[a 8 b c] *[a 7 [[7 [0 1] b] 0 1] c] 42 | //! *[a 9 b c] *[a 7 c 2 [0 1] 0 b] 43 | //! *[a 10 [b c] d] *[a 8 c 7 [0 3] d] 44 | //! *[a 10 b c] *[a c] 45 | //! 46 | //! *a *a 47 | //! ``` 48 | 49 | #![crate_name="nock"] 50 | 51 | extern crate num; 52 | extern crate fnv; 53 | 54 | use std::collections::HashMap; 55 | use std::rc::Rc; 56 | use std::error::Error; 57 | use std::str; 58 | use std::fmt; 59 | use std::iter; 60 | use std::hash; 61 | use std::default; 62 | use num::BigUint; 63 | pub use digit_slice::{DigitSlice, FromDigits, msb}; 64 | 65 | pub use nock::{Nock, get_axis}; 66 | 67 | mod digit_slice; 68 | mod nock; 69 | 70 | /// A wrapper for referencing Noun-like patterns. 71 | #[derive(Copy, Clone)] 72 | pub enum Shape { 73 | Atom(A), 74 | Cell(N, N), 75 | } 76 | 77 | /// A Nock noun, the basic unit of representation. 78 | /// 79 | /// A noun is an atom or a cell. An atom is any natural number. A cell is any 80 | /// ordered pair of nouns. 81 | /// 82 | /// Atoms are represented by a little-endian byte array of 8-bit digits. 83 | #[derive(Clone, PartialEq, Eq)] 84 | pub struct Noun { 85 | hash: u32, 86 | value: Inner, 87 | } 88 | 89 | #[derive(Clone, PartialEq, Eq)] 90 | enum Inner { 91 | Atom(Rc>), 92 | Cell(Rc, Rc), 93 | } 94 | 95 | pub type NounShape<'a> = Shape<&'a [u8], &'a Noun>; 96 | 97 | impl Noun { 98 | /// Get a shape wrapper for the noun to examine its structure. 99 | pub fn get(&self) -> NounShape { 100 | match self.value { 101 | Inner::Atom(ref v) => Shape::Atom(v), 102 | Inner::Cell(ref a, ref b) => Shape::Cell(&*a, &*b), 103 | } 104 | } 105 | 106 | /// Pattern-match a noun with shape [p q r]. 107 | /// 108 | /// The digit sequence shows the branch length of each leaf node in the 109 | /// expression being matched. 122 has the leftmost leaf 1 step away from 110 | /// the root and the two leaves on the right both 2 steps away from the 111 | /// root. 112 | pub fn get_122(&self) -> Option<(&Noun, &Noun, &Noun)> { 113 | if let Shape::Cell(ref a, ref b) = self.get() { 114 | if let Shape::Cell(ref b, ref c) = b.get() { 115 | return Some((a, b, c)); 116 | } 117 | } 118 | None 119 | } 120 | 121 | /// Address of the noun's data in memory, usable as an unique identifier. 122 | /// 123 | /// Nouns with the same address are always the same, but nouns with 124 | /// different addresses are not guaranteed to have different values. 125 | #[inline] 126 | pub fn addr(&self) -> usize { 127 | &*self as *const _ as usize 128 | } 129 | 130 | pub fn mug(&self) -> u32 { 131 | self.hash 132 | } 133 | 134 | /// Build a new atom noun from a little-endian 8-bit digit sequence. 135 | pub fn atom(digits: &[u8]) -> Noun { 136 | Noun { 137 | hash: mug_atom(digits, 2_166_136_261), 138 | value: Inner::Atom(Rc::new(digits.to_vec())), 139 | } 140 | } 141 | 142 | /// Build a new cell noun from two existing nouns. 143 | pub fn cell(a: Noun, b: Noun) -> Noun { 144 | Noun { 145 | hash: mug_pair(a.mug(), b.mug()), 146 | value: Inner::Cell(Rc::new(a), Rc::new(b)), 147 | } 148 | } 149 | 150 | /// Build a noun from a convertible value. 151 | pub fn from(item: T) -> Noun { 152 | item.to_noun() 153 | } 154 | 155 | /// Match noun if it's an atom that's a small integer. 156 | /// 157 | /// Will not match atoms that are larger than 2^32, but is not guaranteed 158 | /// to match atoms that are smaller than 2^32 but not by much. 159 | pub fn as_u32(&self) -> Option { 160 | if let Shape::Atom(ref digits) = self.get() { 161 | u32::from_digits(digits).ok() 162 | } else { 163 | None 164 | } 165 | } 166 | 167 | /// Run a memoizing fold over the noun. 168 | /// 169 | /// Each noun with an unique memory address will only be processed once, so 170 | /// the fold method allows efficient computation over nouns that may be 171 | /// extremely large logically, but involve a great deal of reuse of the same 172 | /// subnoun objects in practice. 173 | pub fn fold<'a, F, T>(&'a self, mut f: F) -> T 174 | where F: FnMut(Shape<&'a [u8], T>) -> T, 175 | T: Clone 176 | { 177 | fn h<'a, F, T, S: hash::BuildHasher>(noun: &'a Noun, 178 | memo: &mut HashMap, 179 | f: &mut F) 180 | -> T 181 | where F: FnMut(Shape<&'a [u8], T>) -> T, 182 | T: Clone 183 | { 184 | let key = noun.addr(); 185 | 186 | if memo.contains_key(&key) { 187 | memo.get(&key).unwrap().clone() 188 | } else { 189 | let ret = match noun.get() { 190 | Shape::Atom(x) => f(Shape::Atom(x)), 191 | Shape::Cell(ref a, ref b) => { 192 | let a = h(*a, memo, f); 193 | let b = h(*b, memo, f); 194 | f(Shape::Cell(a, b)) 195 | } 196 | }; 197 | memo.insert(key, ret.clone()); 198 | ret 199 | } 200 | } 201 | 202 | let fnv = hash::BuildHasherDefault::::default(); 203 | h(self, &mut HashMap::with_hasher(fnv), &mut f) 204 | } 205 | 206 | /// Return whether a noun is a list with more than n elements. 207 | fn is_wider_than(&self, n: usize) -> bool { 208 | if n == 0 { 209 | true 210 | } else { 211 | match self.get() { 212 | Shape::Cell(_, ref a) => a.is_wider_than(n - 1), 213 | _ => false, 214 | } 215 | } 216 | } 217 | 218 | /// 3-letter abbrevation identifier for noun. 219 | fn glyph(&self) -> String { 220 | let alpha = b"abcdefghijklmnopqrstuvwxyz"; 221 | let mut ret = String::new(); 222 | let mut mug = self.mug() as usize; 223 | for _ in 0..3 { 224 | ret.push(alpha[mug % alpha.len()] as char); 225 | mug /= alpha.len(); 226 | } 227 | ret 228 | } 229 | 230 | /// Print the complete noun, even if it's very large. 231 | pub fn print_full(&self, f: &mut fmt::Formatter) -> fmt::Result { 232 | self.print(f, false) 233 | } 234 | 235 | /// Print a truncated noun. 236 | pub fn print_abbrev(&self, f: &mut fmt::Formatter) -> fmt::Result { 237 | self.print(f, true) 238 | } 239 | 240 | fn print(&self, f: &mut fmt::Formatter, abbrev: bool) -> fmt::Result { 241 | const MAX_ATOM_BITS: usize = 128; 242 | const MAX_CELL_WIDTH: usize = 12; 243 | 244 | match self.value { 245 | Inner::Atom(ref n) => { 246 | if abbrev && msb(n) > MAX_ATOM_BITS { 247 | // Print huge atoms as abbreviated glyphs 248 | return write!(f, "@{}", self.glyph()); 249 | } 250 | // Print dot-separated integer. 251 | let s = format!("{}", BigUint::from_digits(n).unwrap()); 252 | let phase = s.len() % 3; 253 | for (i, c) in s.chars().enumerate() { 254 | if i > 0 && i % 3 == phase { 255 | try!(write!(f, ".")); 256 | } 257 | try!(write!(f, "{}", c)); 258 | } 259 | Ok(()) 260 | } 261 | 262 | Inner::Cell(ref a, ref b) => { 263 | if abbrev && self.is_wider_than(MAX_CELL_WIDTH) { 264 | return write!(f, "[{}]", self.glyph()); 265 | } 266 | 267 | try!(write!(f, "[")); 268 | try!(a.print(f, abbrev)); 269 | try!(write!(f, " ")); 270 | // List pretty-printer. 271 | let mut cur = b; 272 | loop { 273 | match cur.value { 274 | Inner::Cell(ref a, ref b) => { 275 | try!(a.print(f, abbrev)); 276 | try!(write!(f, " ")); 277 | cur = b; 278 | } 279 | Inner::Atom(_) => { 280 | try!(cur.print(f, abbrev)); 281 | return write!(f, "]"); 282 | } 283 | } 284 | } 285 | } 286 | } 287 | } 288 | } 289 | 290 | 291 | impl default::Default for Noun { 292 | fn default() -> Self { 293 | Noun::from(0u32) 294 | } 295 | } 296 | 297 | impl hash::Hash for Noun { 298 | fn hash(&self, state: &mut H) { 299 | self.hash.hash(state); 300 | } 301 | } 302 | 303 | impl iter::FromIterator for Noun { 304 | fn from_iter(iterator: T) -> Self 305 | where T: IntoIterator 306 | { 307 | let mut v: Vec = iterator.into_iter().collect(); 308 | v.reverse(); 309 | 310 | v.into_iter() 311 | .fold(None, move |acc, i| { 312 | acc.map_or_else(|| Some(i.clone()), 313 | |a| Some(Noun::cell(i.clone(), a))) 314 | }) 315 | .expect("Can't make noun from empty list") 316 | } 317 | } 318 | 319 | fn mug_atom(a: &[u8], init: u32) -> u32 { 320 | let mut c = init; 321 | for i in a.iter() { 322 | c = _fnv(*i as u32 ^ c); 323 | } 324 | 325 | let ret = (c >> 31) ^ (c & 0x7fffffff); 326 | if ret != 0 { 327 | ret 328 | } else { 329 | mug_atom(a, init.wrapping_add(1)) 330 | } 331 | } 332 | 333 | fn mug_pair(p: u32, q: u32) -> u32 { 334 | let c = _fnv(p ^ _fnv(q)); 335 | let ret = (c >> 31) ^ (c & 0x7fffffff); 336 | if ret != 0 { 337 | ret 338 | } else { 339 | mug_pair(p, q.wrapping_add(1)) 340 | } 341 | } 342 | 343 | fn _fnv(x: u32) -> u32 { 344 | x.wrapping_mul(16_777_619) 345 | } 346 | 347 | 348 | /// Trait for types that can convert themselves to a noun. 349 | pub trait ToNoun { 350 | fn to_noun(&self) -> Noun; 351 | } 352 | 353 | impl ToNoun for T 354 | where T: DigitSlice 355 | { 356 | fn to_noun(&self) -> Noun { 357 | Noun::atom(self.as_digits()) 358 | } 359 | } 360 | 361 | /// A trait for types that can be instantiated from a Nock noun. 362 | pub trait FromNoun: Sized { 363 | /// Try to convert a noun to an instance of the type. 364 | fn from_noun(n: &Noun) -> Result; 365 | } 366 | 367 | impl FromNoun for Noun { 368 | fn from_noun(n: &Noun) -> Result { 369 | Ok((*n).clone()) 370 | } 371 | } 372 | 373 | impl ToNoun for Noun { 374 | fn to_noun(&self) -> Noun { 375 | (*self).clone() 376 | } 377 | } 378 | 379 | impl FromNoun for Rc> { 380 | fn from_noun(n: &Noun) -> Result { 381 | match n.value { 382 | Inner::Atom(ref v) => Ok(v.clone()), 383 | _ => Err(NockError("FromNoun Rc> not an atom".to_owned())), 384 | } 385 | } 386 | } 387 | 388 | impl ToNoun for Vec { 389 | fn to_noun(&self) -> Noun { 390 | Noun::atom(&self[..]) 391 | } 392 | } 393 | 394 | impl FromNoun for T 395 | where T: FromDigits 396 | { 397 | fn from_noun(n: &Noun) -> Result { 398 | match n.get() { 399 | Shape::Atom(x) => { 400 | T::from_digits(x) 401 | .map_err(|_| NockError("FromNoun FromDigits".to_owned())) 402 | } 403 | _ => Err(NockError("FromNoun FromDigits not an atom".to_owned())), 404 | } 405 | } 406 | } 407 | 408 | impl FromNoun for (T,) 409 | where T: FromNoun 410 | { 411 | fn from_noun(n: &Noun) -> Result { 412 | Ok((try!(T::from_noun(n)),)) 413 | } 414 | } 415 | 416 | impl ToNoun for (T,) 417 | where T: ToNoun 418 | { 419 | fn to_noun(&self) -> Noun { 420 | self.0.to_noun() 421 | } 422 | } 423 | 424 | impl FromNoun for (T, U) 425 | where T: FromNoun, 426 | U: FromNoun 427 | { 428 | fn from_noun(n: &Noun) -> Result { 429 | match n.get() { 430 | Shape::Cell(a, b) => { 431 | let t = try!(T::from_noun(a)); 432 | let u = try!(U::from_noun(b)); 433 | Ok((t, u)) 434 | } 435 | _ => Err(NockError("FromNoun (T, U) not a cell".to_owned())), 436 | } 437 | } 438 | } 439 | 440 | impl ToNoun for (T, U) 441 | where T: ToNoun, 442 | U: ToNoun 443 | { 444 | fn to_noun(&self) -> Noun { 445 | Noun::cell(self.0.to_noun(), self.1.to_noun()) 446 | } 447 | } 448 | 449 | impl FromNoun for (T1, T2, T3) 450 | where T1: FromNoun, 451 | T2: FromNoun, 452 | T3: FromNoun 453 | { 454 | fn from_noun(n: &Noun) -> Result { 455 | match n.get_122() { 456 | Some((t1, t2, t3)) => { 457 | let t1 = try!(T1::from_noun(t1)); 458 | let t2 = try!(T2::from_noun(t2)); 459 | let t3 = try!(T3::from_noun(t3)); 460 | Ok((t1, t2, t3)) 461 | } 462 | _ => Err(NockError("FromNoun (T, U, V) not a tuple".to_owned())), 463 | } 464 | } 465 | } 466 | 467 | impl ToNoun for (T1, T2, T3) 468 | where T1: ToNoun, 469 | T2: ToNoun, 470 | T3: ToNoun 471 | { 472 | fn to_noun(&self) -> Noun { 473 | Noun::cell(self.0.to_noun(), 474 | Noun::cell(self.1.to_noun(), self.2.to_noun())) 475 | } 476 | } 477 | 478 | impl FromNoun for String { 479 | fn from_noun(n: &Noun) -> Result { 480 | match n.get() { 481 | Shape::Atom(bytes) => { 482 | String::from_utf8(bytes.to_vec()) 483 | .map_err(|_| NockError("FromNoun String".to_owned())) 484 | } 485 | _ => Err(NockError("FromNoun String not an atom".to_owned())), 486 | } 487 | } 488 | } 489 | 490 | impl ToNoun for str { 491 | fn to_noun(&self) -> Noun { 492 | Noun::atom(self.as_bytes()) 493 | } 494 | } 495 | 496 | impl FromNoun for bool { 497 | fn from_noun(n: &Noun) -> Result { 498 | Ok(n.as_u32() == Some(0)) 499 | } 500 | } 501 | 502 | impl ToNoun for bool { 503 | fn to_noun(&self) -> Noun { 504 | Noun::from(if *self { 505 | 0u32 506 | } else { 507 | 1u32 508 | }) 509 | } 510 | } 511 | 512 | impl FromNoun for Vec { 513 | // Use the Urbit convention of 0-terminated list to match Rust vectors. 514 | fn from_noun(mut n: &Noun) -> Result { 515 | let mut ret = Vec::new(); 516 | 517 | loop { 518 | // List terminator. 519 | if n == &Noun::from(0u32) { 520 | return Ok(ret); 521 | } 522 | 523 | if let Shape::Cell(ref head, ref tail) = n.get() { 524 | ret.push(try!(T::from_noun(head))); 525 | n = tail; 526 | } else { 527 | return Err(NockError("FromNoun Vec".to_owned())); 528 | } 529 | } 530 | } 531 | } 532 | 533 | // TODO: HashMap conversion. 534 | 535 | // TODO: FromNoun/ToNoun for signed numbers using the Urbit representation 536 | // convention. 537 | 538 | 539 | #[derive(Clone, PartialEq, Eq, Debug)] 540 | pub struct NockError(pub String); 541 | 542 | impl fmt::Display for NockError { 543 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 544 | write!(f, "{}", self.0) 545 | } 546 | } 547 | 548 | impl Error for NockError { 549 | fn description(&self) -> &str { 550 | &self.0[..] 551 | } 552 | 553 | fn cause(&self) -> Option<&Error> { 554 | None 555 | } 556 | } 557 | 558 | pub type NockResult = Result; 559 | 560 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 561 | pub struct ParseError; 562 | 563 | impl fmt::Display for ParseError { 564 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 565 | write!(f, "{}", self.description()) 566 | } 567 | } 568 | 569 | impl Error for ParseError { 570 | fn description(&self) -> &str { 571 | "Nock parsing failed" 572 | } 573 | 574 | fn cause(&self) -> Option<&Error> { 575 | None 576 | } 577 | } 578 | 579 | impl str::FromStr for Noun { 580 | type Err = ParseError; 581 | 582 | fn from_str(s: &str) -> Result { 583 | return parse(&mut s.chars().peekable()); 584 | 585 | fn parse>(input: &mut iter::Peekable) 586 | -> Result { 587 | eat_space(input); 588 | match input.peek().cloned() { 589 | Some(c) if c.is_digit(10) => parse_atom(input), 590 | Some(c) if c == '[' => parse_cell(input), 591 | _ => Err(ParseError), 592 | } 593 | } 594 | 595 | /// Parse an atom, a positive integer. 596 | fn parse_atom>(input: &mut iter::Peekable) 597 | -> Result { 598 | let mut buf = Vec::new(); 599 | 600 | while let Some(&c) = input.peek() { 601 | if c.is_digit(10) { 602 | input.next(); 603 | buf.push(c); 604 | } else if c == '.' { 605 | // Dot is used as a sequence separator (*not* as 606 | // decimal point). It can show up anywhere in the 607 | // digit sequence and will be ignored. 608 | input.next(); 609 | } else if c == '[' || c == ']' || c.is_whitespace() { 610 | // Whitespace or cell brackets can terminate the 611 | // digit sequence. 612 | break; 613 | } else { 614 | // Anything else in the middle of the digit sequence 615 | // is an error. 616 | return Err(ParseError); 617 | } 618 | } 619 | 620 | if buf.is_empty() { 621 | return Err(ParseError); 622 | } 623 | 624 | let num: BigUint = buf.into_iter() 625 | .collect::() 626 | .parse() 627 | .expect("Failed to parse atom"); 628 | 629 | Ok(Noun::from(num)) 630 | } 631 | 632 | /// Parse a cell, a bracketed pair of nouns. 633 | /// 634 | /// For additional complication, cells can have the form [a b c] which 635 | /// parses to [a [b c]]. 636 | fn parse_cell>(input: &mut iter::Peekable) 637 | -> Result { 638 | let mut elts = Vec::new(); 639 | 640 | if input.next() != Some('[') { 641 | panic!("Bad cell start"); 642 | } 643 | 644 | // A cell must have at least two nouns in it. 645 | elts.push(try!(parse(input))); 646 | elts.push(try!(parse(input))); 647 | 648 | // It can have further trailing nouns. 649 | loop { 650 | eat_space(input); 651 | match input.peek().cloned() { 652 | Some(c) if c.is_digit(10) => { 653 | elts.push(try!(parse_atom(input))) 654 | } 655 | Some(c) if c == '[' => elts.push(try!(parse_cell(input))), 656 | Some(c) if c == ']' => { 657 | input.next(); 658 | break; 659 | } 660 | _ => return Err(ParseError), 661 | } 662 | } 663 | 664 | Ok(elts.into_iter().collect()) 665 | } 666 | 667 | fn eat_space>(input: &mut iter::Peekable) { 668 | loop { 669 | match input.peek().cloned() { 670 | Some(c) if c.is_whitespace() => { 671 | input.next(); 672 | } 673 | _ => return, 674 | } 675 | } 676 | } 677 | } 678 | } 679 | 680 | impl fmt::Display for Noun { 681 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 682 | self.print_abbrev(f) 683 | } 684 | } 685 | 686 | impl fmt::Debug for Noun { 687 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 688 | self.print_abbrev(f) 689 | } 690 | } 691 | 692 | 693 | #[cfg(test)] 694 | mod tests { 695 | use std::hash; 696 | use num::BigUint; 697 | use super::{Nock, Noun, Shape, FromNoun, ToNoun}; 698 | 699 | struct VM; 700 | impl Nock for VM {} 701 | 702 | /// Macro for noun literals. 703 | /// 704 | /// Rust n![1, 2, 3] corresponds to Nock [1 2 3] 705 | macro_rules! n { 706 | [$x:expr, $y:expr] => { super::Noun::cell($x.into(), $y.into()) }; 707 | [$x:expr, $y:expr, $($ys:expr),+] => { super::Noun::cell($x.into(), n![$y, $($ys),+]) }; 708 | } 709 | 710 | // Into-conversion is only used so that we can put untyped numeric literals in 711 | // the noun-constructing macro and have them typed as unsigned. If the noun 712 | // constructor uses ToNoun, literals are assumed to be i32, which does not map 713 | // to atoms in quite the way we want. 714 | impl Into for u64 { 715 | fn into(self) -> Noun { 716 | Noun::from(self) 717 | } 718 | } 719 | 720 | 721 | fn parses(input: &str, output: Noun) { 722 | assert_eq!(input.parse::().ok().expect("Parsing failed"), output); 723 | } 724 | 725 | fn produces(input: &str, output: &str) { 726 | let (s, f) = match input.parse::() { 727 | Err(_) => panic!("Parsing failed"), 728 | Ok(x) => { 729 | if let Shape::Cell(ref s, ref f) = x.get() { 730 | ((*s).clone(), (*f).clone()) 731 | } else { 732 | panic!("Unnockable input") 733 | } 734 | } 735 | }; 736 | assert_eq!(format!("{}", VM.nock_on(s, f).ok().expect("Eval failed")), 737 | output); 738 | } 739 | 740 | fn hash(t: &T) -> u64 { 741 | use std::hash::Hasher; 742 | let mut s = hash::SipHasher::new(); 743 | t.hash(&mut s); 744 | s.finish() 745 | } 746 | 747 | #[test] 748 | fn test_fold() { 749 | assert_eq!(hash(&n![1, 2, 3]), hash(&n![1, 2, 3])); 750 | assert!(hash(&n![n![1, 2], 3]) != hash(&n![1, 2, 3])); 751 | assert!(hash(&n![1, 2, 3]) != hash(&n![1, 2])); 752 | } 753 | 754 | #[test] 755 | fn test_parser() { 756 | use num::traits::Num; 757 | 758 | assert!("".parse::().is_err()); 759 | assert!("12ab".parse::().is_err()); 760 | assert!("[]".parse::().is_err()); 761 | assert!("[1]".parse::().is_err()); 762 | 763 | parses("0", Noun::from(0u32)); 764 | parses("1", Noun::from(1u32)); 765 | parses("1.000.000", Noun::from(1_000_000u32)); 766 | 767 | parses("4294967295", Noun::from(4294967295u32)); 768 | parses("4294967296", Noun::from(4294967296u64)); 769 | 770 | parses("999.999.999.999.999.999.999.999.999.999.999.999.999.999.999.\ 771 | 999.999.999.999.999", 772 | Noun::from(BigUint::from_str_radix("999999999999999999999999\ 773 | 999999999999999999999999\ 774 | 999999999999", 775 | 10) 776 | .unwrap())); 777 | 778 | parses("[1 2]", n![1, 2]); 779 | parses("[1 2 3]", n![1, 2, 3]); 780 | parses("[1 [2 3]]", n![1, 2, 3]); 781 | parses("[[1 2] 3]", n![n![1, 2], 3]); 782 | } 783 | 784 | #[test] 785 | fn test_autocons() { 786 | produces("[42 [4 0 1] [3 0 1]]", "[43 1]"); 787 | } 788 | 789 | #[test] 790 | fn test_axis() { 791 | // Operator 0: Axis 792 | produces("[[19 42] [0 3] 0 2]", "[42 19]"); 793 | produces("[[19 42] 0 3]", "42"); 794 | produces("[[[97 2] [1 42 0]] 0 7]", "[42 0]"); 795 | 796 | // Bignum axis. 797 | produces("[[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 \ 798 | 23 24 25 26 27 28 29 30 31 32 33] 0 8589934591]", 799 | "33"); 800 | } 801 | 802 | #[test] 803 | fn test_just() { 804 | // Operator 1: Just 805 | produces("[42 1 57]", "57"); 806 | } 807 | 808 | #[test] 809 | fn test_fire() { 810 | // Operator 2: Fire 811 | produces("[[[40 43] [4 0 1]] [2 [0 4] [0 3]]]", "41"); 812 | produces("[[[40 43] [4 0 1]] [2 [0 5] [0 3]]]", "44"); 813 | produces("[77 [2 [1 42] [1 1 153 218]]]", "[153 218]"); 814 | } 815 | 816 | #[test] 817 | fn test_depth() { 818 | // Operator 3: Depth 819 | produces("[1 3 0 1]", "1"); 820 | produces("[[2 3] 3 0 1]", "0"); 821 | } 822 | 823 | #[test] 824 | fn test_bump() { 825 | // Operator 4: Bump 826 | produces("[57 4 0 1]", "58"); 827 | } 828 | 829 | #[test] 830 | fn test_bigint() { 831 | // 32-bit limit, bump up needs bignums if atom is u32 832 | produces("[4294967295 4 0 1]", "4.294.967.296"); 833 | // 64-bit limit, bump up needs bignums if atom is u64 834 | produces("[18446744073709551615 4 0 1]", "18.446.744.073.709.551.616"); 835 | // Bignum-to-bignum bump works, even if base atoms are 64-bit. 836 | produces("[18446744073709551616 4 0 1]", "18.446.744.073.709.551.617"); 837 | } 838 | 839 | #[test] 840 | fn test_same() { 841 | // Operator 5: Same 842 | produces("[[1 1] 5 0 1]", "0"); 843 | produces("[[1 2] 5 0 1]", "1"); 844 | // Various bignum combinations. 845 | produces("[[18446744073709551615 18446744073709551615] 5 0 1]", "0"); 846 | produces("[[18446744073709551615 18446744073709551616] 5 0 1]", "1"); 847 | produces("[[18446744073709551615 2] 5 0 1]", "1"); 848 | produces("[[2 18446744073709551615] 5 0 1]", "1"); 849 | } 850 | 851 | #[test] 852 | fn test_if() { 853 | // Operator 6: If 854 | produces("[[40 43] 6 [3 0 1] [4 0 2] [4 0 1]]", "41"); 855 | produces("[42 6 [1 0] [4 0 1] 1 233]", "43"); 856 | produces("[42 6 [1 1] [4 0 1] 1 233]", "233"); 857 | } 858 | 859 | #[test] 860 | fn test_misc_nock() { 861 | // Operator 7: Compose 862 | produces("[[42 44] [7 [4 0 3] [3 0 1]]]", "1"); 863 | 864 | // Operator 8: Push 865 | 866 | // Operator 9: Call 867 | 868 | // Operator 10: Hint 869 | 870 | produces("[[132 19] [10 37 [4 0 3]]]", "20"); 871 | 872 | // Fibonacci numbers, 873 | // https://groups.google.com/forum/#!topic/urbit-dev/K7QpBge30JI 874 | produces("[10 8 [1 1 1] 8 [1 0] 8 [1 6 [5 [0 15] 4 0 6] [0 28] 9 2 \ 875 | [0 2] [4 0 6] [[0 29] 7 [0 14] 8 [1 0] 8 [1 6 [5 [0 14] 0 \ 876 | 6] [0 15] 9 2 [0 2] [4 0 6] [0 14] 4 0 15] 9 2 0 1] 0 15] \ 877 | 9 2 0 1]", 878 | "55"); 879 | } 880 | 881 | #[test] 882 | fn test_stack() { 883 | // Subtraction. Tests tail call elimination, will trash stack if it 884 | // doesn't work. 885 | produces("[10.000 8 [1 0] 8 [1 6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] \ 886 | [4 0 6] 0 7] 9 2 0 1]", 887 | "9.999"); 888 | } 889 | 890 | #[test] 891 | fn test_cord() { 892 | assert_eq!(String::from_noun(&Noun::from(0u32)), Ok("".to_string())); 893 | assert!(String::from_noun(&Noun::from(190u32)).is_err()); 894 | assert_eq!(String::from_noun(&Noun::from(7303014u32)), 895 | Ok("foo".to_string())); 896 | assert_eq!(String::from_noun(&"quux".to_noun()), 897 | Ok("quux".to_string())); 898 | } 899 | 900 | #[test] 901 | fn test_mug() { 902 | assert_eq!(Noun::from(0u32).mug(), 18_652_612); 903 | assert_eq!(Noun::from(1u32).mug(), 67_918_732); 904 | assert_eq!(Noun::from(126u32).mug(), 2_064_403_808); 905 | assert_eq!(Noun::from(10_000u32).mug(), 178_152_889); 906 | assert_eq!(Noun::from(10_001u32).mug(), 714_838_017); 907 | assert_eq!("123.456.789.123.456.789".parse::().unwrap().mug(), 908 | 322_093_503); 909 | assert_eq!("123.456.789.123.456.789.123.456.789" 910 | .parse::() 911 | .unwrap() 912 | .mug(), 913 | 61_582_623); 914 | assert_eq!(n![1, 2, 3, 4, 5, 0].mug(), 1_067_931_605); 915 | // This would get mugged to zero without the nonzero check. 916 | assert_eq!(Noun::from(2_048_341_237u32).mug(), 1_229_723_070); 917 | } 918 | } 919 | -------------------------------------------------------------------------------- /src/nock.rs: -------------------------------------------------------------------------------- 1 | use num::BigUint; 2 | use num::traits::One; 3 | use digit_slice::{DigitSlice, FromDigits, msb}; 4 | use {Shape, Noun, NockError, NockResult}; 5 | 6 | /// Interface for a virtual machine for Nock code. 7 | /// 8 | /// A virtual machine can process Hint operations and accelerate Call 9 | /// operations. 10 | pub trait Nock { 11 | /// Accelerate computation of a formula, if possible. 12 | /// 13 | /// Nock `*[a 9 b c]`, will trigger `call(*[a c], *[*[a c] 0 b])`. 14 | /// If call returns a noun, that noun will be used as the result of the 15 | /// Nock evaluation. Otherwise the formula will be evaluated as standard 16 | /// Nock. 17 | /// 18 | /// The return value must be exactly the same as you would get for 19 | /// evaluating the formula on the subject with a standard Nock 20 | /// interpreter. 21 | #[allow(unused_variables)] 22 | fn call(&mut self, subject: &Noun, formula: &Noun) -> Option { 23 | None 24 | } 25 | 26 | /// Handle a Nock hint. 27 | /// 28 | /// Nock `*[a 10 b c]` will trigger `hint(a, b, c)`. 29 | #[allow(unused_variables)] 30 | fn hint(&mut self, 31 | subject: &Noun, 32 | hint: &Noun, 33 | c: &Noun) 34 | -> Result<(), NockError> { 35 | Ok(()) 36 | } 37 | 38 | /// Evaluate the nock `*[subject formula]` 39 | fn nock_on(&mut self, mut subject: Noun, mut formula: Noun) -> NockResult { 40 | loop { 41 | if let Shape::Cell(ops, tail) = formula.clone().get() { 42 | match ops.as_u32() { 43 | // Axis 44 | Some(0) => return get_axis(tail, &subject), 45 | 46 | // Just 47 | Some(1) => return Ok(tail.clone()), 48 | 49 | // Fire 50 | Some(2) => { 51 | match tail.get() { 52 | Shape::Cell(ref b, ref c) => { 53 | let p = try!(self.nock_on(subject.clone(), 54 | (*b).clone())); 55 | let q = try!(self.nock_on(subject, 56 | (*c).clone())); 57 | subject = p; 58 | formula = q; 59 | continue; 60 | } 61 | _ => return Err(NockError("fire".to_owned())), 62 | } 63 | } 64 | 65 | // Depth 66 | Some(3) => { 67 | let p = try!(self.nock_on(subject.clone(), 68 | (*tail).clone())); 69 | return match p.get() { 70 | Shape::Cell(_, _) => Ok(Noun::from(0u32)), 71 | _ => Ok(Noun::from(1u32)), 72 | }; 73 | } 74 | 75 | // Bump 76 | Some(4) => { 77 | let p = try!(self.nock_on(subject.clone(), 78 | (*tail).clone())); 79 | return match p.get() { 80 | Shape::Atom(ref x) => { 81 | // TODO: Non-bignum optimization 82 | Ok(Noun::from(BigUint::from_digits(x).unwrap() + 83 | BigUint::one())) 84 | } 85 | _ => Err(NockError("bump".to_owned())), 86 | }; 87 | } 88 | 89 | // Same 90 | Some(5) => { 91 | let p = try!(self.nock_on(subject.clone(), 92 | (*tail).clone())); 93 | return match p.get() { 94 | Shape::Cell(ref a, ref b) => { 95 | if a == b { 96 | // Yes. 97 | return Ok(Noun::from(0u32)); 98 | } else { 99 | // No. 100 | return Ok(Noun::from(1u32)); 101 | } 102 | } 103 | _ => return Err(NockError("same".to_owned())), 104 | }; 105 | } 106 | 107 | // If 108 | Some(6) => { 109 | if let Some((b, c, d)) = tail.get_122() { 110 | let p = try!(self.nock_on(subject.clone(), 111 | (*b).clone())); 112 | match p.get() { 113 | Shape::Atom(ref x) => { 114 | if x == &0u32.as_digits() { 115 | formula = (*c).clone(); 116 | } else if x == &1u32.as_digits() { 117 | formula = (*d).clone(); 118 | } else { 119 | return Err(NockError("if".to_owned())); 120 | } 121 | } 122 | _ => return Err(NockError("if".to_owned())), 123 | } 124 | continue; 125 | } else { 126 | return Err(NockError("if".to_owned())); 127 | } 128 | } 129 | 130 | // Compose 131 | Some(7) => { 132 | match tail.get() { 133 | Shape::Cell(ref b, ref c) => { 134 | let p = try!(self.nock_on(subject.clone(), 135 | (*b).clone())); 136 | subject = p; 137 | formula = (*c).clone(); 138 | continue; 139 | } 140 | _ => return Err(NockError("compose".to_owned())), 141 | } 142 | } 143 | 144 | // Push 145 | Some(8) => { 146 | match tail.get() { 147 | Shape::Cell(ref b, ref c) => { 148 | let p = try!(self.nock_on(subject.clone(), 149 | (*b).clone())); 150 | subject = Noun::cell(p, subject); 151 | formula = (*c).clone(); 152 | continue; 153 | } 154 | _ => return Err(NockError("push".to_owned())), 155 | } 156 | } 157 | 158 | // Call 159 | Some(9) => { 160 | match tail.get() { 161 | Shape::Cell(ref axis, ref c) => { 162 | // Construct core. 163 | subject = try!(self.nock_on(subject.clone(), 164 | (*c).clone())); 165 | // Fetch from core using axis. 166 | formula = try!(get_axis(axis, &subject)); 167 | 168 | if let Some(result) = self.call(&subject, 169 | &formula) { 170 | return result; 171 | } 172 | 173 | continue; 174 | } 175 | _ => return Err(NockError("call".to_owned())), 176 | } 177 | } 178 | 179 | // Hint 180 | Some(10) => { 181 | match tail.get() { 182 | Shape::Cell(ref b, ref c) => { 183 | try!(self.hint(&subject, b, c)); 184 | formula = (*c).clone(); 185 | continue; 186 | } 187 | _ => return Err(NockError("hint".to_owned())), 188 | } 189 | } 190 | 191 | // Unhandled opcode 192 | Some(code) => { 193 | return Err(NockError(format!("unknown opcode {}", 194 | code))); 195 | } 196 | 197 | None => { 198 | if let Shape::Cell(_, _) = ops.get() { 199 | // Autocons 200 | let a = try!(self.nock_on(subject.clone(), 201 | ops.clone())); 202 | let b = try!(self.nock_on(subject, tail.clone())); 203 | return Ok(Noun::cell(a, b)); 204 | } else { 205 | return Err(NockError("autocons".to_owned())); 206 | } 207 | } 208 | } 209 | } else { 210 | return Err(NockError("nock".to_owned())); 211 | } 212 | } 213 | } 214 | } 215 | 216 | /// Evaluate nock `/[axis subject]` 217 | pub fn get_axis(axis: &Noun, subject: &Noun) -> NockResult { 218 | fn fas(x: &[u8], n: usize, mut subject: &Noun) -> NockResult { 219 | for i in (0..(n - 1)).rev() { 220 | if let Shape::Cell(ref a, ref b) = subject.get() { 221 | if bit(x, i) { 222 | subject = b; 223 | } else { 224 | subject = a; 225 | } 226 | } else { 227 | return Err(NockError("axis".to_owned())); 228 | } 229 | } 230 | Ok((*subject).clone()) 231 | } 232 | 233 | #[inline] 234 | fn bit(data: &[u8], pos: usize) -> bool { 235 | data[pos / 8] & (1 << (pos % 8)) != 0 236 | } 237 | 238 | match axis.get() { 239 | Shape::Atom(ref x) => { 240 | let start = msb(x); 241 | fas(x, start, subject) 242 | } 243 | _ => Err(NockError("axis".to_owned())), 244 | } 245 | } 246 | --------------------------------------------------------------------------------