1 #!/usr/bin/env python 2 3 """ 4 A native function library for a really simple virtual processor. 5 NOTE: Ultimately, this should only implement only operations at the lowest 6 NOTE: possible level, with generated native methods providing the functionality 7 NOTE: instead. 8 9 Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk> 10 11 This program is free software; you can redistribute it and/or modify it under 12 the terms of the GNU General Public License as published by the Free Software 13 Foundation; either version 3 of the License, or (at your option) any later 14 version. 15 16 This program is distributed in the hope that it will be useful, but WITHOUT 17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 18 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 19 details. 20 21 You should have received a copy of the GNU General Public License along with 22 this program. If not, see <http://www.gnu.org/licenses/>. 23 """ 24 25 from micropython.program import DataValue 26 import operator 27 28 class Library: 29 30 "Native function implementations." 31 32 # NOTE: These attributes need changing if the instance layout changes. 33 34 instance_template_size = instance_data_offset = 1 35 instance_size = instance_template_size + 1 36 fragment_data_offset = 1 37 38 def __init__(self, machine, constants): 39 40 """ 41 Initialise the library with the 'machine' and the 'constants' addresses 42 dictionary. 43 """ 44 45 self.machine = machine 46 self.constants = constants 47 48 # Native class constants. 49 50 self.int_class, self.int_instance = self._get_builtin_class_and_template("int") 51 self.list_class, self.list_instance = self._get_builtin_class_and_template("list") 52 self.index_error, self.index_error_instance = self._get_builtin_class_and_template("IndexError") 53 self.str_class, self.str_instance = self._get_builtin_class_and_template("basestring") 54 self.accessor_class, self.accessor_instance = self._get_builtin_class_and_template("_accessor") 55 56 self.tuple_class = self.machine.tuple_class 57 self.tuple_instance = self.machine.tuple_instance 58 59 self.attr_error_instance = self.machine.attr_error_instance 60 self.type_error_instance = self.machine.type_error_instance 61 62 def _get_builtin_class_and_template(self, name): 63 cls = self.machine._get_class("__builtins__", name) 64 if cls is not None: 65 return cls.location, cls.instance_template_location 66 else: 67 return None, None 68 69 def _check_index(self, pos, nelements): 70 return pos >= 0 and pos < nelements 71 72 # Native functionality. 73 74 def builtins_no_op(self): 75 pass 76 77 def native_int_arithmetic_op(self, op): 78 left = self.machine.load_from_frame(0) 79 left_data = left.ref + self.instance_data_offset 80 right = self.machine.load_from_frame(1) 81 right_data = right.ref + self.instance_data_offset 82 83 # Make a new object. 84 85 addr = self.machine._MakeObject(self.instance_size, self.int_instance) 86 87 # Store the result. 88 # NOTE: The data is considered ready to use. 89 90 self.machine.save(addr + self.instance_data_offset, op(self.machine.load(left_data), self.machine.load(right_data))) 91 92 # Return the new object. 93 # Introduce object as context for the new object. 94 95 self.machine.LoadImmediate(addr, "working_context") 96 self.machine.LoadImmediate(addr, "working") 97 98 def native_logical_op(self, op): 99 left = self.machine.load_from_frame(0) 100 left_data = left.ref + self.instance_data_offset 101 right = self.machine.load_from_frame(1) 102 right_data = right.ref + self.instance_data_offset 103 104 # Test the data. 105 # NOTE: The data is considered ready to use. 106 107 if op(self.machine.load(left_data), self.machine.load(right_data)): 108 self.machine.LoadImmediate(self.constants[True], "working_context") 109 self.machine.LoadImmediate(self.constants[True], "working") 110 else: 111 self.machine.LoadImmediate(self.constants[False], "working_context") 112 self.machine.LoadImmediate(self.constants[False], "working") 113 114 # Operators. 115 # Although this takes a short-cut by using the operator module, testing is 116 # still performed on the operands to ensure that they qualify for these 117 # native operations. 118 119 def native_int_add(self): 120 return self.native_int_arithmetic_op(operator.add) 121 122 def native_int_sub(self): 123 return self.native_int_arithmetic_op(operator.sub) 124 125 def native_int_pow(self): 126 return self.native_int_arithmetic_op(operator.pow) 127 128 def native_int_and(self): 129 return self.native_int_arithmetic_op(operator.and_) 130 131 def native_int_or(self): 132 return self.native_int_arithmetic_op(operator.or_) 133 134 def native_int_lt(self): 135 return self.native_logical_op(operator.lt) 136 137 def native_int_gt(self): 138 return self.native_logical_op(operator.gt) 139 140 def native_int_eq(self): 141 return self.native_logical_op(operator.eq) 142 143 def native_str_lt(self): 144 return self.native_logical_op(operator.lt) 145 146 def native_str_gt(self): 147 return self.native_logical_op(operator.gt) 148 149 def native_str_eq(self): 150 return self.native_logical_op(operator.eq) 151 152 # Specific operator methods. 153 154 def builtins_int_neg(self): 155 left = self.machine.load_from_frame(0) 156 left_data = left.ref + self.instance_data_offset 157 158 # Make a new object. 159 160 addr = self.machine._MakeObject(self.instance_size, self.int_instance) 161 162 # Store the result. 163 # NOTE: The data is considered ready to use. 164 165 self.machine.save(addr + self.instance_data_offset, -self.machine.load(left_data)) 166 167 # Return the new object. 168 # Introduce object as context for the new object. 169 170 self.machine.LoadImmediate(addr, "working_context") 171 self.machine.LoadImmediate(addr, "working") 172 173 # Various built-in methods. 174 175 def builtins_list_new(self): 176 list_value = self.machine.load_from_frame(0) 177 list_addr = list_value.ref + self.instance_data_offset 178 179 # Make a new sequence. 180 # NOTE: Using an arbitrary size. 181 182 new_fragment = self.machine._MakeFragment(self.fragment_data_offset, 5) # include the header 183 184 # Complete the list instance by saving the fragment reference. 185 # NOTE: This requires an attribute in the list structure. 186 187 self.machine.save(list_addr, DataValue(None, new_fragment)) 188 189 def builtins_list_get_single_item(self): 190 obj_value = self.machine.load_from_frame(0) 191 fragment_member = obj_value.ref + self.instance_data_offset 192 item_value = self.machine.load_from_frame(1) 193 item_pos_member = item_value.ref + self.instance_data_offset 194 195 # Get the fragment address. 196 197 fragment = self.machine.load(fragment_member) 198 199 # Get the fragment header. 200 201 header = self.machine.load(fragment.ref) 202 nelements = header.occupied_size - self.fragment_data_offset 203 204 # Get the item position. 205 206 item_pos = self.machine.load(item_pos_member) 207 208 if not self._check_index(item_pos, nelements): 209 self.machine.LoadImmediate(self.machine._MakeObject(self.instance_size, self.index_error_instance), "exception") 210 return self.machine.RaiseException() 211 212 # Get the item itself. 213 214 data = self.machine.load(fragment.ref + self.fragment_data_offset + item_pos) 215 self.machine.LoadImmediate(data.context, "working_context") 216 self.machine.LoadImmediate(data.ref, "working") 217 218 def builtins_list_len(self): 219 obj_value = self.machine.load_from_frame(0) 220 fragment_member = obj_value.ref + self.instance_data_offset 221 222 # Get the fragment address. 223 224 fragment = self.machine.load(fragment_member) 225 226 # Get the fragment header. 227 228 header = self.machine.load(fragment.ref) 229 nelements = header.occupied_size - self.fragment_data_offset 230 231 # Make a new object. 232 233 addr = self.machine._MakeObject(self.instance_size, self.int_instance) 234 235 # Store the result. 236 # NOTE: The data is considered ready to use. 237 238 self.machine.save(addr + self.instance_data_offset, nelements) 239 240 # Return the new object. 241 # Introduce object as context for the new object. 242 243 self.machine.LoadImmediate(addr, "working_context") 244 self.machine.LoadImmediate(addr, "working") 245 246 def builtins_list_append(self): 247 obj_value = self.machine.load_from_frame(0) 248 fragment_member = obj_value.ref + self.instance_data_offset 249 arg_value = self.machine.load_from_frame(1) 250 251 # Get the fragment address. 252 253 fragment = self.machine.load(fragment_member) 254 255 # Get the fragment header. 256 257 header = self.machine.load(fragment.ref) 258 259 # Attempt to add the reference. 260 261 if header.occupied_size < header.allocated_size: 262 self.machine.save(fragment.ref + header.occupied_size, arg_value) 263 header.occupied_size += 1 264 else: 265 266 # Make a new fragment, maintaining more space than currently 267 # occupied in order to avoid reallocation. 268 269 new_fragment = self.machine._MakeFragment(header.occupied_size + 1, header.occupied_size * 2) 270 271 # Copy existing elements. 272 273 for i in range(self.fragment_data_offset, header.occupied_size): 274 self.machine.save(new_fragment + i, self.machine.load(fragment.ref + i)) 275 276 self.machine.save(new_fragment + header.occupied_size, arg_value) 277 278 # Set the new fragment in the object. 279 # NOTE: The old fragment could be deallocated. 280 281 self.machine.save(fragment_member, DataValue(None, new_fragment)) 282 283 def builtins_tuple_new(self): 284 285 # Get the sequence address. 286 # The first argument should be empty since this function acts as an 287 # instantiator, and in instantiators the first argument is reserved so 288 # that it can be filled in for the call to an initialiser without 289 # allocating a new frame. 290 291 obj_value = self.machine.load_from_frame(1) 292 return self._builtins_tuple(obj_value) 293 294 def builtins_tuple(self): 295 296 # Get the sequence address. 297 298 obj_value = self.machine.load_from_frame(0) 299 return self._builtins_tuple(obj_value) 300 301 def _builtins_tuple(self, obj_value): 302 fragment_member = obj_value.ref + self.instance_data_offset 303 304 if self.machine._CheckInstance(obj_value.ref, self.tuple_class): 305 self.machine.LoadImmediate(obj_value.context, "working_context") 306 self.machine.LoadImmediate(obj_value.ref, "working") 307 return 308 309 # Reject non-list, non-tuple types. 310 # NOTE: This should probably accept any sequence. 311 312 elif not self.machine._CheckInstance(obj_value.ref, self.list_class): 313 self.machine.LoadImmediate(self.machine._MakeObject(self.instance_size, self.type_error_instance), "exception") 314 return self.machine.RaiseException() 315 316 # Get the fragment address. 317 318 fragment = self.machine.load(fragment_member) 319 320 # Get the fragment header. 321 322 header = self.machine.load(fragment.ref) 323 324 # Make a new object. 325 326 addr = self.machine._MakeObject(self.instance_data_offset + header.occupied_size - self.fragment_data_offset, self.tuple_instance) 327 328 # Copy the fragment contents into the tuple. 329 # NOTE: This might be done by repurposing the fragment in some situations. 330 331 for i in range(self.fragment_data_offset, header.occupied_size): 332 self.machine.save(addr + self.instance_data_offset + i - self.fragment_data_offset, self.machine.load(fragment.ref + i)) 333 334 # Return the new object. 335 # Introduce object as context for the new object. 336 337 self.machine.LoadImmediate(addr, "working_context") 338 self.machine.LoadImmediate(addr, "working") 339 340 def builtins_tuple_len(self): 341 obj_value = self.machine.load_from_frame(0) 342 343 # Get the header. 344 345 header = self.machine.load(obj_value.ref) 346 nelements = header.size - self.instance_data_offset 347 348 # Make a new object. 349 350 addr = self.machine._MakeObject(self.instance_size, self.int_instance) 351 352 # Store the result. 353 # NOTE: The data is considered ready to use. 354 355 self.machine.save(addr + self.instance_data_offset, nelements) 356 357 # Return the new object. 358 # Introduce object as context for the new object. 359 360 self.machine.LoadImmediate(addr, "working_context") 361 self.machine.LoadImmediate(addr, "working") 362 363 def builtins_tuple_get_single_item(self): 364 obj_value = self.machine.load_from_frame(0) 365 fragment_member = obj_value.ref + self.instance_data_offset 366 item_value = self.machine.load_from_frame(1) 367 item_pos_member = item_value.ref + self.instance_data_offset 368 369 # Get the header. 370 371 header = self.machine.load(obj_value.ref) 372 nelements = header.size - self.instance_data_offset 373 374 # NOTE: Assume single location for data and header. 375 376 item_pos = self.machine.load(item_pos_member) 377 378 if not self._check_index(item_pos, nelements): 379 self.machine.LoadImmediate(self.machine._MakeObject(self.instance_size, self.index_error_instance), "exception") 380 return self.machine.RaiseException() 381 382 # Get the item. 383 384 data = self.machine.load(fragment_member + item_pos) 385 self.machine.LoadImmediate(data.context, "working_context") 386 self.machine.LoadImmediate(data.ref, "working") 387 388 def builtins_getattr(self): 389 obj_value = self.machine.load_from_frame(0) 390 name_value = self.machine.load_from_frame(1) 391 index_member = name_value.ref + self.instance_data_offset + 1 392 393 if not self.machine._CheckInstance(name_value.ref, self.accessor_class): 394 self.machine.LoadImmediate(self.machine._MakeObject(self.instance_size, self.attr_error_instance), "exception") 395 return self.machine.RaiseException() 396 397 # Get the object table index from the name. It is a bare integer, not a reference. 398 399 index = self.machine.load(index_member) 400 401 # NOTE: This is very much like LoadAttrIndexContextCond. 402 403 data = self.machine.load(obj_value.ref) 404 element = self.machine.load(self.machine.registers["objlist"] + data.classcode + index) 405 406 if element is not None: 407 attr_index, static_attr, offset = element 408 if attr_index == index: 409 if static_attr: 410 loaded_data = self.machine.load(offset) # offset is address of class/module attribute 411 if data.attrcode is not None: # absent attrcode == class/module 412 loaded_data = self.machine._LoadAddressContextCond(loaded_data.context, loaded_data.ref, obj_value.ref) 413 else: 414 loaded_data = self.machine.load(obj_value.ref + offset) 415 self.machine.LoadImmediate(loaded_data.context, "working_context") 416 self.machine.LoadImmediate(loaded_data.ref, "working") 417 return 418 419 self.machine.LoadImmediate(self.machine._MakeObject(self.instance_size, self.attr_error_instance), "exception") 420 return self.machine.RaiseException() 421 422 def builtins_isinstance(self): 423 obj_value = self.machine.load_from_frame(0) 424 cls_value = self.machine.load_from_frame(1) 425 426 if self.machine._CheckInstance(obj_value.ref, cls_value.ref): 427 self.machine.LoadImmediate(self.constants[True], "working_context") 428 self.machine.LoadImmediate(self.constants[True], "working") 429 else: 430 self.machine.LoadImmediate(self.constants[False], "working_context") 431 self.machine.LoadImmediate(self.constants[False], "working") 432 433 def builtins_print(self): 434 # NOTE: Do nothing for now. 435 pass 436 437 def builtins_printnl(self): 438 # NOTE: Do nothing for now. 439 pass 440 441 native_functions = { 442 443 # Native method implementations: 444 445 "native._int_add" : native_int_add, 446 "native._int_sub" : native_int_sub, 447 "native._int_pow" : native_int_pow, 448 "native._int_and" : native_int_and, 449 "native._int_or" : native_int_or, 450 "native._int_lt" : native_int_lt, 451 "native._int_gt" : native_int_gt, 452 "native._int_eq" : native_int_eq, 453 "native._str_lt" : native_str_lt, 454 "native._str_gt" : native_str_gt, 455 "native._str_eq" : native_str_eq, 456 "__builtins__.int.__neg__" : builtins_int_neg, 457 "__builtins__.list.__get_single_item__" : builtins_list_get_single_item, 458 "__builtins__.list.__len__" : builtins_list_len, 459 "__builtins__.list.append" : builtins_list_append, 460 "__builtins__.tuple" : builtins_tuple_new, 461 "__builtins__.tuple.__len__" : builtins_tuple_len, 462 "__builtins__.tuple.__get_single_item__" : builtins_tuple_get_single_item, 463 464 # Native initialisers: 465 466 "__builtins__.BaseException.__init__" : builtins_no_op, # NOTE: To be made distinct, potentially in the builtins module. 467 468 # Native functions: 469 470 "__builtins__._getattr" : builtins_getattr, 471 472 # Native instantiator helpers: 473 474 "__builtins__.list.__new__" : builtins_list_new, 475 476 # Native helper functions: 477 478 "__builtins__._isinstance" : builtins_isinstance, 479 "__builtins__._print" : builtins_print, 480 "__builtins__._printnl" : builtins_printnl, 481 "__builtins__._tuple" : builtins_tuple, 482 } 483 484 # vim: tabstop=4 expandtab shiftwidth=4