about summary refs log blame commit diff stats
path: root/021check_instruction.cc
blob: a32c6178c6a3d69f319d38df4e4c63b99f9c1810 (plain) (tree)
1
2
3
4
5
6
7
8
9


                                                                              





                                                                            
 
                      
                                                      

       
                                                
                                                                                               
                                              
                                                          
                                                   
                                
                             
                                
                  
                                                           
                                                                                                                        

                
                                                         
                                                                              
                                                                                                                                                                                                    




                                             
                                    
                

                                    






                                     
                     
          
                         
 
                                                            

                                            
                     
          
                        
 
                                                                 

                                              
                     
          
                                   
 
                                                                 

                                              
                     
          
                          
 
                                                                   
 
                                           
          

                                 



                              







                                                                       
                                           
          

                       



                             

                                              
          
                  
                      
 
                                                               
 
       
                                 

                                                              

                                                                
                                      


               
                                                          

                                                                                



                                         
                                     

                                                   

                                                                        
                                                            
   
                                        

 
                                   

                                                                     
                                              
                                                                                     

                                                                                
                                   
                                


                                                  

 
                                                                       
                              
                        
                                               
                                           
                   


                                                         





                                                                                                 

 











                                                      







                                                                            

                                        

 

           



                                   

                                          

                             



                                         



                                                                 
                                                         

 

                                            

                               



                                           



                                                                 
                                                           
 
 

                                            

                                  
                                  


                                                       
                                      




                                                                                        
                                           
                            
                                  
                      
                                                      
                                     
   
                                                      

 

                                              






                                                       

 
                                      

                              






                                          
 
//: Introduce a new transform to perform various checks in instructions before
//: we start running them. It'll be extensible, so that we can add checks for
//: new recipes as we extend 'run' to support them.
//:
//: Doing checking in a separate part complicates things, because the values
//: of variables in memory and the processor (current_recipe_name,
//: current_instruction) aren't available at checking time. If I had a more
//: sophisticated layer system I'd introduce the simpler version first and
//: transform it in a separate layer or set of layers.

:(before "End Checks")
Transform.push_back(check_instruction);  // idempotent

:(code)
void check_instruction(const recipe_ordinal r) {
  trace(9991, "transform") << "--- perform checks for recipe " << get(Recipe, r).name << end();
  map<string, vector<type_ordinal> > metadata;
  for (int i = 0;  i < SIZE(get(Recipe, r).steps);  ++i) {
    instruction& inst = get(Recipe, r).steps.at(i);
    if (inst.is_label) continue;
    switch (inst.operation) {
      // Primitive Recipe Checks
      case COPY: {
        if (SIZE(inst.products) > SIZE(inst.ingredients)) {
          raise << maybe(get(Recipe, r).name) << "too many products in '" << to_original_string(inst) << "'\n" << end();
          break;
        }
        for (int i = 0;  i < SIZE(inst.products);  ++i) {
          if (!types_coercible(inst.products.at(i), inst.ingredients.at(i))) {
            raise << maybe(get(Recipe, r).name) << "can't copy '" << inst.ingredients.at(i).original_string << "' to '" << inst.products.at(i).original_string << "'; types don't match\n" << end();
            goto finish_checking_instruction;
          }
        }
        break;
      }
      // End Primitive Recipe Checks
      default: {
        // Defined Recipe Checks
        // End Defined Recipe Checks
      }
    }
    finish_checking_instruction:;
  }
}

:(scenario copy_checks_reagent_count)
% Hide_errors = true;
def main [
  1:num, 2:num <- copy 34
]
+error: main: too many products in '1:num, 2:num <- copy 34'

:(scenario write_scalar_to_array_disallowed)
% Hide_errors = true;
def main [
  1:array:num <- copy 34
]
+error: main: can't copy '34' to '1:array:num'; types don't match

:(scenario write_scalar_to_array_disallowed_2)
% Hide_errors = true;
def main [
  1:num, 2:array:num <- copy 34, 35
]
+error: main: can't copy '35' to '2:array:num'; types don't match

:(scenario write_scalar_to_address_disallowed)
% Hide_errors = true;
def main [
  1:address:num <- copy 34
]
+error: main: can't copy '34' to '1:address:num'; types don't match

:(scenario write_address_to_number_allowed)
def main [
  1:address:num <- copy 12/unsafe
  2:num <- copy 1:address:num
]
+mem: storing 12 in location 2
$error: 0

