Merge pull request #152 from tildeio/usize_isize_coercions

Add missing coercions for `usize` and `isize`
This commit is contained in:
Godfrey Chan 2018-06-04 06:43:19 -07:00 committed by GitHub
commit c6c0011b4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 925 additions and 4 deletions

View File

@ -3,6 +3,7 @@ cache:
- '%HOMEDRIVE%%HOMEPATH%\.multirust -> .appveyor.yml'
environment:
# can't run game_of_life since termion doesn't support windows https://github.com/redox-os/termion/issues/103
EXAMPLES: duration calculator console geometry membership text_transform turbo_blank json_builder
VERBOSE: true
RUST_BACKTRACE: 1

View File

@ -21,7 +21,7 @@ cache:
env:
global:
- EXAMPLES="duration calculator console geometry membership text_transform turbo_blank json_builder"
- EXAMPLES="duration calculator console game_of_life geometry membership text_transform turbo_blank json_builder"
- VERBOSE=true
- RUST_BACKTRACE=1
- RUST_VERSION=stable

View File

@ -21,7 +21,7 @@ appveyor = { repository = "tildeio/helix", branch = "master", service = "github"
[workspace]
members = ["examples/calculator", "examples/console", "examples/geometry", "examples/duration", "examples/membership", "examples/text_transform", "examples/turbo_blank", "examples/json_builder"]
members = ["examples/calculator", "examples/console", "examples/game_of_life", "examples/geometry", "examples/duration", "examples/membership", "examples/text_transform", "examples/turbo_blank", "examples/json_builder"]
[dependencies]
libc = "0.2.0"

View File

@ -4,7 +4,7 @@ task :test do
sh "bundle exec rake"
end
examples = ENV["EXAMPLES"] || "duration calculator console geometry membership text_transform turbo_blank json_builder"
examples = ENV["EXAMPLES"] || "duration calculator console game_of_life geometry membership text_transform turbo_blank json_builder"
sh "bash ./examples/runner default #{examples}"
end
@ -15,7 +15,7 @@ task :install do
sh "bundle"
end
examples = ENV["EXAMPLES"] || "duration calculator console geometry membership text_transform turbo_blank json_builder"
examples = ENV["EXAMPLES"] || "duration calculator console game_of_life geometry membership text_transform turbo_blank json_builder"
sh "bash ./examples/runner install #{examples}"
end

View File

@ -4,6 +4,7 @@
extern crate libc;
use std::ffi::CStr;
use std::mem::size_of;
pub const PKG_VERSION: &'static str = env!("CARGO_PKG_VERSION");
@ -14,6 +15,10 @@ pub fn check_version() {
if PKG_VERSION != version {
panic!("libcsys-ruby version ({}) doesn't match helix_runtime version ({}).", PKG_VERSION, version);
}
if size_of::<usize>() != size_of::<u32>() && size_of::<usize>() != size_of::<u64>() {
panic!("unsupported architecture, size_of::<usize>() = {}", size_of::<usize>());
}
}
pub type void = libc::c_void;
@ -256,3 +261,39 @@ extern "C" {
#[link_name = "HELIX_Data_Set_Struct_Value"]
pub fn Data_Set_Struct_Value(obj: VALUE, data: *mut void);
}
#[inline]
pub unsafe fn NUM2USIZE(v: VALUE) -> usize {
if size_of::<usize>() == size_of::<u32>() {
NUM2U32(v) as usize
} else {
NUM2U64(v) as usize
}
}
#[inline]
pub unsafe fn USIZE2NUM(num: usize) -> VALUE {
if size_of::<usize>() == size_of::<u32>() {
U322NUM(num as u32)
} else {
U642NUM(num as u64)
}
}
#[inline]
pub unsafe fn NUM2ISIZE(v: VALUE) -> isize {
if size_of::<isize>() == size_of::<i32>() {
NUM2I32(v) as isize
} else {
NUM2I64(v) as isize
}
}
#[inline]
pub unsafe fn ISIZE2NUM(num: isize) -> VALUE {
if size_of::<isize>() == size_of::<i32>() {
I322NUM(num as i32)
} else {
I642NUM(num as i64)
}
}

3
examples/game_of_life/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
target
*.bundle
*.gem

View File

@ -0,0 +1,15 @@
[package]
name = "game_of_life"
version = "0.1.0"
authors = ["Godfrey Chan <godfrey@tilde.io>"]
[lib]
crate-type = ["cdylib"]
[dependencies.helix]
path = "../.."
[dependencies]
termion = "*"
rand = "*"

View File

@ -0,0 +1,5 @@
source 'https://rubygems.org'
gem 'helix_runtime', path: '../../ruby'
gem 'rake', '~> 10.0'
gem 'rspec', '~> 3.4'

26
examples/game_of_life/Rakefile Executable file
View File

@ -0,0 +1,26 @@
require 'bundler/setup'
require 'helix_runtime/build_task'
require 'rspec/core/rake_task'
require_relative '../shared.rb'
# For Windows
$stdout.sync = true
HelixRuntime::BuildTask.new do |t|
t.build_root = File.expand_path("../..", __dir__)
t.helix_lib_dir = File.expand_path("../../ruby/windows_build", __dir__)
t.pre_build = HelixRuntime::Tests.pre_build
end
RSpec::Core::RakeTask.new(:spec) do |t|
t.verbose = false
end
task :demo => :build do
$LOAD_PATH.unshift File.expand_path("./lib", __dir__)
require 'game_of_life'
GameOfLife.random.play!
end
task :spec => :build
task :default => :spec

View File

@ -0,0 +1,2 @@
require 'helix_runtime'
require 'game_of_life/native'

View File

@ -0,0 +1,401 @@
require 'spec_helper'
describe GameOfLife do
describe '.parse' do
context 'malformed inputs' do
it 'should reject zero-sized boards' do
expect(->() { GameOfLife.parse(0, 0, '') }).to raise_error(/cannot be 0/)
expect(->() { GameOfLife.parse(0, 10, '') }).to raise_error(/cannot be 0/)
expect(->() { GameOfLife.parse(10, 0, '') }).to raise_error(/cannot be 0/)
end
it 'should reject lines that are too long' do
expect(->() { GameOfLife.parse(2, 2, '...') }).to raise_error(/line 1 is too long/)
end
it 'should reject too many lines' do
expect(->() { GameOfLife.parse(2, 2, "\n\n\n") }).to raise_error(/too many lines/)
end
it 'should parse correctly' do
pattern = <<~GAME
..*..**
.......
**..*..
GAME
game = GameOfLife.parse(7, 3, pattern)
expect(game.to_s).to eq(pattern)
end
end
end
describe 'game rules' do
context 'a live cell' do
context 'with zero live neighbors' do
it 'should die, as if by under population' do
game = GameOfLife.parse 3, 3, <<~GAME
...
.*.
...
GAME
game.advance!
expect(game[1,1]).to eq(false)
end
end
context 'with one live neighbor' do
it 'should die, as if by under population' do
game = GameOfLife.parse 3, 3, <<~GAME
..*
.*.
...
GAME
game.advance!
expect(game[1,1]).to eq(false)
end
end
context 'with two live neighbors' do
it 'should live on' do
game = GameOfLife.parse 3, 3, <<~GAME
..*
.**
...
GAME
game.advance!
expect(game[1,1]).to eq(true)
end
end
context 'with three live neighbors' do
it 'should live on' do
game = GameOfLife.parse 3, 3, <<~GAME
..*
***
...
GAME
game.advance!
expect(game[1,1]).to eq(true)
end
end
context 'with four live neighbors' do
it 'should die, as if by overpopulation' do
game = GameOfLife.parse 3, 3, <<~GAME
..*
***
.*.
GAME
game.advance!
expect(game[1,1]).to eq(false)
end
end
end
context 'a dead cell' do
context 'with zero live neighbors' do
it 'should stay dead' do
game = GameOfLife.parse 3, 3, <<~GAME
...
...
...
GAME
game.advance!
expect(game[1,1]).to eq(false)
end
end
context 'with one live neighbor' do
it 'should stay dead' do
game = GameOfLife.parse 3, 3, <<~GAME
..*
...
...
GAME
game.advance!
expect(game[1,1]).to eq(false)
end
end
context 'with two live neighbors' do
it 'should stay dead' do
game = GameOfLife.parse 3, 3, <<~GAME
..*
..*
...
GAME
game.advance!
expect(game[1,1]).to eq(false)
end
end
context 'with three live neighbors' do
it 'should become a live cell, as if by reproduction' do
game = GameOfLife.parse 3, 3, <<~GAME
..*
*.*
...
GAME
game.advance!
expect(game[1,1]).to eq(true)
end
end
context 'with four live neighbors' do
it 'should stay dead' do
game = GameOfLife.parse 3, 3, <<~GAME
..*
*.*
.*.
GAME
game.advance!
expect(game[1,1]).to eq(false)
end
end
end
end
describe 'known patterns' do
shared_examples 'still life' do
it 'should stay still' do
game = GameOfLife.parse(width, height, pattern)
expect(game.to_s).to eq(pattern)
game.advance!
expect(game.to_s).to eq(pattern)
10.times { game.advance! }
expect(game.to_s).to eq(pattern)
end
end
shared_examples 'oscillator' do |period:|
it 'should oscillate' do
game = GameOfLife.parse(width, height, pattern)
expect(game.to_s).to eq(pattern)
game.advance!
expect(game.to_s).to_not eq(pattern)
(period - 1).times { game.advance! }
expect(game.to_s).to eq(pattern)
end
end
context 'Block' do
let(:width) { 4 }
let(:height) { 4 }
let(:pattern) {
<<~PATTERN
....
.**.
.**.
....
PATTERN
}
it_should_behave_like 'still life'
end
context 'Beehive' do
let(:width) { 6 }
let(:height) { 5 }
let(:pattern) {
<<~PATTERN
......
..**..
.*..*.
..**..
......
PATTERN
}
it_should_behave_like 'still life'
end
context 'Loaf' do
let(:width) { 6 }
let(:height) { 6 }
let(:pattern) {
<<~PATTERN
......
..**..
.*..*.
..*.*.
...*..
......
PATTERN
}
it_should_behave_like 'still life'
end
context 'Boat' do
let(:width) { 5 }
let(:height) { 5 }
let(:pattern) {
<<~PATTERN
.....
.**..
.*.*.
..*..
.....
PATTERN
}
it_should_behave_like 'still life'
end
context 'Tub' do
let(:width) { 5 }
let(:height) { 5 }
let(:pattern) {
<<~PATTERN
.....
..*..
.*.*.
..*..
.....
PATTERN
}
it_should_behave_like 'still life'
end
context 'Blinker' do
let(:width) { 5 }
let(:height) { 5 }
let(:pattern) {
<<~PATTERN
.....
..*..
..*..
..*..
.....
PATTERN
}
it_should_behave_like 'oscillator', period: 2
end
context 'Toad' do
let(:width) { 6 }
let(:height) { 6 }
let(:pattern) {
<<~PATTERN
......
......
..***.
.***..
......
......
PATTERN
}
it_should_behave_like 'oscillator', period: 2
end
context 'Beacon' do
let(:width) { 6 }
let(:height) { 6 }
let(:pattern) {
<<~PATTERN
......
.**...
.**...
...**.
...**.
......
PATTERN
}
it_should_behave_like 'oscillator', period: 2
end
context 'Pulsar' do
let(:width) { 17 }
let(:height) { 17 }
let(:pattern) {
<<~PATTERN
.................
.................
....***...***....
.................
..*....*.*....*..
..*....*.*....*..
..*....*.*....*..
....***...***....
.................
....***...***....
..*....*.*....*..
..*....*.*....*..
..*....*.*....*..
.................
....***...***....
.................
.................
PATTERN
}
it_should_behave_like 'oscillator', period: 3
end
context 'Pentadecathlon' do
let(:width) { 11 }
let(:height) { 18 }
let(:pattern) {
<<~PATTERN
...........
...........
...........
....***....
...*...*...
...*...*...
....***....
...........
...........
...........
...........
....***....
...*...*...
...*...*...
....***....
...........
...........
...........
PATTERN
}
it_should_behave_like 'oscillator', period: 15
end
end
end

View File

@ -0,0 +1,2 @@
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'game_of_life'

View File

@ -0,0 +1,376 @@
#[macro_use]
extern crate helix;
extern crate rand;
extern crate termion;
ruby! {
class GameOfLife {
struct {
width: usize,
height: usize,
cells: Vec<bool>,
}
def random() -> GameOfLife {
use termion::terminal_size;
let (width, height) = terminal_size().unwrap_or((100, 100));
let mut game = GameOfLife::new(width as usize, height as usize);
game.randomize();
game
}
def parse(width: usize, height: usize, input: String) -> GameOfLife {
let mut game = GameOfLife::new(width, height);
for (y, line) in input.lines().enumerate() {
assert!(y < height, "invalid input: too many lines (the game has a height of {})", height);
for (x, c) in line.chars().enumerate() {
assert!(x < width, "invalid input: line {} is too long (the game has a width of {})", y+1, width);
if c == '*' {
game.set(x, y, true);
}
}
}
game
}
def initialize(helix, width: usize, height: usize) {
assert!(width > 0, "width cannot be 0");
assert!(height > 0, "height cannot be 0");
let mut cells = vec![];
cells.resize(width * height, false);
GameOfLife { helix, width, height, cells }
}
#[ruby_name = "[]"]
def get(&self, x: usize, y: usize) -> bool {
self.cell_at(x, y).is_alive()
}
#[ruby_name = "[]="]
def set(&mut self, x: usize, y: usize, is_alive: bool) {
self.mut_cell_at(x, y).set(is_alive)
}
#[ruby_name = "advance!"]
def advance(&mut self) {
let mut next = self.cells.clone();
for cell in self.cells() {
let index = cell.index();
let is_alive = cell.is_alive();
let live_neighbors = cell.live_neighbors();
// Any live cell with fewer than two live neighbors dies, as if
// by under population.
if is_alive && live_neighbors < 2 {
next[index] = false;
// Any live cell with more than three live neighbors dies, as
// if by overpopulation.
} else if is_alive && live_neighbors > 3 {
next[index] = false;
// Any dead cell with exactly three live neighbors becomes a
// live cell, as if by reproduction.
} else if !is_alive && live_neighbors == 3 {
next[index] = true;
}
}
self.cells = next;
}
#[ruby_name = "randomize!"]
def randomize(&mut self) {
use rand::prelude::random;
for cell in self.cells.iter_mut() {
*cell = random();
}
}
#[ruby_name = "play!"]
def play(&mut self) {
use std::io::Write;
use std::thread::sleep;
use std::time::Duration;
use termion::async_stdin;
use termion::clear;
use termion::color::*;
use termion::cursor::{Goto, Hide, Show};
use termion::event::Key::Char;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::terminal_size;
let mut stdin = async_stdin().keys();
let mut stdout = std::io::stdout().into_raw_mode().unwrap();
let (terminal_width, terminal_height) = terminal_size().unwrap();
// ~30 FPS
let timeout = Duration::from_millis(30);
print!("{}", clear::All);
print!("{}", Hide);
loop {
for cell in self.cells() {
let (x, y) = cell.coordinates();
if x >= terminal_width as usize || y >= terminal_height as usize {
continue;
}
print!("{}", Goto(x as u16 + 1, y as u16 + 1));
if cell.is_alive() {
print!("{}*{}", Fg(LightYellow), Fg(Reset));
} else {
print!(".");
}
}
self.advance();
print!("{}", Goto(1, terminal_height));
print!("{} Press {}r{} to randomize, {}q{} to quit. {}", Bg(Blue), Fg(LightWhite), Fg(Reset), Fg(LightWhite), Fg(Reset), Bg(Reset));
stdout.flush().unwrap();
match stdin.next() {
Some(Ok(Char('r'))) => {
self.randomize();
},
Some(Ok(Char('q'))) => {
print!("{}", clear::All);
print!("{}", Goto(1, 1));
print!("{}", Show);
stdout.flush().unwrap();
return;
},
_ => ()
}
sleep(timeout);
}
}
def to_s(&self) -> String {
let mut s = String::with_capacity(self.cells.len() + self.height);
for cell in self.cells() {
if cell.is_alive() {
s.push('*');
} else {
s.push('.');
}
if cell.is_last_in_row() {
s.push('\n');
}
}
s
}
}
}
// These are hidden from Ruby
impl GameOfLife {
fn index_for(&self, x: usize, y: usize) -> usize {
assert!(x < self.width && y < self.height, "({}, {}) is out-of-bounds", x, y);
y * self.width + x
}
fn cell_at(&self, x: usize, y: usize) -> Cell {
let index = self.index_for(x, y);
Cell { game: self, index }
}
fn mut_cell_at(&mut self, x: usize, y: usize) -> MutCell {
let index = self.index_for(x, y);
MutCell { game: self, index }
}
fn cells(&self) -> Cells {
Cells { next: Some(self.cell_at(0,0)), size: self.cells.len() }
}
}
struct Cells<'a> {
next: Option<Cell<'a>>,
size: usize,
}
impl<'a> std::iter::Iterator for Cells<'a> {
type Item = Cell<'a>;
fn next(&mut self) -> Option<Self::Item> {
let next = match self.next {
Some(ref cell) => cell.next(),
None => None,
};
std::mem::replace(&mut self.next, next)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = match self.next {
Some(ref cell) => self.size - cell.index(),
None => 0,
};
(remaining, Some(remaining))
}
}
impl<'a> std::iter::ExactSizeIterator for Cells<'a> {}
impl<'a> std::iter::FusedIterator for Cells<'a> {}
struct Cell<'a> {
game: &'a GameOfLife,
index: usize,
}
struct MutCell<'a> {
game: &'a mut GameOfLife,
index: usize,
}
enum Neighbor {
TopLeft,
Top,
TopRight,
Left,
Right,
BottomLeft,
Bottom,
BottomRight,
}
trait Navigate {
fn game(&self) -> &GameOfLife;
fn index(&self) -> usize;
fn coordinates(&self) -> (usize, usize) {
(self.x(), self.y())
}
fn x(&self) -> usize {
self.index() % self.game().width
}
fn y(&self) -> usize {
self.index() / self.game().width
}
fn is_alive(&self) -> bool {
self.game().cells[self.index()]
}
fn is_last_in_row(&self) -> bool {
self.neighbor(Neighbor::Right).is_none()
}
fn is_last(&self) -> bool {
self.index() == self.game().cells.len() - 1
}
fn live_neighbors(&self) -> usize {
use Neighbor::*;
let neighbors = vec![
self.neighbor(TopLeft),
self.neighbor(Top),
self.neighbor(TopRight),
self.neighbor(Left),
self.neighbor(Right),
self.neighbor(BottomLeft),
self.neighbor(Bottom),
self.neighbor(BottomRight),
];
neighbors.into_iter()
.filter(Option::is_some)
.map(Option::unwrap)
.map(|cell| if cell.is_alive() { 1 } else { 0 })
.sum()
}
fn neighbor(&self, position: Neighbor) -> Option<Cell> {
use Neighbor::*;
let (x, y) = self.coordinates();
let game = self.game();
let mut nx = x;
let mut ny = y;
match position {
TopLeft => { nx -= 1; ny -= 1; },
Top => { ny -= 1; },
TopRight => { nx += 1; ny -= 1; },
Left => { nx -= 1; },
Right => { nx += 1; },
BottomLeft => { nx -= 1; ny += 1; },
Bottom => { ny += 1; },
BottomRight => { nx += 1; ny += 1; },
}
if nx >= game.width || ny >= game.height {
None
} else {
Some(Cell { game, index: game.index_for(nx, ny) })
}
}
}
impl<'a> Navigate for Cell<'a> {
fn game(&self) -> &GameOfLife {
self.game
}
fn index(&self) -> usize {
self.index
}
}
impl<'a> Navigate for MutCell<'a> {
fn game(&self) -> &GameOfLife {
self.game
}
fn index(&self) -> usize {
self.index
}
}
impl<'a> Cell<'a> {
// TODO: this should be in Navigate
fn next(&self) -> Option<Cell<'a>> {
if self.is_last() {
None
} else {
Some(Cell { game: self.game, index: self.index + 1 })
}
}
}
impl<'a> MutCell<'a> {
fn set(&mut self, value: bool) {
self.game.cells[self.index] = value
}
}

