tis_100/
spec.rs

1//! Constructs for specifying TIS-100 puzzles.
2
3use std::fs::File;
4use std::path::Path;
5use vec_map::VecMap;
6use hlua::{Lua, LuaTable};
7use hlua::functions_read::LuaFunction;
8use save::Save;
9use node::{Node, TestNode, BasicExecutionNode, DamagedExecutionNode, StackMemoryNode, TestInputNode, TestOutputNode, TestImageNode};
10use machine::{NUM_NODES, INPUT_0, Tis100};
11
12/// Used to seed the Lua random number generator.
13const SEED_RANDOM_EXEC: &'static str = "math.randomseed(os.time())";
14
15/// Constants for extracting the TIS-100 layout from the spec.
16const LAYOUT_TABLE: &'static str = "layout";
17const LAYOUT_FN: &'static str = "get_layout";
18const LAYOUT_FN_EXEC: &'static str = "layout = get_layout()";
19
20/// Constants for extracting the TIS-100 test streams from the spec.
21const STREAMS_TABLE: &'static str = "streams";
22const STREAMS_FN: &'static str = "get_streams";
23const STREAMS_FN_EXEC: &'static str = "streams = get_streams()";
24const STREAM_KIND_IDX: u32 = 1;
25const STREAM_NAME_IDX: u32 = 2;
26const STREAM_NODE_IDX: u32 = 3;
27const STREAM_DATA_IDX: u32 = 4;
28
29/// Enumerations for the stream kinds.
30const STREAM_INPUT: u32 = 0;
31const STREAM_OUTPUT: u32 = 1;
32const STREAM_IMAGE: u32 = 2;
33
34/// Enumerations for the tile kinds.
35const TILE_COMPUTE: u32 = 0;
36const TILE_MEMORY: u32 = 1;
37const TILE_DAMAGED: u32 = 2;
38
39/// The different kinds of nodes available to the spec.
40#[derive(Debug, PartialEq, Eq, Copy, Clone)]
41enum Tile {
42    Compute,
43    Memory,
44    Damaged,
45}
46
47use self::Tile::*;
48
49/// Intermediate representation of a test stream.
50#[derive(Debug)]
51struct Stream {
52    kind: StreamKind,
53    name: String,
54    node: usize,
55    data: Vec<isize>
56}
57
58/// The different kinds of streams available to the spec.
59#[derive(Debug, PartialEq, Eq, Copy, Clone)]
60enum StreamKind {
61    Input,
62    Output,
63    Image,
64}
65
66use self::StreamKind::*;
67
68/// An error that can be returned while loading a spec.
69pub enum SpecError {
70    SeedRandomFailed,
71    ReadFileFailed,
72    GetLayoutFailed,
73    GetStreamsFailed,
74}
75
76use self::SpecError::*;
77
78/// A specification for a TIS-100 puzzle. Specifications are Lua files that configure the layout,
79/// inputs, and outputs for the TIS-100. At a minimum, a specification must provide the
80/// `get_layout` and `get_streams` functions.
81pub struct Spec {
82    save: Save,
83    layout: Vec<Tile>,
84    streams: Vec<Stream>,
85}
86
87impl Spec {
88    /// Load a `Spec` from a file.
89    pub fn from_file(filename: &str, save: Save) -> Result<Spec, SpecError> {
90        // Prepare the Lua context.
91        let mut lua = Lua::new();
92        lua.openlibs();
93
94        if let Err(_) = lua.execute::<()>(SEED_RANDOM_EXEC) {
95            return Err(SeedRandomFailed);
96        }
97
98        lua.set("STREAM_INPUT", STREAM_INPUT);
99        lua.set("STREAM_OUTPUT", STREAM_OUTPUT);
100        lua.set("STREAM_IMAGE", STREAM_IMAGE);
101        lua.set("TILE_COMPUTE", TILE_COMPUTE);
102        lua.set("TILE_MEMORY", TILE_MEMORY);
103        lua.set("TILE_DAMAGED", TILE_DAMAGED);
104
105        // Read and execute the spec file.
106        if let Ok(file) = File::open(&Path::new(filename)) {
107            if let Err(_) = lua.execute_from_reader::<(), _>(file) {
108                return Err(ReadFileFailed);
109            }
110        } else {
111            return Err(ReadFileFailed);
112        }
113
114        // Make sure that get_layout exists and can be called.
115        if let None = lua.get::<LuaFunction<_>, _>(LAYOUT_FN) {
116            return Err(GetLayoutFailed);
117        }
118
119        // FIXME: Figure out how to return a LuaTable from a LuaFunction call.
120        //        For now we call the get_layout function and save the result table to a variable.
121        if let Err(_) = lua.execute::<()>(LAYOUT_FN_EXEC) {
122            return Err(GetLayoutFailed);
123        }
124
125        // Read the layout from Lua.
126        let mut layout = Vec::new();
127        if let Some(mut layout_table) = lua.get::<LuaTable<_>, _>(LAYOUT_TABLE) {
128            for (_, v) in layout_table.iter::<u32, u32>().filter_map(|e| e) {
129                match v {
130                    TILE_COMPUTE => layout.push(Compute),
131                    TILE_MEMORY => layout.push(Memory),
132                    TILE_DAMAGED => layout.push(Damaged),
133                    _ => return Err(GetLayoutFailed),
134                };
135            }
136
137            if layout.len() != NUM_NODES {
138                return Err(GetLayoutFailed);
139            }
140        }
141
142        // Make sure that get_streams exists and can be called.
143        if let None = lua.get::<LuaFunction<_>, _>(STREAMS_FN) {
144            return Err(GetStreamsFailed);
145        }
146
147        // FIXME: Figure out how to return a LuaTable from a LuaFunction call.
148        //        For now we call the get_streams function and save the result table to a variable.
149        if let Err(_) = lua.execute::<()>(STREAMS_FN_EXEC) {
150            return Err(GetStreamsFailed);
151        }
152
153        // Read the streams from Lua.
154        let mut streams = Vec::new();
155        if let Some(mut streams_table) = lua.get::<LuaTable<_>, _>(STREAMS_TABLE) {
156            // FIXME: Figure out how to iterate over a table of tables.
157            //        For now, we can only have 8 total inputs and outputs, so just try each index.
158            for index in 1..9 {
159                // Each stream is a table with the following format:
160                // 1: kind (input, output, image)
161                // 2: name
162                // 3: node the stream is connected to
163                // 4: data stream
164                if let Some(mut stream_table) = streams_table.get::<LuaTable<_>, _>(index) {
165                    let kind = match stream_table.get::<u32, _>(STREAM_KIND_IDX) {
166                        Some(STREAM_INPUT) => Input,
167                        Some(STREAM_OUTPUT) => Output,
168                        Some(STREAM_IMAGE) => Image,
169                        _ => return Err(GetStreamsFailed),
170                    };
171
172                    let name = match stream_table.get::<String, _>(STREAM_NAME_IDX) {
173                        Some(name) => name,
174                        None => return Err(GetStreamsFailed),
175                    };
176
177                    let node = match stream_table.get::<u32, _>(STREAM_NODE_IDX) {
178                        Some(node) => node as usize,
179                        None => return Err(GetStreamsFailed),
180                    };
181
182                    let data = match stream_table.get::<LuaTable<_>, _>(STREAM_DATA_IDX) {
183                        Some(mut data_table) => {
184                            let mut data = Vec::new();
185                            for (_, v) in data_table.iter::<u32, i32>().filter_map(|e| e) {
186                                data.push(v as isize);
187                            }
188                            data
189                        },
190                        None => return Err(GetStreamsFailed),
191                    };
192
193                    streams.push(Stream {
194                        kind: kind,
195                        name: name,
196                        node: node,
197                        data: data,
198                    });
199                } else {
200                    break;
201                }
202            }
203        }
204
205        Ok(Spec {
206            save: save,
207            layout: layout,
208            streams: streams,
209        })
210    }
211
212    /// Configure a `Tis100` instance using the spec.
213    pub fn setup(&mut self, cpu: &mut Tis100) {
214        for (index, &tile) in self.layout.iter().enumerate() {
215            let node: Box<Node> = match tile {
216                Compute => match self.save.get(index) {
217                    Some(prog) => Box::new(BasicExecutionNode::with_program(prog.clone())),
218                    None => Box::new(BasicExecutionNode::new()),
219                },
220                Memory => Box::new(StackMemoryNode::new()),
221                Damaged => Box::new(DamagedExecutionNode),
222            };
223
224            cpu.add_node(index, node);
225        }
226
227        // Test inputs are added as regular nodes since we probably don't need to interact with
228        // them after they are set up.
229        for stream in self.streams.iter() {
230            if let Input = stream.kind {
231                cpu.add_node(stream.node + INPUT_0, Box::new(TestInputNode::with_data(&stream.data)));
232            }
233        }
234    }
235
236    /// Get the test output nodes used by the spec.
237    pub fn tests(&self) -> VecMap<Box<TestNode>> {
238        let mut tests: VecMap<Box<TestNode>> = VecMap::new();
239
240        for stream in self.streams.iter() {
241            match stream.kind {
242                Input => (),
243                Output => {
244                    tests.insert(stream.node, Box::new(TestOutputNode::with_data(&stream.data)));
245                },
246                Image => {
247                    tests.insert(stream.node, Box::new(TestImageNode::with_data(&stream.data, 30, 18)));
248                },
249            };
250        }
251
252        tests
253    }
254}