:(scenario write_address_to_character_disallowed)
% Hide_errors = true;
def main [
  1:address:num <- copy 12/unsafe
  2:char <- copy 1:address:num
]
+error: main: can't copy '1:address:num' to '2:char'; types don't match

:(scenario write_boolean_to_number_allowed)
def main [
  1:bool <- copy 1/true
  2:num <- copy 1:bool
]
+mem: storing 1 in location 2
$error: 0

:(scenario write_number_to_boolean_disallowed)
% Hide_errors = true;
def main [
  1:num <- copy 34
  2:bool <- copy 1:num
]
+error: main: can't copy '1:num' to '2:bool'; types don't match

:(code)
// types_match with some leniency
bool types_coercible(const reagent& to, const reagent& from) {
  if (types_match(to, from)) return true;
  if (is_mu_address(from) && is_real_mu_number(to)) return true;
  if (is_mu_boolean(from) && is_real_mu_number(to)) return true;
  // End types_coercible Special-cases
  return false;
}

bool types_match(const reagent& to, const reagent& from) {
  // to sidestep type-checking, use /unsafe in the source.
  // this will be highlighted in red inside vim. just for setting up some tests.
  if (is_unsafe(from)) return true;
  if (is_literal(from)) {
    if (is_mu_array(to)) return false;
    // End Matching Types For Literal(to)
    // allow writing 0 to any address
    if (is_mu_address(to)) return from.name == "0";
    if (!to.type) return false;
    if (to.type->atom && to.type->value == get(Type_ordinal, "boolean"))
      return from.name == "0" || from.name == "1";
    return size_of(to) == 1;  // literals are always scalars
  }
  return types_strictly_match(to, from);
}

//: copy arguments for later layers
bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) {
  // End Preprocess types_strictly_match(reagent to, reagent from)
  if (to.type == NULL) return false;  // error
  if (is_literal(from) && to.type->value == get(Type_ordinal, "number")) return true;
  // to sidestep type-checking, use /unsafe in the source.
  // this will be highlighted in red inside vim. just for setting up some tests.
  if (is_unsafe(from)) return true;
  // '_' never raises type error
  if (is_dummy(to)) return true;
  if (!to.type) return !from.type;
  return types_strictly_match(to.type, from.type);
}

bool types_strictly_match(const type_tree* to, const type_tree* from) {
  if (from == to) return true;
  if (!to) return false;
  if (!from) return to->atom && to->value == 0;
  if (from->atom != to->atom) return false;
  if (from->atom) {
    if (from->value == -1) return from->name == to->name;
    return from->value == to->value;
  }
  if (types_strictly_match(to->left, from->left) && types_strictly_match(to->right, from->right))
    return true;
  // fallback: (x) == x
  if (to->right == NULL && types_strictly_match(to->left, from)) return true;
  if (from->right == NULL && types_strictly_match(to, from->left)) return true;
  return false;
}

void test_unknown_type_does_not_match_unknown_type() {
  reagent a("a:foo");
  reagent b("b:bar");
  CHECK(!types_strictly_match(a, b));
}

void test_unknown_type_matches_itself() {
  reagent a("a:foo");
  reagent b("b:foo");
  CHECK(types_strictly_match(a, b));
}

void test_type_abbreviations_match_raw_types() {
  put(Type_abbreviations, "text", new_type_tree("address:array:character"));
  // a has type (address buffer (address array character))
  reagent a("a:address:buffer:text");
  expand_type_abbreviations(a.type);
  // b has type (address buffer address array character)
  reagent b("b:address:buffer:address:array:character");
  CHECK(types_strictly_match(a, b));
  delete Type_abbreviations["text"];
  put(Type_abbreviations, "text", NULL);
}

//: helpers

bool is_unsafe(const reagent& r) {
  return has_property(r, "unsafe");
}

bool is_mu_array(reagent/*copy*/ r) {
  // End Preprocess is_mu_array(reagent r)
  return is_mu_array(r.type);
}
bool is_mu_array(const type_tree* type) {
  if (!type) return false;
  if (is_literal(type)) return false;
  if (type->atom) return false;
  if (!type->left->atom) {
    raise << "invalid type " << to_string(type) << '\n' << end();
    return false;
  }
  return type->left->value == get(Type_ordinal, "array");
}

bool is_mu_address(reagent/*copy*/ r) {
  // End Preprocess is_mu_address(reagent r)
  return is_mu_address(r.type);
}
bool is_mu_address(const type_tree* type) {
  if (!type) return false;
  if (is_literal(type)) return false;
  if (type->atom) return false;
  if (!type->left->atom) {
    raise << "invalid type " << to_string(type) << '\n' << end();
    return false;
  }
  return type->left->value == get(Type_ordinal, "address");
}