View File

@ -1,6 +1,55 @@
use std::mem::size_of;
use sys::{self, VALUE, T_FIXNUM, T_BIGNUM};
use super::{FromRuby, CheckResult, CheckedValue, ToRuby, ToRubyResult};
impl FromRuby for usize {
type Checked = CheckedValue<usize>;
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<usize>> {
if unsafe { sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
Ok(unsafe { CheckedValue::new(value) })
} else if size_of::<usize>() == size_of::<u32>() {
type_error!(value, "a 32-bit unsigned integer")
} else {
type_error!(value, "a 64-bit unsigned integer")
}
}
fn from_checked(checked: CheckedValue<usize>) -> usize {
unsafe { sys::NUM2USIZE(checked.to_value()) }
}
}
impl ToRuby for usize {
fn to_ruby(self) -> ToRubyResult {
Ok(unsafe { sys::USIZE2NUM(self) })
}
}
impl FromRuby for isize {
type Checked = CheckedValue<isize>;
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<isize>> {
if unsafe { sys::RB_TYPE_P(value, T_FIXNUM) || sys::RB_TYPE_P(value, T_BIGNUM) } {
Ok(unsafe { CheckedValue::new(value) })
} else if size_of::<isize>() == size_of::<i32>() {
type_error!(value, "a 32-bit signed integer")
} else {
type_error!(value, "a 64-bit signed integer")
}
}
fn from_checked(checked: CheckedValue<isize>) -> isize {
unsafe { sys::NUM2ISIZE(checked.to_value()) }
}
}
impl ToRuby for isize {
fn to_ruby(self) -> ToRubyResult {
Ok(unsafe { sys::ISIZE2NUM(self) })
}
}
impl FromRuby for u64 {
type Checked = CheckedValue<u64>;