diff --git a/.github/workflows/actions.yml b/.github/workflows/action_deploy.yml similarity index 98% rename from .github/workflows/actions.yml rename to .github/workflows/action_deploy.yml index c2c1664..112616b 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/action_deploy.yml @@ -1,3 +1,4 @@ +name: delopy main on: push: branches: diff --git a/.github/workflows/devlop-deploy-netlify.yml b/.github/workflows/devlop-deploy-netlify.yml new file mode 100644 index 0000000..ce6438d --- /dev/null +++ b/.github/workflows/devlop-deploy-netlify.yml @@ -0,0 +1,63 @@ +name: deploy preview to netlify +on: + push: + branches: + - develop + - "!main" + +permissions: + contents: read + packages: read + checks: write + statuses: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: '8.15.7' + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + - name: Build ic10lsp + run: | + RUST_BACKTRACE=1 cargo xtask build -p ic10lsp_wasm --release -- -- + - name: Build ic10emu + run: | + RUST_BACKTRACE=1 cargo xtask build -p ic10emu_wasm --release -- -- + - name: Build Page + run: | + cd www + pnpm install + pnpm build + - name: Fix permissions + run: | + chmod -c -R +rX "www/dist/" | while read line; do + echo "::warning title=Invalid file permissions automatically fixed::$line" + done + - name: Upload artifacts + uses: actions/upload-pages-artifact@v3 + with: + path: www/dist + - name: Deploy to Netlify + uses: jsmrcaga/action-netlify-deploy@v2.0.0 + with: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_TOKEN_SECRET }} + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} + NETLIFY_DEPLOY_TO_PROD: true + install_command: "echo Skipping installing the dependencies" + build_command: "echo Skipping building the web files" + build_directory: www/dist + - name: Status check + uses: Sibz/github-status-action@v1.1.1 + with: + authToken: ${{ secrets.GITHUB_TOKEN }} + context: Netlify preview + state: success + target_url: ${{ env.NETLIFY_PREVIEW_URL }} diff --git a/.github/workflows/pull_request_deploy_preview.yml b/.github/workflows/pull_request_deploy_preview.yml new file mode 100644 index 0000000..1aa2def --- /dev/null +++ b/.github/workflows/pull_request_deploy_preview.yml @@ -0,0 +1,65 @@ + +name: netlify deploy-preview +on: + pull_request: + types: ['opened', 'edited', 'synchronize'] + branches: + - develop + - "!main" + +permissions: + contents: read + packages: read + checks: write + statuses: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: '8.15.7' + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + - name: Build ic10lsp + run: | + RUST_BACKTRACE=1 cargo xtask build -p ic10lsp_wasm --release -- -- + - name: Build ic10emu + run: | + RUST_BACKTRACE=1 cargo xtask build -p ic10emu_wasm --release -- -- + - name: Build Page + run: | + cd www + pnpm install + pnpm build + - name: Fix permissions + run: | + chmod -c -R +rX "www/dist/" | while read line; do + echo "::warning title=Invalid file permissions automatically fixed::$line" + done + - name: Upload artifacts + uses: actions/upload-pages-artifact@v3 + with: + path: www/dist + - name: Deploy to Netlify + uses: jsmrcaga/action-netlify-deploy@v2.0.0 + with: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_TOKEN_SECRET }} + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} + install_command: "echo Skipping installing the dependencies" + build_command: "echo Skipping building the web files" + build_directory: www/dist + deploy_alias: ${{ env.BRANCH_NAME }} + - name: Status check + uses: Sibz/github-status-action@v1.1.1 + with: + authToken: ${{ secrets.GITHUB_TOKEN }} + context: Netlify preview + state: success + target_url: ${{ env.NETLIFY_PREVIEW_URL }} diff --git a/.gitignore b/.gitignore index 4ab856e..e908052 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ target/ Session.vim *.pyc package-lock.json + +# Local Netlify folder +.netlify diff --git a/CHANGELOG.md b/CHANGELOG.md index ba9bb52..d154369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v0.2.1 + + - prevent borrow panics in VM during batch operations + - fix Maximize batch mode + - fix panic in parsing invalid numbers + ## v0.2.0 ### Share VM State! diff --git a/Cargo.lock b/Cargo.lock index 14fe0ed..6bda096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -560,7 +560,7 @@ dependencies = [ [[package]] name = "ic10emu" -version = "0.2.0" +version = "0.2.1" dependencies = [ "const-crc32", "convert_case", @@ -580,7 +580,7 @@ dependencies = [ [[package]] name = "ic10emu_wasm" -version = "0.2.0" +version = "0.2.1" dependencies = [ "console_error_panic_hook", "ic10emu", @@ -617,7 +617,7 @@ dependencies = [ [[package]] name = "ic10lsp_wasm" -version = "0.2.0" +version = "0.2.1" dependencies = [ "console_error_panic_hook", "futures", @@ -1844,7 +1844,7 @@ checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "xtask" -version = "0.2.0" +version = "0.2.1" dependencies = [ "clap", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 42e22c3..d1e3ba9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["ic10lsp_wasm", "ic10emu_wasm", "ic10emu", "xtask"] resolver = "2" [workspace.package] -version = "0.2.0" +version = "0.2.1" edition = "2021" [profile.release] diff --git a/ic10emu/build.rs b/ic10emu/build.rs index 480398a..87da430 100644 --- a/ic10emu/build.rs +++ b/ic10emu/build.rs @@ -44,13 +44,15 @@ fn write_repr_enum<'a, T: std::io::Write, I, P>( .map(|s| format!("serialize = \"{s}\"")) .collect::>() .join(", "); - let deprecated_str = if variant.deprecated { - ", deprecated = \"true\"".to_owned() - } else { - "".to_owned() - }; - let props_str = if let Some(val) = &variant.value { - format!(", props( value = \"{val}\"{deprecated_str})") + let mut props = Vec::new(); + if variant.deprecated { + props.push("deprecated = \"true\"".to_owned()); + } + if let Some(val) = &variant.value { + props.push(format!("value = \"{val}\"")); + } + let props_str = if !props.is_empty() { + format!(", props( {} )", props.join(", ")) } else { "".to_owned() }; @@ -70,7 +72,7 @@ fn write_logictypes() { let output_file = File::create(dest_path).unwrap(); let mut writer = BufWriter::new(&output_file); - let mut logictypes: Vec<(String, EnumVariant)> = Vec::new(); + let mut logictypes: Vec<(String, EnumVariant)> = Vec::new(); let l_infile = Path::new("data/logictypes.txt"); let l_contents = fs::read_to_string(l_infile).unwrap(); @@ -78,7 +80,7 @@ fn write_logictypes() { let mut it = line.splitn(3, ' '); let name = it.next().unwrap(); let val_str = it.next().unwrap(); - let val: Option = val_str.parse().ok(); + let val: Option = val_str.parse().ok(); let docs = it.next(); let deprecated = docs .map(|docs| docs.trim().to_uppercase() == "DEPRECATED") @@ -172,7 +174,7 @@ fn write_enums() { let output_file = File::create(dest_path).unwrap(); let mut writer = BufWriter::new(&output_file); - let mut enums_map: Vec<(String, EnumVariant)> = Vec::new(); + let mut enums_map: Vec<(String, EnumVariant)> = Vec::new(); let e_infile = Path::new("data/enums.txt"); let e_contents = fs::read_to_string(e_infile).unwrap(); @@ -180,7 +182,7 @@ fn write_enums() { let mut it = line.splitn(3, ' '); let name = it.next().unwrap(); let val_str = it.next().unwrap(); - let val: Option = val_str.parse().ok(); + let val: Option = val_str.parse().ok(); let docs = it.next(); let deprecated = docs .map(|docs| docs.trim().to_uppercase() == "DEPRECATED") diff --git a/ic10emu/data/logictypes.txt b/ic10emu/data/logictypes.txt index 331a124..e363bdc 100644 --- a/ic10emu/data/logictypes.txt +++ b/ic10emu/data/logictypes.txt @@ -7,18 +7,16 @@ AutoLand 226 Engages the automatic landing algorithm. The rocket will automatica AutoShutOff 218 Turns off all devices in the rocket upon reaching destination Bpm 103 Bpm BurnTimeRemaining 225 Estimated time in seconds until fuel is depleted. Calculated based on current fuel usage. -Bypass None Bypasses some internal behavoiur of the atmospherics device. CelestialHash 242 CelestialParentHash 250 -Channel None Channel on a cable network which should be considered volatile -Channel0 165 Channel on a cable network which should be considered volatile -Channel1 166 Channel on a cable network which should be considered volatile -Channel2 167 Channel on a cable network which should be considered volatile -Channel3 168 Channel on a cable network which should be considered volatile -Channel4 169 Channel on a cable network which should be considered volatile -Channel5 170 Channel on a cable network which should be considered volatile -Channel6 171 Channel on a cable network which should be considered volatile -Channel7 172 Channel on a cable network which should be considered volatile +Channel0 165 Channel 0 on a cable network which should be considered volatile +Channel1 166 Channel 1 on a cable network which should be considered volatile +Channel2 167 Channel 2 on a cable network which should be considered volatile +Channel3 168 Channel 3 on a cable network which should be considered volatile +Channel4 169 Channel 4 on a cable network which should be considered volatile +Channel5 170 Channel 5 on a cable network which should be considered volatile +Channel6 171 Channel 6 on a cable network which should be considered volatile +Channel7 172 Channel 7 on a cable network which should be considered volatile Charge 11 The current charge the device has Chart 256 ChartedNavPoints 259 @@ -92,7 +90,6 @@ OperationalTemperatureEfficiency 150 How the input pipe's temperature effects th OrbitPeriod 245 Orientation 230 Output 70 The output operation for a sort handling device, such as a stacker or sorter, when in logic mode the device will only action one repetition when set zero or above and then back to -1 and await further instructions -OverShootTarget None How far is the landing rocket going to overshoot its landing target at its current thrust. Expressed as a negative value in meters. ensure this is at zero by the time a rocket lands or risk total loss of rocket. PassedMoles 234 Plant 68 Performs the planting action for any plant based machinery PlantEfficiency1 52 DEPRECATED @@ -228,7 +225,6 @@ SizeX 160 Size on the X (right) axis of the object in largeGrids (a largeGrid is SizeY 161 Size on the Y(Up) axis of the object in largeGrids (a largeGrid is 2meters) SizeZ 162 Size on the Z(Forward) axis of the object in largeGrids (a largeGrid is 2meters) SolarAngle 22 Solar angle of the device -SolarConstant None Solar constant of the world SolarIrradiance 176 SoundAlert 175 Plays a sound alert on the devices speaker Stress 156 Machines get stressed when working hard. When Stress reaches 100 the machine will automatically shut down @@ -257,7 +253,6 @@ TotalMolesOutput 135 Returns the total moles of the device's Output Network TotalMolesOutput2 145 Returns the total moles of the device's Output2 Network TotalQuantity 265 TrueAnomaly 251 -Unknown None No description available VelocityMagnitude 79 The current magnitude of the velocity vector VelocityRelativeX 80 The current velocity X relative to the forward vector of this VelocityRelativeY 81 The current velocity Y relative to the forward vector of this diff --git a/ic10emu/generate_data.py b/ic10emu/generate_data.py index 785be14..79373e3 100644 --- a/ic10emu/generate_data.py +++ b/ic10emu/generate_data.py @@ -8,7 +8,7 @@ from collections import defaultdict from itertools import chain from pathlib import Path - +from typing import Any # type:ignore[reportAny] def intOrNone(val: str): try: @@ -219,23 +219,28 @@ def update_enum(enum: dict[str, tuple[int | None, str]], values: dict[str, int]) logic_types_path = Path("data") / "logictypes.txt" with logic_types_path.open(mode="w") as f: for t, (v, help) in sorted(logictypes.items()): - _ = f.write(f"{t} {v} {help.replace("\r", "").replace("\n", "\\n")}\n") + if v is not None: + _ = f.write(f"{t} {v} {help.replace("\r", "").replace("\n", "\\n")}\n") slot_logic_types_path = Path("data") / "slotlogictypes.txt" with slot_logic_types_path.open(mode="w") as f: for t, (v, help) in sorted(slotlogictypes.items()): - _ = f.write(f"{t} {v} {help.replace("\r", "").replace("\n", "\\n")}\n") + if v is not None: + _ = f.write(f"{t} {v} {help.replace("\r", "").replace("\n", "\\n")}\n") batch_modes_path = Path("data") / "batchmodes.txt" with batch_modes_path.open(mode="w") as f: for t, (v, help) in sorted(batchmodes.items()): - _ = f.write(f"{t} {v} {help.replace("\r", "").replace("\n", "\\n")}\n") + if v is not None: + _ = f.write(f"{t} {v} {help.replace("\r", "").replace("\n", "\\n")}\n") reagent_modes_path = Path("data") / "reagentmodes.txt" with reagent_modes_path.open(mode="w") as f: for t, (v, help) in sorted(reagentmodes.items()): - _ = f.write(f"{t} {v} {help.replace("\r", "").replace("\n", "\\n")}\n") + if v is not None: + _ = f.write(f"{t} {v} {help.replace("\r", "").replace("\n", "\\n")}\n") enums_path = Path("data") / "enums.txt" with enums_path.open(mode="w") as f: for name, (val, help) in sorted(enums.items()): - _ = f.write(f"{name} {val} {help.replace("\r", "").replace("\n", "\\n")}\n") + if val is not None: + _ = f.write(f"{name} {val} {help.replace("\r", "").replace("\n", "\\n")}\n") if __name__ == "__main__": main() diff --git a/ic10emu/src/device.rs b/ic10emu/src/device.rs index d5c3cf5..199b0a7 100644 --- a/ic10emu/src/device.rs +++ b/ic10emu/src/device.rs @@ -628,14 +628,14 @@ impl Device { LogicType::LineNumber, LogicField { field_type: FieldType::ReadWrite, - value: ic.ip as f64, + value: ic.ip() as f64, }, ); copy.insert( LogicType::Error, LogicField { field_type: FieldType::Read, - value: match ic.state { + value: match *ic.state.borrow() { ICState::Error(_) => 1.0, _ => 0.0, }, @@ -736,19 +736,12 @@ impl Device { pub fn get_field(&self, typ: LogicType, vm: &VM) -> Result { if typ == LogicType::LineNumber && self.ic.is_some() { - if let Ok(ic) = vm + let ic = vm .ics .get(&self.ic.unwrap()) .ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))? - .try_borrow() - { - Ok(ic.ip as f64) - } else { - // HACK: the game succeeds in getting the correct line number - // when reading it's own IC, but we'll panic trying to do it here - // this is worked around in internal_step so just return 0 here - Ok(0.0) - } + .borrow(); + Ok(ic.ip() as f64) } else if let Some(field) = self.get_fields(vm).get(&typ) { if field.field_type == FieldType::Read || field.field_type == FieldType::ReadWrite { Ok(field.value) @@ -773,16 +766,12 @@ impl Device { { Err(ICError::ReadOnlyField(typ.to_string())) } else if typ == LogicType::LineNumber && self.ic.is_some() { - // try borrow to set ip, we should only fail if the ic is in use aka is is *our* ic - // in game trying to set your own ic's LineNumber appears to be a Nop so this is fine. - if let Ok(mut ic) = vm + let ic = vm .ics .get(&self.ic.unwrap()) .ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))? - .try_borrow_mut() - { - ic.ip = val as u32; - } + .borrow(); + ic.set_ip(val as u32); Ok(()) } else if let Some(field) = self.fields.get_mut(&typ) { if field.field_type == FieldType::Write @@ -818,20 +807,12 @@ impl Device { && self.ic.is_some() && typ == SlotLogicType::LineNumber { - // try borrow to get ip, we should only fail if the ic is in us aka is is *our* ic - if let Ok(ic) = vm + let ic = vm .ics .get(&self.ic.unwrap()) .ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))? - .try_borrow() - { - Ok(ic.ip as f64) - } else { - // HACK: the game succeeds in getting the correct line number - // when reading it's own IC, but we'll panic trying to do it here - // this is worked around in internal_step so just return 0 here - Ok(0.0) - } + .borrow(); + Ok(ic.ip() as f64) } else { Ok(slot.get_field(typ)) } @@ -848,32 +829,18 @@ impl Device { .ok_or(ICError::SlotIndexOutOfRange(index))?; let mut fields = slot.get_fields(); if slot.typ == SlotType::ProgrammableChip && slot.occupant.is_some() && self.ic.is_some() { - // try borrow to get ip, we should only fail if the ic is in us aka is is *our* ic - if let Ok(ic) = vm + let ic = vm .ics .get(&self.ic.unwrap()) .ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))? - .try_borrow() - { - fields.insert( - SlotLogicType::LineNumber, - LogicField { - field_type: FieldType::ReadWrite, - value: ic.ip as f64, - }, - ); - } else { - // HACK: the game succeeds in getting the correct line number - // when reading it's own IC, but we'll panic trying to do it here - // this is worked around in internal_step so just return 0 here - fields.insert( - SlotLogicType::LineNumber, - LogicField { - field_type: FieldType::ReadWrite, - value: 0.0, - }, - ); - } + .borrow(); + fields.insert( + SlotLogicType::LineNumber, + LogicField { + field_type: FieldType::ReadWrite, + value: ic.ip() as f64, + }, + ); } Ok(fields) } @@ -883,32 +850,14 @@ impl Device { index: f64, typ: SlotLogicType, val: f64, - vm: &VM, + _vm: &VM, force: bool, ) -> Result<(), ICError> { let slot = self .slots .get_mut(index as usize) .ok_or(ICError::SlotIndexOutOfRange(index))?; - if slot.typ == SlotType::ProgrammableChip - && slot.occupant.is_some() - && self.ic.is_some() - && typ == SlotLogicType::LineNumber - { - // try borrow to set ip, we shoudl only fail if the ic is in us aka is is *our* ic - // in game trying to set your own ic's LineNumber appears to be a Nop so this is fine. - if let Ok(mut ic) = vm - .ics - .get(&self.ic.unwrap()) - .ok_or_else(|| ICError::UnknownDeviceID(self.ic.unwrap() as f64))? - .try_borrow_mut() - { - ic.ip = val as u32; - } - Ok(()) - } else { - slot.set_field(typ, val, force) - } + slot.set_field(typ, val, force) } pub fn get_slot(&self, index: f64) -> Result<&Slot, ICError> { diff --git a/ic10emu/src/grammar.rs b/ic10emu/src/grammar.rs index 24ec601..744956e 100644 --- a/ic10emu/src/grammar.rs +++ b/ic10emu/src/grammar.rs @@ -347,11 +347,13 @@ pub enum Operand { RegisterSpec(RegisterSpec), DeviceSpec(DeviceSpec), Number(Number), - LogicType(LogicType), - SlotLogicType(SlotLogicType), - LogicOrSlotLogicType(LogicType, SlotLogicType), - BatchMode(BatchMode), - ReagentMode(ReagentMode), + Type { + logic_type: Option, + slot_logic_type: Option, + batch_mode: Option, + reagent_mode: Option, + identifier: Identifier, + }, Identifier(Identifier), } @@ -368,27 +370,49 @@ impl Operand { target, }) => ic.get_register(indirection, target), Operand::Number(num) => Ok(num.value()), - Operand::LogicType(lt) => lt - .get_str("value") - .map(|val| val.parse::().unwrap() as f64) - .ok_or(interpreter::ICError::TypeValueNotKnown), - Operand::SlotLogicType(slt) => slt - .get_str("value") - .map(|val| val.parse::().unwrap() as f64) - .ok_or(interpreter::ICError::TypeValueNotKnown), - // default to using LogicType when converting to value - Operand::LogicOrSlotLogicType(lt, _) => lt - .get_str("value") - .map(|val| val.parse::().unwrap() as f64) - .ok_or(interpreter::ICError::TypeValueNotKnown), - Operand::BatchMode(bm) => bm - .get_str("value") - .map(|val| val.parse::().unwrap() as f64) - .ok_or(interpreter::ICError::TypeValueNotKnown), - Operand::ReagentMode(rm) => rm - .get_str("value") - .map(|val| val.parse::().unwrap() as f64) - .ok_or(interpreter::ICError::TypeValueNotKnown), + Operand::Type { + logic_type, + slot_logic_type, + batch_mode, + reagent_mode, + identifier: _, + } => { + if let Some(lt) = logic_type { + Ok(lt + .get_str("value") + .ok_or_else(|| ICError::NoGeneratedValue(lt.to_string()))? + .parse::() + .map_err(|_| { + ICError::BadGeneratedValueParse(lt.to_string(), "u16".to_owned()) + })? as f64) + } else if let Some(slt) = slot_logic_type { + Ok(slt + .get_str("value") + .ok_or_else(|| ICError::NoGeneratedValue(slt.to_string()))? + .parse::() + .map_err(|_| { + ICError::BadGeneratedValueParse(slt.to_string(), "u8".to_owned()) + })? as f64) + } else if let Some(bm) = batch_mode { + Ok(bm + .get_str("value") + .ok_or_else(|| ICError::NoGeneratedValue(bm.to_string()))? + .parse::() + .map_err(|_| { + ICError::BadGeneratedValueParse(bm.to_string(), "u8".to_owned()) + })? as f64) + } else if let Some(rm) = reagent_mode { + Ok(rm + .get_str("value") + .ok_or_else(|| ICError::NoGeneratedValue(rm.to_string()))? + .parse::() + .map_err(|_| { + ICError::BadGeneratedValueParse(rm.to_string(), "u8".to_owned()) + })? as f64) + } else { + Err(interpreter::ICError::TypeValueNotKnown) + } + } Operand::Identifier(id) => { Err(interpreter::ICError::UnknownIdentifier(id.name.to_string())) } @@ -400,6 +424,49 @@ impl Operand { } } + pub fn as_value_i64( + &self, + ic: &interpreter::IC, + signed: bool, + inst: InstructionOp, + index: u32, + ) -> Result { + match self { + Self::Number(num) => Ok(num.value_i64()), + _ => { + let val = self.as_value(ic, inst, index)?; + if val < -9.223_372_036_854_776E18 { + Err(interpreter::ICError::ShiftUnderflowI64) + } else if val <= 9.223_372_036_854_776E18 { + Ok(interpreter::f64_to_i64(val, signed)) + } else { + Err(interpreter::ICError::ShiftOverflowI64) + } + } + } + } + + pub fn as_value_i32( + &self, + ic: &interpreter::IC, + inst: InstructionOp, + index: u32, + ) -> Result { + match self { + Self::Number(num) => Ok(num.value_i64() as i32), + _ => { + let val = self.as_value(ic, inst, index)?; + if val < -2147483648.0 { + Err(interpreter::ICError::ShiftUnderflowI32) + } else if val <= 2147483647.0 { + Ok(val as i32) + } else { + Err(interpreter::ICError::ShiftOverflowI32) + } + } + } + } + pub fn as_register( &self, ic: &interpreter::IC, @@ -431,6 +498,7 @@ impl Operand { Device::Numbered(p) => { let dp = ic .pins + .borrow() .get(p as usize) .ok_or(interpreter::ICError::DeviceIndexOutOfRange(p as f64)) .copied()?; @@ -443,6 +511,7 @@ impl Operand { let val = ic.get_register(indirection, target)?; let dp = ic .pins + .borrow() .get(val as usize) .ok_or(interpreter::ICError::DeviceIndexOutOfRange(val)) .copied()?; @@ -460,73 +529,74 @@ impl Operand { } } - pub fn as_value_i64( + pub fn as_logic_type( &self, ic: &interpreter::IC, - signed: bool, inst: InstructionOp, index: u32, - ) -> Result { - let val = self.as_value(ic, inst, index)?; - if val < -9.223_372_036_854_776E18 { - Err(interpreter::ICError::ShiftUnderflowI64) - } else if val <= 9.223_372_036_854_776E18 { - Ok(interpreter::f64_to_i64(val, signed)) - } else { - Err(interpreter::ICError::ShiftOverflowI64) + ) -> Result { + match &self { + Operand::Type { + logic_type: Some(lt), + .. + } => Ok(*lt), + _ => LogicType::try_from(self.as_value(ic, inst, index)?), } } - pub fn as_value_i32( + pub fn as_slot_logic_type( &self, ic: &interpreter::IC, inst: InstructionOp, index: u32, - ) -> Result { - let val = self.as_value(ic, inst, index)?; - if val < -2147483648.0 { - Err(interpreter::ICError::ShiftUnderflowI32) - } else if val <= 2147483647.0 { - Ok(val as i32) - } else { - Err(interpreter::ICError::ShiftOverflowI32) + ) -> Result { + match &self { + Operand::Type { + slot_logic_type: Some(slt), + .. + } => Ok(*slt), + _ => SlotLogicType::try_from(self.as_value(ic, inst, index)?), } } - pub fn as_logic_type( + pub fn as_batch_mode( &self, ic: &interpreter::IC, inst: InstructionOp, index: u32, - ) -> Result { + ) -> Result { match &self { - Operand::LogicType(lt) => Ok(*lt), - Operand::LogicOrSlotLogicType(lt, _slt) => Ok(*lt), - _ => LogicType::try_from(self.as_value(ic, inst, index)?), + Operand::Type { + batch_mode: Some(bm), + .. + } => Ok(*bm), + _ => BatchMode::try_from(self.as_value(ic, inst, index)?), } } - pub fn as_slot_logic_type( + pub fn as_reagent_mode( &self, ic: &interpreter::IC, inst: InstructionOp, index: u32, - ) -> Result { + ) -> Result { match &self { - Operand::SlotLogicType(slt) => Ok(*slt), - Operand::LogicOrSlotLogicType(_lt, slt) => Ok(*slt), - _ => SlotLogicType::try_from(self.as_value(ic, inst, index)?), + Operand::Type { + reagent_mode: Some(rm), + .. + } => Ok(*rm), + _ => ReagentMode::try_from(self.as_value(ic, inst, index)?), } } pub fn translate_alias(&self, ic: &interpreter::IC) -> Self { match &self { - Operand::Identifier(id) => { - if let Some(alias) = ic.aliases.get(&id.name) { + Operand::Identifier(id) | Operand::Type { identifier: id, .. } => { + if let Some(alias) = ic.aliases.borrow().get(&id.name) { alias.clone() - } else if let Some(define) = ic.defines.get(&id.name) { + } else if let Some(define) = ic.defines.borrow().get(&id.name) { Operand::Number(Number::Float(*define)) - } else if let Some(label) = ic.program.labels.get(&id.name) { + } else if let Some(label) = ic.program.borrow().labels.get(&id.name) { Operand::Number(Number::Float(*label as f64)) } else { self.clone() @@ -673,11 +743,11 @@ impl FromStr for Operand { if rest_iter.next().is_none() { Ok(Some(connection)) } else { - let start = 1 + target_str.len() + 1 + connection_str.len(); + let end = 1 + target_str.len() + 1 + connection_str.len(); Err(ParseError { line: 0, - start, - end: start, + start: end - connection_str.len(), + end, msg: "Invalid device connection specifier".to_owned(), }) } @@ -692,10 +762,11 @@ impl FromStr for Operand { connection, })) } else { + let end = 1 + target_str.len(); Err(ParseError { line: 0, - start: 0, - end: 0, + start: 1, + end, msg: "Invalid device specifier".to_owned(), }) } @@ -710,8 +781,8 @@ impl FromStr for Operand { } else { Err(ParseError { line: 0, - start: 0, - end: 0, + start: 6, + end: hash_str.len(), msg: "Invalid hash string: Can not contain '\"'".to_owned(), }) } @@ -721,7 +792,7 @@ impl FromStr for Operand { let num_str = rest_iter .take_while_ref(|c| c.is_ascii_hexdigit()) .collect::(); - let num = i64::from_str_radix(&num_str, 16).unwrap() as f64; + let num = i64::from_str_radix(&num_str, 16).unwrap(); if rest_iter.next().is_none() { Ok(Operand::Number(Number::Hexadecimal(num))) } else { @@ -738,7 +809,7 @@ impl FromStr for Operand { let num_str = rest_iter .take_while_ref(|c| c.is_digit(2)) .collect::(); - let num = i64::from_str_radix(&num_str, 2).unwrap() as f64; + let num = i64::from_str_radix(&num_str, 2).unwrap(); if rest_iter.next().is_none() { Ok(Operand::Number(Number::Binary(num))) } else { @@ -767,26 +838,40 @@ impl FromStr for Operand { .collect::(); if !decimal_str.is_empty() { let float_str = float_str + "." + &decimal_str; - let num = f64::from_str(&float_str).unwrap(); - Ok(Operand::Number(Number::Float(num))) + if let Ok(num) = f64::from_str(&float_str) { + Ok(Operand::Number(Number::Float(num))) + } else { + Err(ParseError { + line: 0, + start: 0, + end: 0, + msg: "Invalid Number".to_owned(), + }) + } } else { - let start = float_str.len() + 1; Err(ParseError { line: 0, - start, - end: start, + start: 0, + end: float_str.len(), msg: "Invalid Decimal Number".to_owned(), }) } } else if rest_iter.next().is_none() { - let num = f64::from_str(&float_str).unwrap(); - Ok(Operand::Number(Number::Float(num))) + if let Ok(num) = f64::from_str(&float_str) { + Ok(Operand::Number(Number::Float(num))) + } else { + Err(ParseError { + line: 0, + start: 0, + end: float_str.len(), + msg: "Invalid Number".to_owned(), + }) + } } else { - let start = float_str.len(); Err(ParseError { line: 0, - start, - end: start, + start: 0, + end: float_str.len(), msg: "Invalid Integer Number".to_owned(), }) } @@ -796,24 +881,23 @@ impl FromStr for Operand { Ok(Operand::Number(Number::Enum( val.get_str("value").unwrap().parse().unwrap(), ))) - } else if let Ok(lt) = LogicType::from_str(s) { - if let Ok(slt) = SlotLogicType::from_str(s) { - Ok(Operand::LogicOrSlotLogicType(lt, slt)) - } else { - Ok(Operand::LogicType(lt)) - } - } else if let Ok(slt) = SlotLogicType::from_str(s) { - if let Ok(lt) = LogicType::from_str(s) { - Ok(Operand::LogicOrSlotLogicType(lt, slt)) + } else { + let lt = LogicType::from_str(s).ok(); + let slt = SlotLogicType::from_str(s).ok(); + let bm = BatchMode::from_str(s).ok(); + let rm = ReagentMode::from_str(s).ok(); + let identifier = Identifier::from_str(s)?; + if lt.is_some() || slt.is_some() || bm.is_some() || rm.is_some() { + Ok(Operand::Type { + logic_type: lt, + slot_logic_type: slt, + batch_mode: bm, + reagent_mode: rm, + identifier, + }) } else { - Ok(Operand::SlotLogicType(slt)) + Ok(Operand::Identifier(identifier)) } - } else if let Ok(bm) = BatchMode::from_str(s) { - Ok(Operand::BatchMode(bm)) - } else if let Ok(rm) = ReagentMode::from_str(s) { - Ok(Operand::ReagentMode(rm)) - } else { - Ok(Operand::Identifier(s.parse::()?)) } } } @@ -863,12 +947,10 @@ impl Display for Operand { Operand::Number(number) => match number { Number::Float(_) => Display::fmt(&number.value(), f), Number::Hexadecimal(n) => { - // FIXME: precision loss here, maybe we should track the source i64? - write!(f, "${:x}", *n as i64) + write!(f, "${:x}", *n) } Number::Binary(n) => { - // FIXME: precision loss here, maybe we should track the source i64? - write!(f, "%{:b}", *n as i64) + write!(f, "%{:b}", *n) } Number::Constant(c) => { dbg!(c); @@ -893,11 +975,7 @@ impl Display for Operand { Display::fmt(&number.value(), f) } }, - Operand::LogicType(logic) => Display::fmt(logic, f), - Operand::SlotLogicType(slot_logic) => Display::fmt(slot_logic, f), - Operand::LogicOrSlotLogicType(logic, _) => Display::fmt(logic, f), - Operand::BatchMode(batch_mode) => Display::fmt(batch_mode, f), - Operand::ReagentMode(reagent_mode) => Display::fmt(reagent_mode, f), + Operand::Type { identifier, .. } => Display::fmt(&identifier, f), Operand::Identifier(ident) => Display::fmt(&ident, f), } } @@ -985,8 +1063,8 @@ impl Display for Identifier { #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] pub enum Number { Float(f64), - Binary(f64), - Hexadecimal(f64), + Binary(i64), + Hexadecimal(i64), Constant(f64), String(String), Enum(f64), @@ -995,14 +1073,19 @@ pub enum Number { impl Number { pub fn value(&self) -> f64 { match self { - Number::Enum(val) - | Number::Float(val) - | Number::Binary(val) - | Number::Constant(val) - | Number::Hexadecimal(val) => *val, + Number::Enum(val) | Number::Float(val) | Number::Constant(val) => *val, + + Number::Binary(val) | Number::Hexadecimal(val) => *val as f64, Number::String(s) => const_crc32::crc32(s.as_bytes()) as i32 as f64, } } + pub fn value_i64(&self) -> i64 { + match self { + Number::Enum(val) | Number::Float(val) | Number::Constant(val) => *val as i64, + Number::Binary(val) | Number::Hexadecimal(val) => *val, + Number::String(s) => const_crc32::crc32(s.as_bytes()) as i32 as i64, + } + } } #[cfg(test)] @@ -1034,7 +1117,15 @@ mod tests { device: Device::Numbered(0), connection: None, }), - Operand::LogicType(LogicType::Setting), + Operand::Type { + logic_type: Some(LogicType::Setting), + slot_logic_type: None, + batch_mode: None, + reagent_mode: None, + identifier: Identifier { + name: "Setting".to_owned(), + }, + }, Operand::Number(Number::Float(0.0)), ], },),), @@ -1055,7 +1146,7 @@ mod tests { indirection: 0, target: 0, }), - Operand::Number(Number::Hexadecimal(4095.0)), + Operand::Number(Number::Hexadecimal(4095)), ], },),), comment: None, @@ -1157,7 +1248,15 @@ mod tests { device: Device::Numbered(0), connection: None }), - Operand::LogicOrSlotLogicType(LogicType::On, SlotLogicType::On), + Operand::Type { + logic_type: Some(LogicType::On), + slot_logic_type: Some(SlotLogicType::On), + batch_mode: None, + reagent_mode: None, + identifier: Identifier { + name: "On".to_owned(), + }, + }, Operand::Number(Number::Float(1.0)) ] })), @@ -1230,7 +1329,15 @@ mod tests { }, connection: None, }), - Operand::LogicType(LogicType::RatioWater), + Operand::Type { + logic_type: Some(LogicType::RatioWater), + slot_logic_type: None, + batch_mode: None, + reagent_mode: None, + identifier: Identifier { + name: "RatioWater".to_owned(), + }, + }, ], },),), comment: None, @@ -1269,7 +1376,7 @@ mod tests { indirection: 0, target: 1, }), - Operand::Number(Number::Hexadecimal(255.0)), + Operand::Number(Number::Hexadecimal(255)), ], },),), comment: None, @@ -1282,7 +1389,7 @@ mod tests { indirection: 0, target: 1, }), - Operand::Number(Number::Binary(8.0)), + Operand::Number(Number::Binary(8)), ], },),), comment: None, @@ -1363,4 +1470,48 @@ mod tests { test_roundtrip("$abcd"); test_roundtrip("%1001"); } + + + #[test] + fn all_generated_enums_have_value() { + use strum::IntoEnumIterator; + for lt in LogicType::iter() { + println!("testing LogicType.{lt}"); + let value = lt.get_str("value"); + assert!(value.is_some()); + assert!(value.unwrap().parse::().is_ok()); + } + for slt in SlotLogicType::iter() { + println!("testing SlotLogicType.{slt}"); + let value = slt.get_str("value"); + assert!(value.is_some()); + assert!(value.unwrap().parse::().is_ok()); + } + for bm in BatchMode::iter() { + println!("testing BatchMode.{bm}"); + let value = bm.get_str("value"); + assert!(value.is_some()); + assert!(value.unwrap().parse::().is_ok()); + } + for rm in ReagentMode::iter() { + println!("testing ReagentMode.{rm}"); + let value = rm.get_str("value"); + assert!(value.is_some()); + assert!(value.unwrap().parse::().is_ok()); + } + for le in LogicEnums::iter() { + println!("testing Enum.{le}"); + let value = le.get_str("value"); + assert!(value.is_some()); + assert!(value.unwrap().parse::().is_ok()); + } + } + + #[test] + fn bad_parse_does_not_panic() { + let code = "move foo -"; + let parsed = parse(code); + assert!(parsed.is_err()); + println!("{}", parsed.unwrap_err()); + } } diff --git a/ic10emu/src/interpreter.rs b/ic10emu/src/interpreter.rs index 933f256..c0a1f65 100644 --- a/ic10emu/src/interpreter.rs +++ b/ic10emu/src/interpreter.rs @@ -1,6 +1,6 @@ use core::f64; use serde::{Deserialize, Serialize}; -use std::{ops::Deref, string::ToString}; +use std::{cell::{Cell, RefCell}, ops::Deref, string::ToString}; use std::{ collections::{HashMap, HashSet}, error::Error, @@ -119,6 +119,10 @@ pub enum ICError { ChannelIndexOutOfRange(usize), #[error("slot has no occupant")] SlotNotOccupied, + #[error("generated Enum {0} has no value attached. Report this error.")] + NoGeneratedValue(String), + #[error("generated Enum {0}'s value does not parse as {1} . Report this error.")] + BadGeneratedValueParse(String, String), } impl ICError { @@ -180,21 +184,20 @@ impl Display for ICState { pub struct IC { pub device: u32, pub id: u32, - pub registers: [f64; 18], + pub registers: RefCell<[f64; 18]>, /// Instruction Pointer - pub ip: u32, + pub ip: Cell, /// Instruction Count since last yield - pub ic: u16, - pub stack: [f64; 512], - pub aliases: HashMap, - pub defines: HashMap, - pub pins: [Option; 6], - pub code: String, - pub program: Program, - pub state: ICState, + pub ic: Cell, + pub stack: RefCell<[f64; 512]>, + pub aliases: RefCell>, + pub defines: RefCell>, + pub pins: RefCell<[Option; 6]>, + pub code: RefCell, + pub program: RefCell, + pub state: RefCell, } - #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FrozenIC { @@ -215,21 +218,22 @@ pub struct FrozenIC { } impl From for FrozenIC - where T: Deref +where + T: Deref, { fn from(ic: T) -> Self { FrozenIC { device: ic.device, id: ic.id, - registers: ic.registers, - ip: ic.ip, - ic: ic.ic, - stack: ic.stack, - aliases: ic.aliases.clone(), - defines: ic.defines.clone(), - pins: ic.pins, - state: ic.state.clone(), - code: ic.code.clone(), + registers: *ic.registers.borrow(), + ip: ic.ip.get(), + ic: ic.ic.get(), + stack: *ic.stack.borrow(), + aliases: ic.aliases.borrow().clone(), + defines: ic.defines.borrow().clone(), + pins: *ic.pins.borrow(), + state: ic.state.borrow().clone(), + code: ic.code.borrow().clone(), } } } @@ -239,16 +243,16 @@ impl From for IC { IC { device: value.device, id: value.id, - registers: value.registers, - ip: value.ip, - ic: value.ic, - stack: value.stack, - aliases: value.aliases, - defines: value.defines, - pins: value.pins, - state: value.state, - code: value.code.clone(), - program: Program::from_code_with_invalid(&value.code), + registers: RefCell::new(value.registers), + ip: Cell::new(value.ip), + ic: Cell::new(value.ic), + stack: RefCell::new(value.stack), + aliases: RefCell::new(value.aliases), + defines: RefCell::new(value.defines), + pins: RefCell::new(value.pins), + state: RefCell::new(value.state), + code: RefCell::new(value.code.clone()), + program: RefCell::new(Program::from_code_with_invalid(&value.code)), } } } @@ -369,57 +373,49 @@ impl IC { IC { device, id, - ip: 0, - ic: 0, - registers: [0.0; 18], - stack: [0.0; 512], - pins: [None; 6], - program: Program::new(), - code: String::new(), - aliases: HashMap::new(), - defines: HashMap::new(), - state: ICState::Start, + ip: Cell::new(0), + ic: Cell::new(0), + registers: RefCell::new([0.0; 18]), + stack: RefCell::new([0.0; 512]), + pins: RefCell::new([None; 6]), + program: RefCell::new(Program::new()), + code: RefCell::new(String::new()), + aliases: RefCell::new(HashMap::new()), + defines: RefCell::new(HashMap::new()), + state: RefCell::new(ICState::Start), } } - pub fn reset(&mut self) { - self.ip = 0; - self.ic = 0; - self.registers = [0.0; 18]; - self.stack = [0.0; 512]; - self.aliases = HashMap::new(); - self.defines = HashMap::new(); - self.state = ICState::Start; + pub fn reset(&self) { + self.ip.replace(0); + self.ic.replace(0); + self.registers.replace([0.0; 18]); + self.stack.replace([0.0; 512]); + self.aliases.replace(HashMap::new()); + self.defines.replace(HashMap::new()); + self.state.replace(ICState::Start); } /// Set program code if it's valid - pub fn set_code(&mut self, code: &str) -> Result<(), ICError> { + pub fn set_code(&self, code: &str) -> Result<(), ICError> { let prog = Program::try_from_code(code)?; - self.ip = 0; - self.ic = 0; - self.aliases = HashMap::new(); - self.defines = HashMap::new(); - self.program = prog; - self.code = code.to_string(); + self.program.replace(prog); + self.code.replace(code.to_string()); Ok(()) } /// Set program code and translate invalid lines to Nop, collecting errors pub fn set_code_invalid(&mut self, code: &str) { let prog = Program::from_code_with_invalid(code); - self.ip = 0; - self.ic = 0; - self.aliases = HashMap::new(); - self.defines = HashMap::new(); - self.program = prog; - self.code = code.to_string(); + self.program.replace(prog); + self.code.replace(code.to_string()); } pub fn get_real_target(&self, indirection: u32, target: u32) -> Result { let mut i = indirection; let mut t = target as f64; while i > 0 { - if let Some(new_t) = self.registers.get(t as usize) { + if let Some(new_t) = self.registers.borrow().get(t as usize) { t = *new_t; } else { return Err(ICError::RegisterIndexOutOfRange(t)); @@ -432,6 +428,7 @@ impl IC { pub fn get_register(&self, indirection: u32, target: u32) -> Result { let t = self.get_real_target(indirection, target)?; self.registers + .borrow() .get(t as usize) .ok_or(ICError::RegisterIndexOutOfRange(t)) .copied() @@ -439,72 +436,76 @@ impl IC { /// sets a register thorough, recursing through provided indirection, returns value previously pub fn set_register( - &mut self, + &self, indirection: u32, target: u32, val: f64, ) -> Result { let t = self.get_real_target(indirection, target)?; - let old_val = self - .registers + let mut registers = self.registers.borrow_mut(); + let old_val = registers .get(t as usize) .ok_or(ICError::RegisterIndexOutOfRange(t)) .copied()?; - self.registers[t as usize] = val; + registers[t as usize] = val; Ok(old_val) } /// save ip to 'ra' or register 18 - fn al(&mut self) { - self.registers[17] = self.ip as f64 + 1.0; + fn al(&self) { + self.registers.borrow_mut()[17] = self.ip() as f64 + 1.0; } - pub fn push(&mut self, val: f64) -> Result { - let sp = (self.registers[16].round()) as i32; + pub fn push(&self, val: f64) -> Result { + let mut registers = self.registers.borrow_mut(); + let mut stack = self.stack.borrow_mut(); + let sp = (registers[16].round()) as i32; if sp < 0 { Err(ICError::StackUnderflow) } else if sp >= 512 { Err(ICError::StackOverflow) } else { - let last = self.stack[sp as usize]; - self.stack[sp as usize] = val; - self.registers[16] += 1.0; + let last = stack[sp as usize]; + stack[sp as usize] = val; + registers[16] += 1.0; Ok(last) } } - pub fn pop(&mut self) -> Result { - self.registers[16] -= 1.0; - let sp = (self.registers[16].round()) as i32; + pub fn pop(&self) -> Result { + let mut registers = self.registers.borrow_mut(); + registers[16] -= 1.0; + let sp = (registers[16].round()) as i32; if sp < 0 { Err(ICError::StackUnderflow) } else if sp >= 512 { Err(ICError::StackOverflow) } else { - let last = self.stack[sp as usize]; + let last = self.stack.borrow()[sp as usize]; Ok(last) } } - pub fn poke(&mut self, address: f64, val: f64) -> Result { + pub fn poke(&self, address: f64, val: f64) -> Result { let sp = address.round() as i32; if !(0..512).contains(&sp) { Err(ICError::StackIndexOutOfRange(address)) } else { - let last = self.stack[sp as usize]; - self.stack[sp as usize] = val; + let mut stack = self.stack.borrow_mut(); + let last = stack[sp as usize]; + stack[sp as usize] = val; Ok(last) } } pub fn peek(&self) -> Result { - let sp = (self.registers[16] - 1.0).round() as i32; + let sp = (self.registers.borrow()[16] - 1.0).round() as i32; if sp < 0 { Err(ICError::StackUnderflow) } else if sp >= 512 { Err(ICError::StackOverflow) } else { - let last = self.stack[sp as usize]; + let last = self.stack.borrow()[sp as usize]; Ok(last) } } @@ -516,37 +517,49 @@ impl IC { } else if sp >= 512 { Err(ICError::StackOverflow) } else { - let last = self.stack[sp as usize]; + let last = self.stack.borrow()[sp as usize]; Ok(last) } } /// processes one line of the contained program - pub fn step(&mut self, vm: &VM, advance_ip_on_err: bool) -> Result { + pub fn step(&self, vm: &VM, advance_ip_on_err: bool) -> Result { // TODO: handle sleep - self.state = ICState::Running; - let line = self.ip; + self.state.replace(ICState::Running); + let line = self.ip(); let result = self.internal_step(vm, advance_ip_on_err); if let Err(error) = result { let error = LineError { error, line }; - self.state = ICState::Error(error.clone()); + self.state.replace(ICState::Error(error.clone())); Err(error) } else { Ok(true) } } - fn internal_step(&mut self, vm: &VM, advance_ip_on_err: bool) -> Result<(), ICError> { + pub fn ip(&self) -> u32 { + self.ip.get() + } + + pub fn set_ip(&self, val: u32) { + self.ip.replace(val); + } + + fn internal_step(&self, vm: &VM, advance_ip_on_err: bool) -> Result<(), ICError> { use grammar::*; use ICError::*; - let mut next_ip = self.ip + 1; + let mut next_ip = self.ip() + 1; // XXX: This closure should be replaced with a try block // https://github.com/rust-lang/rust/issues/31436 - let mut process_op = |this: &mut Self| -> Result<(), ICError> { + let mut process_op = |this: &Self| -> Result<(), ICError> { use grammar::InstructionOp::*; - let line = this.program.get_line(this.ip)?; + // force the program borrow to drop + let line = { + let prog = this.program.borrow(); + prog.get_line(this.ip())?.clone() + }; let operands = &line.operands; let inst = line.instruction; match inst { @@ -557,14 +570,14 @@ impl IC { let a = a.as_value(this, inst, 1)?; let now = time::OffsetDateTime::now_local() .unwrap_or_else(|_| time::OffsetDateTime::now_utc()); - this.state = ICState::Sleep(now, a); + this.state.replace(ICState::Sleep(now, a)); Ok(()) } oprs => Err(ICError::mismatch_operands(oprs.len(), 1)), }, // TODO Yield => match &operands[..] { [] => { - this.state = ICState::Yield; + this.state.replace(ICState::Yield); Ok(()) } oprs => Err(ICError::mismatch_operands(oprs.len(), 0)), @@ -585,10 +598,11 @@ impl IC { desired: "Number".to_owned(), }); }; - if this.defines.contains_key(&ident.name) { + let mut defines = this.defines.borrow_mut(); + if defines.contains_key(&ident.name) { Err(DuplicateDefine(ident.name.clone())) } else { - this.defines.insert(ident.name.clone(), num.value()); + defines.insert(ident.name.clone(), num.value()); Ok(()) } } @@ -625,7 +639,7 @@ impl IC { }) } }; - this.aliases.insert(ident.name.clone(), alias); + this.aliases.borrow_mut().insert(ident.name.clone(), alias); Ok(()) } oprs => Err(ICError::mismatch_operands(oprs.len(), 2)), @@ -671,7 +685,7 @@ impl IC { let b = b.as_value(this, inst, 2)?; let c = c.as_value(this, inst, 3)?; next_ip = if a == b { - (this.ip as f64 + c) as u32 + (this.ip() as f64 + c) as u32 } else { next_ip }; @@ -703,7 +717,7 @@ impl IC { let a = a.as_value(this, inst, 1)?; let b = b.as_value(this, inst, 2)?; next_ip = if a == 0.0 { - (this.ip as f64 + b) as u32 + (this.ip() as f64 + b) as u32 } else { next_ip }; @@ -738,7 +752,7 @@ impl IC { let b = b.as_value(this, inst, 2)?; let c = c.as_value(this, inst, 3)?; next_ip = if a != b { - (this.ip as f64 + c) as u32 + (this.ip() as f64 + c) as u32 } else { next_ip }; @@ -770,7 +784,7 @@ impl IC { let a = a.as_value(this, inst, 1)?; let b = b.as_value(this, inst, 2)?; next_ip = if a != 0.0 { - (this.ip as f64 + b) as u32 + (this.ip() as f64 + b) as u32 } else { next_ip }; @@ -805,7 +819,7 @@ impl IC { let b = b.as_value(this, inst, 2)?; let c = c.as_value(this, inst, 3)?; next_ip = if a < b { - (this.ip as f64 + c) as u32 + (this.ip() as f64 + c) as u32 } else { next_ip }; @@ -840,7 +854,7 @@ impl IC { let b = b.as_value(this, inst, 2)?; let c = c.as_value(this, inst, 3)?; next_ip = if a <= b { - (this.ip as f64 + c) as u32 + (this.ip() as f64 + c) as u32 } else { next_ip }; @@ -872,7 +886,7 @@ impl IC { let a = a.as_value(this, inst, 1)?; let b = b.as_value(this, inst, 2)?; next_ip = if a <= 0.0 { - (this.ip as f64 + b) as u32 + (this.ip() as f64 + b) as u32 } else { next_ip }; @@ -904,7 +918,7 @@ impl IC { let a = a.as_value(this, inst, 1)?; let b = b.as_value(this, inst, 2)?; next_ip = if a < 0.0 { - (this.ip as f64 + b) as u32 + (this.ip() as f64 + b) as u32 } else { next_ip }; @@ -939,7 +953,7 @@ impl IC { let b = b.as_value(this, inst, 2)?; let c = c.as_value(this, inst, 3)?; next_ip = if a > b { - (this.ip as f64 + c) as u32 + (this.ip() as f64 + c) as u32 } else { next_ip }; @@ -971,7 +985,7 @@ impl IC { let a = a.as_value(this, inst, 1)?; let b = b.as_value(this, inst, 2)?; next_ip = if a > 0.0 { - (this.ip as f64 + b) as u32 + (this.ip() as f64 + b) as u32 } else { next_ip }; @@ -1006,7 +1020,7 @@ impl IC { let b = b.as_value(this, inst, 2)?; let c = c.as_value(this, inst, 3)?; next_ip = if a >= b { - (this.ip as f64 + c) as u32 + (this.ip() as f64 + c) as u32 } else { next_ip }; @@ -1038,7 +1052,7 @@ impl IC { let a = a.as_value(this, inst, 1)?; let b = b.as_value(this, inst, 2)?; next_ip = if a >= 0.0 { - (this.ip as f64 + b) as u32 + (this.ip() as f64 + b) as u32 } else { next_ip }; @@ -1090,7 +1104,7 @@ impl IC { next_ip = if f64::abs(a - b) <= f64::max(c * f64::max(a.abs(), b.abs()), f64::EPSILON * 8.0) { - (this.ip as f64 + d) as u32 + (this.ip() as f64 + d) as u32 } else { next_ip }; @@ -1134,7 +1148,7 @@ impl IC { let b = b.as_value(this, inst, 2)?; let c = c.as_value(this, inst, 3)?; next_ip = if a.abs() <= f64::max(b * a.abs(), f64::EPSILON * 8.0) { - (this.ip as f64 + c) as u32 + (this.ip() as f64 + c) as u32 } else { next_ip }; @@ -1186,7 +1200,7 @@ impl IC { next_ip = if f64::abs(a - b) > f64::max(c * f64::max(a.abs(), b.abs()), f64::EPSILON * 8.0) { - (this.ip as f64 + d) as u32 + (this.ip() as f64 + d) as u32 } else { next_ip }; @@ -1229,7 +1243,7 @@ impl IC { let b = b.as_value(this, inst, 2)?; let c = c.as_value(this, inst, 3)?; next_ip = if a.abs() > f64::max(b * a.abs(), f64::EPSILON * 8.0) { - (this.ip as f64 + c) as u32 + (this.ip() as f64 + c) as u32 } else { next_ip }; @@ -1261,7 +1275,7 @@ impl IC { let (device, _connection) = d.as_device(this, inst, 1)?; let a = a.as_value(this, inst, 2)?; next_ip = if device.is_some() { - (this.ip as f64 + a) as u32 + (this.ip() as f64 + a) as u32 } else { next_ip }; @@ -1293,7 +1307,7 @@ impl IC { let (device, _connection) = d.as_device(this, inst, 1)?; let a = a.as_value(this, inst, 2)?; next_ip = if device.is_none() { - (this.ip as f64 + a) as u32 + (this.ip() as f64 + a) as u32 } else { next_ip }; @@ -1315,7 +1329,7 @@ impl IC { let a = a.as_value(this, inst, 1)?; let b = b.as_value(this, inst, 2)?; next_ip = if a.is_nan() { - (this.ip as f64 + b) as u32 + (this.ip() as f64 + b) as u32 } else { next_ip }; @@ -1344,7 +1358,7 @@ impl IC { Jr => match &operands[..] { [a] => { let a = a.as_value(this, inst, 1)?; - next_ip = (this.ip as f64 + a) as u32; + next_ip = (this.ip() as f64 + a) as u32; Ok(()) } oprs => Err(ICError::too_many_operands(oprs.len(), 1)), @@ -2178,7 +2192,7 @@ impl IC { if ic_id == &this.id { this.poke(addr, val)?; } else { - let mut ic = vm.ics.get(ic_id).unwrap().borrow_mut(); + let ic = vm.ics.get(ic_id).unwrap().borrow(); ic.poke(addr, val)?; } vm.set_modified(device_id); @@ -2206,7 +2220,7 @@ impl IC { if ic_id == &this.id { this.poke(addr, val)?; } else { - let mut ic = vm.ics.get(ic_id).unwrap().borrow_mut(); + let ic = vm.ics.get(ic_id).unwrap().borrow(); ic.poke(addr, val)?; } vm.set_modified(device_id as u32); @@ -2360,7 +2374,7 @@ impl IC { if lt == LogicType::LineNumber && this.device == device_id { // HACK: we can't use device.get_field as that will try to reborrow our // ic which will panic - this.set_register(indirection, target, this.ip as f64)?; + this.set_register(indirection, target, this.ip() as f64)?; Ok(()) } else { let device = vm.get_device_same_network(this.device, device_id); @@ -2390,7 +2404,7 @@ impl IC { if lt == LogicType::LineNumber && this.device == device_id as u32 { // HACK: we can't use device.get_field as that will try to reborrow our // ic which will panic - this.set_register(indirection, target, this.ip as f64)?; + this.set_register(indirection, target, this.ip() as f64)?; Ok(()) } else { let device = vm.get_device_same_network(this.device, device_id as u32); @@ -2419,7 +2433,7 @@ impl IC { if slt == SlotLogicType::LineNumber && this.device == device_id { // HACK: we can't use device.get_slot_field as that will try to reborrow our // ic which will panic - this.set_register(indirection, target, this.ip as f64)?; + this.set_register(indirection, target, this.ip() as f64)?; Ok(()) } else { let device = vm.get_device_same_network(this.device, device_id); @@ -2448,7 +2462,7 @@ impl IC { let device = vm.get_device_same_network(this.device, device_id); match device { Some(device) => { - let rm = ReagentMode::try_from(rm.as_value(this, inst, 3)?)?; + let rm = rm.as_reagent_mode(this, inst, 3)?; let name = name.as_value(this, inst, 4)?; let val = device.borrow().get_reagent(&rm, name); this.set_register(indirection, target, val)?; @@ -2467,7 +2481,7 @@ impl IC { } = reg.as_register(this, inst, 1)?; let prefab = prefab.as_value(this, inst, 2)?; let lt = lt.as_logic_type(this, inst, 3)?; - let bm = BatchMode::try_from(bm.as_value(this, inst, 4)?)?; + let bm = bm.as_batch_mode(this, inst, 4)?; let val = vm.get_batch_device_field(this.device, prefab, lt, bm)?; this.set_register(indirection, target, val)?; Ok(()) @@ -2483,7 +2497,7 @@ impl IC { let prefab = prefab.as_value(this, inst, 2)?; let name = name.as_value(this, inst, 3)?; let lt = lt.as_logic_type(this, inst, 4)?; - let bm = BatchMode::try_from(bm.as_value(this, inst, 5)?)?; + let bm = bm.as_batch_mode(this, inst, 5)?; let val = vm.get_batch_name_device_field(this.device, prefab, name, lt, bm)?; this.set_register(indirection, target, val)?; @@ -2501,7 +2515,7 @@ impl IC { let name = name.as_value(this, inst, 3)?; let index = index.as_value(this, inst, 4)?; let slt = slt.as_slot_logic_type(this, inst, 5)?; - let bm = BatchMode::try_from(bm.as_value(this, inst, 6)?)?; + let bm = bm.as_batch_mode(this, inst, 6)?; let val = vm.get_batch_name_device_slot_field( this.device, prefab, @@ -2524,7 +2538,7 @@ impl IC { let prefab = prefab.as_value(this, inst, 2)?; let index = index.as_value(this, inst, 3)?; let slt = slt.as_slot_logic_type(this, inst, 4)?; - let bm = BatchMode::try_from(bm.as_value(this, inst, 5)?)?; + let bm = bm.as_batch_mode(this, inst, 5)?; let val = vm.get_batch_device_slot_field(this.device, prefab, index, slt, bm)?; this.set_register(indirection, target, val)?; @@ -2536,8 +2550,8 @@ impl IC { }; let result = process_op(self); if result.is_ok() || advance_ip_on_err { - self.ic += 1; - self.ip = next_ip; + self.ic.set(self.ic.get() + 1); + self.set_ip(next_ip); } result } @@ -2590,3 +2604,83 @@ pub fn i64_to_f64(i: i64) -> f64 { } i as f64 } + +#[cfg(test)] +mod tests { + use crate::vm::VMError; + + use super::*; + + #[test] + fn batch_modes() -> Result<(), VMError> { + let mut vm = VM::new(); + let ic = vm.add_ic(None).unwrap(); + let ic_id = { + let device = vm.devices.get(&ic).unwrap(); + let device_ref = device.borrow(); + device_ref.ic.unwrap() + }; + let ic_chip = vm.ics.get(&ic_id).unwrap().borrow(); + vm.set_code( + ic, + r#"lb r0 HASH("ItemActiveVent") On Sum + lb r1 HASH("ItemActiveVent") On Maximum + lb r2 HASH("ItemActiveVent") On Minimum"#, + )?; + vm.step_ic(ic, false)?; + let r0 = ic_chip.get_register(0, 0).unwrap(); + assert_eq!(r0, 0.0); + vm.step_ic(ic, false)?; + let r1 = ic_chip.get_register(0, 1).unwrap(); + assert_eq!(r1, f64::NEG_INFINITY); + vm.step_ic(ic, false)?; + let r2 = ic_chip.get_register(0, 2).unwrap(); + assert_eq!(r2, f64::INFINITY); + Ok(()) + } + + #[test] + fn stack() -> Result<(), VMError> { + let mut vm = VM::new(); + let ic = vm.add_ic(None).unwrap(); + let ic_id = { + let device = vm.devices.get(&ic).unwrap(); + let device_ref = device.borrow(); + device_ref.ic.unwrap() + }; + let ic_chip = vm.ics.get(&ic_id).unwrap().borrow(); + vm.set_code( + ic, + r#"push 100 + push 10 + pop r0 + push 1000 + peek r1 + poke 1 20 + pop r2 + "#, + )?; + vm.step_ic(ic, false)?; + let stack0 = ic_chip.peek_addr(0.0)?; + assert_eq!(stack0, 100.0); + vm.step_ic(ic, false)?; + let stack1 = ic_chip.peek_addr(1.0)?; + assert_eq!(stack1, 10.0); + vm.step_ic(ic, false)?; + let r0 = ic_chip.get_register(0, 0).unwrap(); + assert_eq!(r0, 10.0); + vm.step_ic(ic, false)?; + let stack1 = ic_chip.peek_addr(1.0)?; + assert_eq!(stack1, 1000.0); + vm.step_ic(ic, false)?; + let r1 = ic_chip.get_register(0, 1).unwrap(); + assert_eq!(r1, 1000.0); + vm.step_ic(ic, false)?; + let stack1 = ic_chip.peek_addr(1.0)?; + assert_eq!(stack1, 20.0); + vm.step_ic(ic, false)?; + let r2 = ic_chip.get_register(0, 2).unwrap(); + assert_eq!(r2, 20.0); + Ok(()) + } +} diff --git a/ic10emu/src/vm.rs b/ic10emu/src/vm.rs index 832ee65..00b7825 100644 --- a/ic10emu/src/vm.rs +++ b/ic10emu/src/vm.rs @@ -285,13 +285,11 @@ impl VM { device.borrow_mut().id = new_id; self.devices.insert(new_id, device); self.ics.iter().for_each(|(_id, ic)| { - if let Ok(mut ic_ref) = ic.try_borrow_mut() { - ic_ref.pins.iter_mut().for_each(|pin| { - if pin.is_some_and(|d| d == old_id) { - pin.replace(new_id); - } - }) - } + ic.borrow().pins.borrow_mut().iter_mut().for_each(|pin| { + if pin.is_some_and(|d| d == old_id) { + pin.replace(new_id); + } + }) }); self.networks.iter().for_each(|(_net_id, net)| { if let Ok(mut net_ref) = net.try_borrow_mut() { @@ -312,15 +310,14 @@ impl VM { .ok_or(VMError::UnknownId(id))? .borrow(); let ic_id = *device.ic.as_ref().ok_or(VMError::NoIC(id))?; - let mut ic = self + let ic = self .ics .get(&ic_id) .ok_or(VMError::UnknownIcId(ic_id))? - .borrow_mut(); + .borrow(); let new_prog = interpreter::Program::try_from_code(code)?; - ic.program = new_prog; - ic.ip = 0; - ic.code = code.to_string(); + ic.program.replace(new_prog); + ic.code.replace(code.to_string()); Ok(true) } @@ -332,15 +329,14 @@ impl VM { .ok_or(VMError::UnknownId(id))? .borrow(); let ic_id = *device.ic.as_ref().ok_or(VMError::NoIC(id))?; - let mut ic = self + let ic = self .ics .get(&ic_id) .ok_or(VMError::UnknownIcId(ic_id))? .borrow_mut(); let new_prog = interpreter::Program::from_code_with_invalid(code); - ic.program = new_prog; - ic.ip = 0; - ic.code = code.to_string(); + ic.program.replace(new_prog); + ic.code.replace(code.to_string()); Ok(true) } @@ -363,8 +359,8 @@ impl VM { .get(&ic_id) .ok_or(VMError::UnknownIcId(ic_id))? .clone(); - ic.borrow_mut().ic = 0; - let result = ic.borrow_mut().step(self, advance_ip_on_err)?; + ic.borrow().ic.replace(0); + let result = ic.borrow().step(self, advance_ip_on_err)?; Ok(result) } @@ -378,21 +374,23 @@ impl VM { .get(&ic_id) .ok_or(VMError::UnknownIcId(ic_id))? .clone(); - ic.borrow_mut().ic = 0; + ic.borrow().ic.replace(0); self.set_modified(id); for _i in 0..128 { - if let Err(err) = ic.borrow_mut().step(self, ignore_errors) { + if let Err(err) = ic.borrow().step(self, ignore_errors) { if !ignore_errors { return Err(err.into()); } } - if let interpreter::ICState::Yield = ic.borrow().state { + if let interpreter::ICState::Yield = *ic.borrow().state.borrow() { return Ok(false); - } else if let interpreter::ICState::Sleep(_then, _sleep_for) = ic.borrow().state { + } else if let interpreter::ICState::Sleep(_then, _sleep_for) = + *ic.borrow().state.borrow() + { return Ok(false); } } - ic.borrow_mut().state = interpreter::ICState::Yield; + ic.borrow().state.replace(interpreter::ICState::Yield); Ok(true) } @@ -408,8 +406,8 @@ impl VM { .get(&ic_id) .ok_or(VMError::UnknownIcId(ic_id))? .clone(); - ic.borrow_mut().ic = 0; - ic.borrow_mut().reset(); + ic.borrow().ic.replace(0); + ic.borrow().reset(); Ok(true) } @@ -506,7 +504,7 @@ impl VM { let Some(ic_id) = device.borrow().ic else { return Err(VMError::NoIC(id)); }; - self.ics.get(&ic_id).unwrap().borrow_mut().pins[pin] = val; + self.ics.get(&ic_id).unwrap().borrow().pins.borrow_mut()[pin] = val; Ok(true) } } diff --git a/ic10emu_wasm/src/lib.rs b/ic10emu_wasm/src/lib.rs index 703501d..5c851c4 100644 --- a/ic10emu_wasm/src/lib.rs +++ b/ic10emu_wasm/src/lib.rs @@ -116,7 +116,7 @@ impl DeviceRef { .borrow() .ics .get(ic) - .map(|ic| ic.as_ref().borrow().ip) + .map(|ic| ic.as_ref().borrow().ip()) }) } @@ -127,7 +127,7 @@ impl DeviceRef { .borrow() .ics .get(ic) - .map(|ic| ic.as_ref().borrow().ic) + .map(|ic| ic.as_ref().borrow().ic.get()) }) } @@ -138,7 +138,7 @@ impl DeviceRef { .borrow() .ics .get(ic) - .map(|ic| Stack(ic.as_ref().borrow().stack)) + .map(|ic| Stack(*ic.as_ref().borrow().stack.borrow())) }) } @@ -149,44 +149,44 @@ impl DeviceRef { .borrow() .ics .get(ic) - .map(|ic| Registers(ic.as_ref().borrow().registers)) + .map(|ic| Registers(*ic.as_ref().borrow().registers.borrow())) }) } #[wasm_bindgen(getter, js_name = "aliases", skip_typescript)] pub fn ic_aliases(&self) -> JsValue { - serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| { + let aliases = &self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) - .map(|ic| ic.as_ref().borrow().aliases.clone()) - })) - .unwrap() + .map(|ic| ic.as_ref().borrow().aliases.borrow().clone()) + }); + serde_wasm_bindgen::to_value(aliases).unwrap() } #[wasm_bindgen(getter, js_name = "defines", skip_typescript)] pub fn ic_defines(&self) -> JsValue { - serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| { + let defines = &self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) - .map(|ic| ic.as_ref().borrow().defines.clone()) - })) - .unwrap() + .map(|ic| ic.as_ref().borrow().defines.borrow().clone()) + }); + serde_wasm_bindgen::to_value(defines).unwrap() } #[wasm_bindgen(getter, js_name = "pins", skip_typescript)] pub fn ic_pins(&self) -> JsValue { - serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| { + let pins = &self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) - .map(|ic| ic.as_ref().borrow().pins) - })) - .unwrap() + .map(|ic| *ic.as_ref().borrow().pins.borrow()) + }); + serde_wasm_bindgen::to_value(pins).unwrap() } #[wasm_bindgen(getter, js_name = "state")] @@ -202,19 +202,19 @@ impl DeviceRef { .get(ic) .map(|ic| ic.borrow().state.clone()) }) - .map(|state| state.to_string()) + .map(|state| state.borrow().to_string()) } #[wasm_bindgen(getter, js_name = "program", skip_typescript)] pub fn ic_program(&self) -> JsValue { - serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| { + let prog = &self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) - .map(|ic| ic.borrow().program.clone()) - })) - .unwrap() + .map(|ic| ic.borrow().program.borrow().clone()) + }); + serde_wasm_bindgen::to_value(prog).unwrap() } #[wasm_bindgen(getter, js_name = "code")] @@ -224,7 +224,7 @@ impl DeviceRef { .borrow() .ics .get(ic) - .map(|ic| ic.borrow().code.clone()) + .map(|ic| ic.borrow().code.borrow().clone()) }) } diff --git a/ic10emu_wasm/src/types.ts b/ic10emu_wasm/src/types.ts index db87feb..12c659b 100644 --- a/ic10emu_wasm/src/types.ts +++ b/ic10emu_wasm/src/types.ts @@ -59,8 +59,8 @@ export type OperandReagentMode = { readonly ReagentMode: string }; export type Identifier = { readonly Identifier: { name: string } }; export type NumberFloat = { readonly Float: number }; -export type NumberBinary = { readonly Binary: number }; -export type NumberHexadecimal = { readonly Hexadecimal: number }; +export type NumberBinary = { readonly Binary: BigInt }; +export type NumberHexadecimal = { readonly Hexadecimal: BigInt }; export type NumberConstant = { readonly Constant: number }; export type NumberString = { readonly String: string }; export type NumberEnum = { readonly Enum: number }; diff --git a/www/package.json b/www/package.json index d137442..d019d1e 100644 --- a/www/package.json +++ b/www/package.json @@ -1,6 +1,6 @@ { "name": "ic10emu", - "version": "0.2.0", + "version": "0.2.1", "description": "an IC10 emulator for IC10 mips from Stationeers", "main": "index.js", "scripts": { diff --git a/www/src/ts/app/welcome.ts b/www/src/ts/app/welcome.ts index b93faae..7b6b9a3 100644 --- a/www/src/ts/app/welcome.ts +++ b/www/src/ts/app/welcome.ts @@ -54,7 +54,7 @@ export class AppWelcome extends BaseElement { render() { return html` - +
Hey there!

Looks like there have been some updates since you've last visit.


diff --git a/www/src/ts/components/base.ts b/www/src/ts/components/base.ts index 3f40530..795cd41 100644 --- a/www/src/ts/components/base.ts +++ b/www/src/ts/components/base.ts @@ -31,6 +31,10 @@ export const defaultCss = [ .flex-g { flex-grow: 1; } + sl-divider:not([vertical]) { + border-top-width: var(--width); + border-color: var(--color); + } `, ];