bool is_mu_boolean(reagent/*copy*/ r) {
  // End Preprocess is_mu_boolean(reagent r)
  if (!r.type) return false;
  if (is_literal(r)) return false;
  if (!r.type->atom) return false;
  return r.type->value == get(Type_ordinal, "boolean");
}

bool is_mu_number(reagent/*copy*/ r) {
  if (is_mu_character(r.type)) return true;  // permit arithmetic on unicode code points
  return is_real_mu_number(r);
}

bool is_real_mu_number(reagent/*copy*/ r) {
  // End Preprocess is_mu_number(reagent r)
  if (!r.type) return false;
  if (!r.type->atom) return false;
  if (is_literal(r)) {
    return r.type->name == "literal-fractional-number"
        || r.type->name == "literal";
  }
  return r.type->value == get(Type_ordinal, "number");
}

bool is_mu_character(reagent/*copy*/ r) {
  // End Preprocess is_mu_character(reagent r)
  return is_mu_character(r.type);
}
bool is_mu_character(const type_tree* type) {
  if (!type) return false;
  if (!type->atom) return false;
  if (is_literal(type)) return false;
  return type->value == get(Type_ordinal, "character");
}

bool is_mu_scalar(reagent/*copy*/ r) {
  return is_mu_scalar(r.type);
}
bool is_mu_scalar(const type_tree* type) {
  if (!type) return false;
  if (is_mu_address(type)) return true;
  if (!type->atom) return false;
  if (is_literal(type))
    return type->name != "literal-string";
  return size_of(type) == 1;
}
span>add 2, 2 a <- multiply a, 3 ] ``` But it's easier to read in color: <img alt='code example' src='html/example1.png' width='188px'> Mu functions or 'recipes' are lists of instructions, one to a line. Each instruction operates on some *ingredients* and returns some *products*. ``` [products] <- instruction [ingredients] ``` Result and ingredient *reagents* have to be variables. But you can have any number of them. In particular you can have any number of products. For example, you can perform integer division as follows: ``` quotient:number, remainder:number <- divide-with-remainder 11, 3 ``` Each reagent can provide a name as well as its type separated by a colon. You only have to specify the type the first time you mention a name, but you can be more explicit if you choose. Types can be multiple words and even arbitrary trees, like: ```nim x:array:number:3 # x is an array of 3 numbers y:list:number # y is a list of numbers # without syntactic sugar {z: (map (address array character) (list number))} # map from string to list of numbers ``` Try out the program now: ```shell $ ./mu example1.mu $ ``` Not much to see yet, since it doesn't print anything. To print the result, try adding the instruction `$print a` to the recipe. --- Here's a second example, of a recipe that can take ingredients: <img alt='fahrenheit to celsius' src='html/f2c-1.png' width='426px'> Recipes can specify headers showing their expected ingredients and products, separated by `->` (unlike the `<-` in *calls*). Since mu is a low-level VM language, it provides extra control at the cost of verbosity. Using `local-scope`, you have explicit control over stack frames to isolate your recipes (in a type-safe manner; more on that below). One consequence: you have to explicitly `load-ingredients` after you set up the stack. An alternative syntax is what the above example is converted to internally: <img alt='fahrenheit to celsius desugared' src='html/f2c-2.png' width='426px'> The header gets dropped after checking types at call-sites, and after replacing `load-ingredients` with explicit instructions to load each ingredient separately, and to explicitly return products to the caller. After this translation recipes are once again just lists of instructions. This alternative syntax isn't just an implementation detail. I've actually found it easier to teach functions to non-programmers by starting with this syntax, so that they can visualize a pipe from caller to callee, and see the names of variables gradually get translated through the pipe. --- A third example, this time illustrating conditionals: <img alt='factorial example' src='html/factorial.png' width='330px'> In spite of how it looks, this is still just a list of instructions. Internally, the instructions `break` and `loop` get converted to `jump` instructions to after the enclosing `}` or `{`, respectively. Try out the factorial program now: ```shell $ ./mu factorial.mu result: 120 # factorial of 5 ``` You can also run its unit tests: ```shell $ ./mu test factorial.mu ``` Here's what one of the tests inside `factorial.mu` looks like: <img alt='test example' src='html/factorial-test.png' width='250px'> Every test conceptually spins up a really lightweight virtual machine, so you can do things like check the value of specific locations in memory. You can also print to screen and check that the screen contains what you expect at the end of a test. For example, `chessboard.mu` checks the initial position of a game of chess (delimiting the edges of the screen with periods): <img alt='screen test' src='html/chessboard-test.png' width='320px'> Similarly you can fake the keyboard to pretend someone typed something: ``` assume-keyboard [a2-a4] ``` As we add a file system, graphics, audio, network support and so on, we'll augment scenarios with corresponding abilities to use them inside tests. --- The name of a reagent is for humans, but what the computer needs to access it is its address. Mu maps names to addresses for you like in other languages, but in a more transparent, lightweight, hackable manner. This instruction: ```nim z:number <- add x:number, y:number ``` might turn into this: ```nim 3:number <- add 1:number, 2:number ``` You shouldn't rely on the specific address Mu chooses for a variable, but it will be unique (other variables won't clobber it) and consistent (all mentions of the name will map to the same address inside a recipe). Things get more complicated when your recipes call other recipes. Mu doesn't preserve uniqueness of addresses across recipes, so you need to organize your names into spaces. At the start of each recipe (like `factorial` above), set its *default space*: ```nim local-scope ``` or ```nim new-default-space ``` or ```nim default-space:address:array:location <- new location:type, 30/capacity ``` Without one of these lines, all variables in the recipe will be *global*, something you rarely want. (Luckily, this is also the sort of mistake that will be easily caught by tests.) *With* this line, all addresses in your recipe will by default refer to one of the (30, in the final case) slots inside this local space. (If you choose the last, most explicit option and need more than 30 slots, mu will complain asking you to increase capacity.) Spaces can do more than just implement local variables. You can string them together, pass them around, return them from functions, share them between parallel routines, and much else. However, any function receiving a space has to know the names and types of variables in it, so any instruction should always receive spaces created by the same function, no matter how many times it's run. (If you're familiar with lexical scope, this constraint is identical to it.) To string two spaces together, write one into slot 0 of the other. This instruction chains a space received from its caller: ```nim 0:address:array:location <- next-ingredient ``` Once you've chained spaces together, you can access variables in them by adding a 'space' property: ```nim 3:number/space:1 ``` This reagent is the number in slot 3 of the space chained in slot 0 of the default space. We usually call it slot 3 in the 'next space'. `/space:2` would be the next space of the next space, and so on. See `counters.mu` for an example of managing multiple accumulators at once without allowing them to clobber each other. This is a classic example of the sorts of things closures and objects are useful for in other languages. Spaces in Mu provide the same functionality. --- You can append arbitrary properties to reagents besides types and spaces. Just separate them with slashes. ```nim x:array:number:3/uninitialized y:string/tainted:yes z:list:number/assign-once:true/assigned:false ``` Most properties are meaningless to Mu, and it'll silently skip them when running, but they are fodder for *meta-programs* to check or modify your programs, a task other languages typically hide from their programmers. For example, where other programmers are restricted to the checks their type system permits and forces them to use, you'll learn to create new checks that make sense for your specific program. If it makes sense to perform different checks in different parts of your program, you'll be able to do that. You can imagine each reagent as a table, rows separated by slashes, columns within a row separated by colons. So the last example above would become something like this: ``` z : list : integer / assign-once : true / assigned : false ``` --- An alternative way to define factorial is by inserting *labels* and later inserting code at them. ```nim recipe factorial [ local-scope n:number <- next-ingredient { +base-case: } +recursive-case: ] after +base-case [ # if n=0 return 1 zero?:boolean <- equal n, 0 break-unless zero? reply 1 ] after +recursive-case [ # return n * factorial(n-1) x:number <- subtract n, 1 subresult:number <- factorial x result:number <- multiply subresult, n reply result ] ``` (You'll find this version in `tangle.mu`.) Any instruction without ingredients or products that starts with a non-alphanumeric character is a label. By convention we use '+' to indicate label names. This is a good time to point out that `{` and `}` are also just labels in Mu syntax, and that `break` and `loop` get rewritten as jumps to just after the enclosing `}` and `{` respectively. This gives us a simple sort of structured programming without adding complexity to the parser -- Mu functions remain just flat lists of instructions. --- Another example, this time with concurrency. ``` recipe main [ start-running thread2 { $print 34 loop } ] recipe thread2 [ { $print 35 loop } ] ``` ```shell $ ./mu fork.mu ``` Notice that it repeatedly prints either '34' or '35' at random. Hit ctrl-c to stop. Yet another example forks two 'routines' that communicate over a channel: ```shell $ ./mu channel.mu produce: 0 produce: 1 produce: 2 produce: 3 consume: 0 consume: 1 consume: 2 produce: 4 consume: 3 consume: 4 # The exact order above might shift over time, but you'll never see a number # consumed before it's produced. ``` Channels are the unit of synchronization in Mu. Blocking on channels are the only way tasks can sleep waiting for results. The plan is to do all I/O over channels that wait for data to return. Routines are expected to communicate purely by message passing, though nothing stops them from sharing memory since all routines share a common address space. However, idiomatic Mu will make it hard to accidentally read or clobber random memory locations. Bounds checking is baked deeply into the semantics, and pointer arithmetic will be mostly forbidden (except inside the memory allocator and a few other places). --- If you're still reading, here are some more things to check out: a) Look at the [chessboard program](http://akkartik.github.io/mu/html/chessboard.mu.html) for a more complex example where I write tests showing blocking reads from the keyboard and what gets printed to the screen -- things we don't typically associate with automated tests. b) Try skimming the [colorized source code](http://akkartik.github.io/mu). I'd like it to eventually be possible to get a pretty good sense for how things work just by skimming the files in order, skimming the top of each file and ignoring details lower down. Tell me how successful my efforts are. c) Try running the tests: ```shell $ ./mu test ``` You might also want to peek in the `.traces` directory, which automatically includes logs for each test showing you just how it ran on my machine. If Mu eventually gets complex enough that you have trouble running examples, these logs might help figure out if my system is somehow different from yours or if I've just been insufficiently diligent and my documentation is out of date. d) Try out the programming environment: ```shell $ ./mu test edit # takes about 30s; shouldn't show any failures $ ./mu edit ``` Screenshot: <img alt='programming environment' src='html/edit.png' width='720px'> You write recipes on the left and try them out in *sandboxes* on the right. Hit F4 to rerun all sandboxes with the latest version of the code. More details: http://akkartik.name/post/mu. Beware, it won't save your edits by default. But if you create a sub-directory called `lesson/` under `mu/` it will. If you turn that directory into a git repo with `git init`, it will also back up each version you try out. Once you have a sandbox you can click on its result to mark it as expected: <img alt='expected result' src='html/expected-result.png' width='180px'> Later if the result changes it'll be flagged in red to draw your attention to it. Thus, manually tested sandboxes become reproducible automated tests. <img alt='unexpected result' src='html/unexpected-result.png' width='180px'> Another feature: Clicking on the code in a sandbox expands its trace for you to browse. To add to the trace, use `stash`. For example: ```nim stash [first ingredient is ], x ``` Invaluable for understanding complex control flow without cluttering up the screen. The next major milestone on Mu's roadmap is support for recording and faking console input to a sandbox, so that you can type in an input once and have it replay everytime you hit F4. Once this support is in place it will be easy to generalize to more interfaces, like requesting urls over a network or reading files on a disk. **Credits** Mu builds on many ideas that have come before, especially: - [Peter Naur](http://alistair.cockburn.us/ASD+book+extract%3A+%22Naur,+Ehn,+Musashi%22) for articulating the paramount problem of programming: communicating a codebase to others; - [Christopher Alexander](http://www.amazon.com/Notes-Synthesis-Form-Harvard-Paperbacks/dp/0674627512) and [Richard Gabriel](http://dreamsongs.net/Files/PatternsOfSoftware.pdf) for the intellectual tools for reasoning about the higher order design of a codebase; - Unix and C for showing us how to co-evolve language and OS, and for teaching the (much maligned, misunderstood and underestimated) value of concise *implementation* in addition to a clean interface; - Donald Knuth's [literate programming](http://www.literateprogramming.com/knuthweb.pdf) for liberating "code for humans to read" from the tyranny of compiler order; - [David Parnas](http://www.cs.umd.edu/class/spring2003/cmsc838p/Design/criteria.pdf) and others for highlighting the value of separating concerns and stepwise refinement; - [Lisp](http://www.paulgraham.com/rootsoflisp.html) for showing the power of dynamic languages, late binding and providing the right primitives *a la carte*, especially lisp macros; - The folklore of debugging by print and the trace facility in many lisp systems; - Automated tests for showing the value of developing programs inside an elaborate harness; - [Python doctest](http://docs.python.org/2/library/doctest.html) for exemplifying interactive documentation that doubles as tests; - [ReStructuredText](https://en.wikipedia.org/wiki/ReStructuredText) and [its antecedents](https://en.wikipedia.org/wiki/Setext) for showing that markup can be clean; - BDD for challenging us all to write tests at a higher level; - JavaScript and CSS for demonstrating the power of a DOM for complex structured documents.