Last active
August 29, 2015 14:04
-
-
Save zobar/c68c7df7a1207edbe0b9 to your computer and use it in GitHub Desktop.
Ruby simulation of a full adder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| .rbenv-gemsets | |
| .yardopts | |
| */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 2.1.2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ruby -w | |
| require 'curses' | |
| require 'forwardable' | |
| require_relative 'board' | |
| require_relative 'key' | |
| require_relative 'led' | |
| require_relative 'logger' | |
| require_relative 'logical' | |
| require_relative 'toggle' | |
| # | |
| # Simulation of a 4-bit full adder. To toggle input bits, press the | |
| # corresponding key. | |
| # | |
| class Adder | |
| extend Forwardable | |
| # | |
| # Contains the Adder's components and runs the main event loop. | |
| # | |
| attr_reader :board | |
| def_delegators :board, :run | |
| # | |
| # Creates a new Adder. | |
| # | |
| def initialize | |
| Curses.init_screen | |
| cols = Curses.cols | |
| lines = Curses.lines | |
| @board = Board height: lines, width: cols, x: 0, y: 0 | |
| board.logger x: 17, y: 0, height: lines, width: cols - 36 | |
| key_0_3 = switch 'd' | |
| board.led key_0_3, label: 'D', x: 4, y: 3 | |
| key_0_2 = switch 'a' | |
| board.led key_0_2, label: 'A', x: 7, y: 3 | |
| key_0_1 = switch 'v' | |
| board.led key_0_1, label: 'V', x: 10, y: 3 | |
| key_0_0 = switch 'e' | |
| board.led key_0_0, label: 'E', x: 13, y: 3 | |
| key_1_3 = switch 'r' | |
| board.led key_1_3, label: 'R', x: 4, y: 6 | |
| key_1_2 = switch 'u' | |
| board.led key_1_2, label: 'U', x: 7, y: 6 | |
| key_1_1 = switch 'l' | |
| board.led key_1_1, label: 'L', x: 10, y: 6 | |
| key_1_0 = switch 'z' | |
| board.led key_1_0, label: 'Z', x: 13, y: 6 | |
| result_0, carry_1 = half_adder key_0_0, key_1_0 | |
| board.led result_0, label: '1', x: 13, y: 9 | |
| board.led carry_1, label: ' ', x: 10, y: 0 | |
| result_1, carry_2 = full_adder carry_1, key_0_1, key_1_1 | |
| board.led result_1, label: '2', x: 10, y: 9 | |
| board.led carry_2, label: ' ', x: 7, y: 0 | |
| result_2, carry_3 = full_adder carry_2, key_0_2, key_1_2 | |
| board.led result_2, label: '4', x: 7, y: 9 | |
| board.led carry_3, label: ' ', x: 4, y: 0 | |
| result_3, result_4 = full_adder carry_3, key_0_3, key_1_3 | |
| board.led result_3, label: '8', x: 4, y: 9 | |
| board.led result_4, label: '16', x: 0, y: 9 | |
| end | |
| private | |
| def full_adder(carry, input_0, input_1) | |
| result_0, carry_0 = half_adder(input_0, input_1) | |
| result, carry_1 = half_adder(carry, result_0) | |
| [result, board.or(carry_0, carry_1)] | |
| end | |
| def half_adder(input_0, input_1) | |
| [board.xor(input_0, input_1), board.and(input_0, input_1)] | |
| end | |
| def log(string) | |
| puts string | |
| end | |
| def switch(character) | |
| board.toggle board.key(character) | |
| end | |
| end | |
| Adder.new.run |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'curses' | |
| require_relative 'factories' | |
| class Board | |
| include Factories | |
| # | |
| # Visible height of the Board. | |
| # | |
| attr_reader :height | |
| # | |
| # Hash that maps keys to an Array of handlers that should be called when each | |
| # key is pressed. | |
| # | |
| attr_reader :key_press_handlers | |
| # | |
| # Array of handlers that will be called at the end of the event loop. | |
| # | |
| attr_reader :soon_handlers | |
| # | |
| # Array of visual components. | |
| # | |
| attr_reader :visual | |
| # | |
| # Visible width of the Board. | |
| # | |
| attr_reader :width | |
| # | |
| # Curses window that is used to draw this Board and supply key events. | |
| # | |
| attr_reader :window | |
| # | |
| # Horizontal offset of the Board on the screen. | |
| # | |
| attr_reader :x | |
| # | |
| # Vertical offset of the Board on the screen. | |
| # | |
| attr_reader :y | |
| # | |
| # Registers a visual component with the Board. The component should implement | |
| # `start` to create its Curses context, and `stop` to destroy it. | |
| # | |
| def add_visual(component) | |
| visual << component | |
| end | |
| # | |
| # Clears the log area. | |
| # | |
| def clear_log | |
| @logger.clear unless @logger.nil? | |
| end | |
| def initialize(x, y, width, height) | |
| @x, @y, @width, @height = x, y, width, height | |
| @key_press_handlers = Hash.new { |hash, key| hash[key] = [] } | |
| @soon_handlers = [] | |
| @visual = [] | |
| end | |
| # | |
| # Appends a message to the log area. | |
| # | |
| def log(message) | |
| @logger.log message unless @logger.nil? | |
| end | |
| # | |
| # Registers a key-press handler. | |
| # | |
| def on_key_press(character, &block) | |
| @key_press_handlers[character] << block | |
| end | |
| # | |
| # Initializes Curses and all visual components, runs the event loop, and | |
| # cleans up when signaled by an `Interrupt` (Control-C). | |
| # | |
| def run | |
| start | |
| loop(&method(:event_loop)) | |
| rescue Interrupt | |
| ensure | |
| stop | |
| end | |
| # | |
| # Registers a handler to be called at the end of the current event loop. All | |
| # `soon` handlers are cleared after they return, so you will need to | |
| # re-register your handler if it needs to be called on a subsequent iteration | |
| # of the event loop. | |
| # | |
| def soon(&block) | |
| @soon_handlers << block | |
| end | |
| private | |
| def event_loop | |
| Curses.doupdate | |
| key = Curses.getch | |
| clear_log | |
| key_press_handlers[key].each(&:call) | |
| until soon_handlers.empty? | |
| handlers, @soon_handlers = @soon_handlers, [] | |
| handlers.each(&:call) | |
| end | |
| end | |
| def start | |
| Curses.cbreak | |
| Curses.curs_set 0 | |
| Curses.noecho | |
| @window = Curses.stdscr.subwin height, width, y, x | |
| visual.each(&:start) | |
| log "Press Control-C to quit." | |
| end | |
| def stop | |
| visual.each(&:stop) | |
| window.close unless window.nil? | |
| @window = nil | |
| Curses.close_screen | |
| end | |
| end | |
| def Board(options) | |
| Board.new(*options.values_at(:x, :y, :width, :height)) | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'forwardable' | |
| # | |
| # Base class for all logic components. | |
| # | |
| class Component | |
| extend Forwardable | |
| # | |
| # Board that this Component belongs to. | |
| # | |
| attr_reader :board | |
| # | |
| # Handlers that will be called when this Component's value changes. | |
| # | |
| attr_reader :change_handlers | |
| # | |
| # Current value of the component. | |
| # | |
| attr_reader :value | |
| def_delegators :board, :log | |
| # | |
| # Sets the value of the Component. If the value has changed, all registered | |
| # `change` handlers are called. | |
| # | |
| def change(new_value) | |
| if new_value != value | |
| log "#{self} → #{new_value.inspect}" | |
| @value = new_value | |
| change_handlers.each(&:call) | |
| else | |
| log "#{self} (noop)" | |
| end | |
| end | |
| def initialize(board, *args) | |
| @board = board | |
| @change_handlers = [] | |
| @value = false | |
| end | |
| # | |
| # Registers a handler to be called when this Component's value changes. | |
| # | |
| def on_change(&handler) | |
| change_handlers << handler | |
| end | |
| protected | |
| def watch(*components, &calculate) | |
| components.each do |component| | |
| component.on_change do | |
| change calculate.call(*components.map(&:value)) | |
| end | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # | |
| # Methods for creating Components on a Board. | |
| # | |
| module Factories | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| source 'https://rubygems.org' | |
| gem 'curses' | |
| group :development do | |
| gem 'yard' | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| GEM | |
| remote: https://rubygems.org/ | |
| specs: | |
| curses (1.0.1) | |
| yard (0.8.7.4) | |
| PLATFORMS | |
| ruby | |
| DEPENDENCIES | |
| curses | |
| yard |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require_relative 'strobe' | |
| require_relative 'factories' | |
| # | |
| # Strobes when a specific key has been pressed. | |
| # | |
| class Key < Strobe | |
| attr_reader :character | |
| def initialize(board, character) | |
| super | |
| @character = character | |
| board.on_key_press character, &method(:strobe) | |
| end | |
| def to_s | |
| "Key #{character.inspect}" | |
| end | |
| end | |
| module Factories | |
| # | |
| # Adds a Key to this Board. | |
| # | |
| def key(character) | |
| Key.new self, character | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'curses' | |
| require_relative 'component' | |
| require_relative 'factories' | |
| # | |
| # Visual component that displays a label in normal text if its input is off, or | |
| # inverse text if its input is on. | |
| # | |
| class Led < Component | |
| extend Forwardable | |
| # | |
| # Component that this Led uses for input. | |
| # | |
| attr_reader :input | |
| # | |
| # Label that this Led displays on-screen. | |
| # | |
| attr_reader :label | |
| # | |
| # Curses window that corresponds to this Led. | |
| # | |
| attr_reader :window | |
| # | |
| # Horizontal offset of this Led on the screen. | |
| # | |
| attr_reader :x | |
| # | |
| # Vertical offset of this Led on the screen. | |
| # | |
| attr_reader :y | |
| def initialize(board, input, x, y, label) | |
| super | |
| @input, @label = input, label | |
| @x = x + board.x | |
| @y = y + board.y | |
| watch input, &method(:calculate) | |
| end | |
| # | |
| # Creates and initializes this Led's Curses window. | |
| # | |
| def start | |
| @window = board.window.subwin 3, (label.size + 2), y, x | |
| calculate input.value | |
| end | |
| # | |
| # Destroys this Led's Curses window. | |
| # | |
| def stop | |
| window.close unless window.nil? | |
| @window = nil | |
| end | |
| def to_s | |
| "Led #{label.inspect}" | |
| end | |
| private | |
| def calculate(value) | |
| window.attrset(value ? Curses::A_REVERSE : Curses::A_NORMAL) | |
| window.box 0, 0 | |
| window.setpos 1, 1 | |
| window.addstr label | |
| window.noutrefresh | |
| value | |
| end | |
| end | |
| module Factories | |
| # | |
| # Adds a new Led component to this Board. Available options are `:x`, `:y`, | |
| # and `:label`. | |
| # | |
| def led(input, options) | |
| add_visual Led.new(self, input, *options.values_at(:x, :y, :label)) | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'curses' | |
| require_relative 'factories' | |
| # | |
| # Area for displaying a Board's event history. | |
| # | |
| class Logger | |
| # | |
| # Board that this Logger belongs to. | |
| # | |
| attr_reader :board | |
| # | |
| # Height of this Logger on-screen. | |
| # | |
| attr_reader :height | |
| # | |
| # Width of this Logger on-screen. | |
| # | |
| attr_reader :width | |
| # | |
| # Curses window that corresponds to this Logger. | |
| # | |
| attr_reader :window | |
| # | |
| # Horizontal offset of this Logger on-screen. | |
| # | |
| attr_reader :x | |
| # | |
| # Vertical offset of this Logger on-screen. | |
| # | |
| attr_reader :y | |
| def initialize(board, x, y, width, height) | |
| @board, @width, @height = board, width, height | |
| @x = x + board.x | |
| @y = y + board.y | |
| end | |
| # | |
| # Clears the contents of this Logger. | |
| # | |
| def clear | |
| window.clear | |
| window.noutrefresh | |
| end | |
| # | |
| # Logs a message. | |
| # | |
| def log(message) | |
| window.addstr "#{message}\n" | |
| window.noutrefresh | |
| end | |
| # | |
| # Creates and initializes this Logger's Curses window. | |
| # | |
| def start | |
| @window = board.window.subwin height, width, y, x | |
| window.scrollok true | |
| end | |
| # | |
| # Destroys this Logger's Curses window. | |
| # | |
| def stop | |
| window.close unless window.nil? | |
| @window = nil | |
| end | |
| end | |
| module Factories | |
| # | |
| # Adds a Logger to this Board. Available options are `:height`, `:width`, | |
| # `:x`, and `:y`. | |
| # | |
| def logger(options) | |
| @logger = Logger.new(self, *options.values_at(:x, :y, :width, :height)) | |
| add_visual @logger | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require_relative 'component' | |
| require_relative 'factories' | |
| # | |
| # Implements components whose outputs are defined by applying pure functions to | |
| # their inputs. | |
| # | |
| class Logical < Component | |
| # | |
| # Name of the operation performed by this Logical component. | |
| # | |
| attr_reader :name | |
| def initialize(board, name, inputs, &calculate) | |
| super | |
| @name = name | |
| watch(*inputs, &calculate) | |
| end | |
| def to_s | |
| "Logical #{name.inspect}" | |
| end | |
| end | |
| module Factories | |
| # | |
| # Adds an `and` gate to this Board. | |
| # | |
| def and(input_0, input_1) | |
| logical('and', input_0, input_1) do |value_0, value_1| | |
| value_0 && value_1 | |
| end | |
| end | |
| # | |
| # Adds an `or` gate to this Board. | |
| # | |
| def or(input_0, input_1) | |
| logical('or', input_0, input_1) do |value_0, value_1| | |
| value_0 || value_1 | |
| end | |
| end | |
| # | |
| # Adds an `xor` gate to this Board. | |
| # | |
| def xor(input_0, input_1) | |
| logical('xor', input_0, input_1) do |value_0, value_1| | |
| value_0 != value_1 | |
| end | |
| end | |
| private | |
| def logical(name, *inputs, &calculate) | |
| Logical.new self, name, inputs, &calculate | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require_relative 'component' | |
| # | |
| # Abstract class for components that strobe their outputs (ie, signal true then | |
| # false on the same iteration of the event loop). | |
| # | |
| class Strobe < Component | |
| # | |
| # Strobes this component's output. | |
| # | |
| def strobe | |
| change true | |
| board.soon { change false } | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require_relative 'component' | |
| require_relative 'factories' | |
| # | |
| # Component whose value toggles between `true` and `false` when its input | |
| # changes from `false` to `true`. | |
| # | |
| class Toggle < Component | |
| def initialize(board, input) | |
| super | |
| watch(input) { |input_value| input_value != value } | |
| end | |
| def to_s | |
| 'Toggle' | |
| end | |
| end | |
| module Factories | |
| # | |
| # Adds a Toggle to this Board. | |
| # | |
| def toggle(input) | |
| Toggle.new self, input | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment