Skip to content

Commit

Permalink
Initial commit, version 0.1.0
Browse files Browse the repository at this point in the history
Basic assembler/lc-3 VM works on browser.
  • Loading branch information
Nam Jeonghyun committed Sep 5, 2019
0 parents commit 98d7120
Show file tree
Hide file tree
Showing 12 changed files with 762 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
/.idea
16 changes: 16 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "lc3web"
version = "0.1.0"
authors = ["Nam Jeonghyun <[email protected]>"]
edition = "2018"

publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
yew = "0.8"
lc3-rs = "0.5"
serde = "1.0"
stdweb = "0.4"
lc3asm = "0.1"
339 changes: 339 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# lc3web: [lc3-rs](https://github.com/cr0sh/lc3-rs)\/[lc3asm](https://github.com/cr0sh/lc3asm) on web browser
This is a demo program about running LC-3 tools on web browser, using [WebAssembly](https://developer.mozilla.org/docs/WebAssembly) technology.

Technologies/Software used:
- [Rust](http://www.rust-lang.org) (>90% of codes were written + some HTML/CSS)
- [WebAssembly](https://developer.mozilla.org/docs/WebAssembly) (To run on browser)
- [Yew](https://github.com/yewstack/yew) (frontend)

## Requirements
- Rust/Cargo
- [cargo-web](https://github.com/koute/cargo-web)
- Install with `cargo install cargo-web`

Other dependencies will be downloaded during building.

## Running
- `git clone` this repository
- `cargo web run`
- Go to http://localhost:8000
85 changes: 85 additions & 0 deletions src/assembler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use crate::util::{CrossComponentBridge, Props};
use yew::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;

pub struct AssemblerConsole {
source: String,
output: String,
bridge: Rc<RefCell<CrossComponentBridge<Vec<u8>>>>,
}

pub enum Msg {
CheckSource,
AssembleAndLoad,
SourceInput(String),
}

impl Component for AssemblerConsole {
type Message = Msg;
type Properties = Props;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self {
source: String::new(),
output: String::new(),
bridge: props.bridge,
}
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::CheckSource => {
self.output.clear();
self.output.push_str("Checking...");
match lc3asm::assemble(&self.source) {
Ok(_) => {
self.output.push_str("Success\n");
}
Err(e) => {
self.output.push_str("Failed\n");
self.output.push_str(&e.to_string());
}
}
true
}
Msg::AssembleAndLoad => {
self.output.clear();
self.output.push_str("Assembling...");
match lc3asm::assemble(&self.source) {
Ok((obj, _)) => {
self.output.push_str("Success\n");
self.output.push_str("Sending to LC-3 console...");
self.bridge.borrow().send(obj);
self.output.push_str("Done\n");
}
Err(e) => {
self.output.push_str("Failed\n");
self.output.push_str(&e.to_string());
}
}
true
}
Msg::SourceInput(value) => {
self.source = value;
true
}
}
}
}

impl Renderable<Self> for AssemblerConsole {
fn view(&self) -> Html<Self> {
html! {
<div>
{ "Source code" }
<br />
<textarea id="asm-source" rows=15 cols=25 value=&self.source oninput=|e| Msg::SourceInput(e.value) />
<textarea readonly=true id="asm-output" rows=15 cols=25>
{ &self.output }
</textarea>
<button onclick = |_| Msg::CheckSource>{ "Check" }</button>
<button onclick = |_| Msg::AssembleAndLoad>{ "Assemble and load" }</button>
</div>
}
}
}
170 changes: 170 additions & 0 deletions src/lc3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
use crate::util::Props;
use lc3::{IOStreamHandler, VM};
use serde::{Deserialize, Serialize};
use std::cell::Cell;
use std::io::{Cursor, Read, Result as IOResult};
use yew::prelude::*;
use yew::worker::*;

pub struct LC3Console {
display: String,
input: String,
context: Box<dyn Bridge<LC3Agent>>,
}

pub enum Msg {
AgentResponse(String),
LoadSample,
Run,
LoadFromAssembler(Vec<u8>),
Clear,
Input(String),
}

impl Component for LC3Console {
type Message = Msg;
type Properties = Props;
fn create(props: Self::Properties, mut link: ComponentLink<Self>) -> Self {
let callback = link.send_back(|x: <LC3Agent as Agent>::Output| Msg::AgentResponse(x.0));
props
.bridge
.borrow_mut()
.register_callback(link.send_back(|v| {
stdweb::console!(log, "Received program!");
Msg::LoadFromAssembler(v)
}));
stdweb::console!(log, "Registered CCB callback");
LC3Console {
display: String::new(),
input: String::new(),
context: LC3Agent::bridge(callback),
}
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::LoadSample => {
self.display.clear();
self.context.send(AgentRequest::Reset);
self.context.send(AgentRequest::Load(Vec::from(
include_bytes!("print.obj").as_ref(),
)));
true
}
Msg::Run => {
self.display.clear();
self.context.send(AgentRequest::Run(self.input.clone()));
false
}
Msg::Clear => {
self.display.clear();
true
}
Msg::LoadFromAssembler(v) => {
self.display.clear();
self.display.push_str("Loading assembled file...\n");
self.context.send(AgentRequest::Reset);
self.context.send(AgentRequest::Load(v));
true
}
Msg::AgentResponse(s) => {
self.display.push_str(&s);
true
}
Msg::Input(value) => {
self.input = value;
true
}
}
}
}

impl Renderable<Self> for LC3Console {
fn view(&self) -> Html<Self> {
html! {
<div>
{ "Output" }
<br />
<textarea id="lc3-output" rows=15 cols=25 value=&self.display />
<button onclick = |_| Msg::LoadSample>{ "Load sample program" }</button>
<br />
{ "Input" }
<br />
<textarea id="lc3-input" rows=15 cols=25 value=&self.input oninput=|e| Msg::Input(e.value) />
<button onclick = |_| Msg::Run>{ "Run" }</button>
<button onclick = |_| Msg::Clear>{ "Clear output screen" }</button>
</div>
}
}
}

#[derive(Serialize, Deserialize)]
enum AgentRequest {
Load(Vec<u8>),
Run(String),
Reset,
}
impl Transferable for AgentRequest {}

#[derive(Serialize, Deserialize)]
struct AgentResponse(String);
impl Transferable for AgentResponse {}

struct LC3Agent {
lc3_vm: Box<VM<IOStreamHandler<StringCell, Vec<u8>>>>,
link: AgentLink<Self>,
}

impl Agent for LC3Agent {
type Reach = Context;
type Message = ();
type Input = AgentRequest;
type Output = AgentResponse;

fn create(link: AgentLink<Self>) -> Self {
let vm = VM::new((
StringCell(Cell::new(Cursor::new(String::new()))),
Vec::new(),
));
Self {
lc3_vm: Box::new(vm),
link,
}
}

fn update(&mut self, _msg: Self::Message) {}

fn handle(&mut self, msg: Self::Input, who: HandlerId) {
match msg {
AgentRequest::Load(program) => {
self.lc3_vm.load_u8(&program);
self.link
.response(who, AgentResponse(String::from("Loaded program...\n")))
}
AgentRequest::Run(input) => {
(self.lc3_vm.context.0).0.set(Cursor::new(input));
self.lc3_vm.run().unwrap();
self.link.response(
who,
AgentResponse(String::from_utf8_lossy(&self.lc3_vm.context.1).into_owned()),
)
}
AgentRequest::Reset => {
let vm = VM::new((
StringCell(Cell::new(Cursor::new(String::new()))),
Vec::new(),
));
self.lc3_vm = Box::new(vm);
self.link.response(who, AgentResponse(String::from("VM reset complete\n")))
}
}
}
}

struct StringCell(Cell<Cursor<String>>);

impl Read for StringCell {
fn read(&mut self, buf: &mut [u8]) -> IOResult<usize> {
self.0.get_mut().read(buf)
}
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#![recursion_limit = "512"]

pub mod assembler;
pub mod lc3;
pub mod util;
45 changes: 45 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use lc3web::assembler::AssemblerConsole;
use lc3web::lc3::LC3Console;
use lc3web::util::CrossComponentBridge;
use yew::prelude::*;
use std::rc::Rc;
use std::cell::RefCell;

struct RootModel(Rc<RefCell<CrossComponentBridge<Vec<u8>>>>);

impl Component for RootModel {
type Message = ();
type Properties = ();
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
Self(Rc::new(RefCell::new(CrossComponentBridge::new())))
}

fn update(&mut self, _: Self::Message) -> ShouldRender {
true
}
}

impl Renderable<RootModel> for RootModel {
fn view(&self) -> Html<Self> {
html! {
<div id="root-element">
<div class="outer-container">
<label>{"LC-3 Console"}</label>
<div class="container">
<LC3Console bridge=Rc::clone(&self.0) />
</div>
</div>
<div class="outer-container">
<label>{"Assembler Console"}</label>
<div class="container">
<AssemblerConsole bridge=Rc::clone(&self.0) />
</div>
</div>
</div>
}
}
}

fn main() {
yew::start_app::<RootModel>()
}
Binary file added src/print.obj
Binary file not shown.
29 changes: 29 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use std::rc::Rc;
use yew::prelude::*;
use std::cell::RefCell;

#[derive(Properties)]
pub struct Props {
#[props(required)]
pub bridge: Rc<RefCell<CrossComponentBridge<Vec<u8>>>>,
}

pub struct CrossComponentBridge<T> {
callback: Option<Callback<T>>,
}

impl<T> CrossComponentBridge<T> {
pub fn new() -> Self {
Self { callback: None }
}
pub fn send(&self, msg: T) {
if let Some(cb) = &self.callback {
cb.emit(msg)
} else {
stdweb::console!(log, "?");
}
}
pub fn register_callback(&mut self, callback: Callback<T>) {
self.callback = Some(callback)
}
}
Loading

0 comments on commit 98d7120

Please sign in to comment.