167 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Factor
		
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Factor
		
	
	
| USING: classes continuations effects help.markup help.syntax io
 | |
| kernel quotations sequences stack-checker.errors ;
 | |
| IN: stack-checker
 | |
| 
 | |
| ARTICLE: "inference-simple" "Straight-line stack effects"
 | |
| "The simplest case is when a piece of code does not have any branches or recursion, and just pushes literals and calls words."
 | |
| $nl
 | |
| "Pushing a literal has stack effect " { $snippet "( -- x )" } ". The stack effect of most words is always known statically from the declaration. Stack effects of " { $link POSTPONE: inline } " words and " { $link "macros" } ", may depend on literals pushed on the stack prior to the call, and this case is discussed in " { $link "inference-combinators" } "."
 | |
| $nl
 | |
| "The stack effect of each element in a code snippet is composed. The result is then the stack effect of the snippet."
 | |
| $nl
 | |
| "An example:"
 | |
| { $example "[ 1 2 3 ] infer." "( -- x x x )" }
 | |
| "Another example:"
 | |
| { $example "[ 2 + ] infer." "( x -- x )" } ;
 | |
| 
 | |
| ARTICLE: "inference-combinators" "Combinator stack effects"
 | |
| "If a word calls a combinator, one of the following two conditions must hold for the stack checker to succeed:"
 | |
| { $list
 | |
|   { "The combinator must be called with a quotation that is either literal or built from literal quotations, " { $link curry } ", and " { $link compose } ". (Note that quotations that use " { $vocab-link "fry" } " or " { $vocab-link "locals" } " use " { $link curry } " and " { $link compose } " from the perspective of the stack checker.)" }
 | |
|   { "If the word is declared " { $link POSTPONE: inline } ", the combinator may additionally be called on one of the word's input parameters or with quotations built from the word's input parameters, literal quotations, " { $link curry } ", and " { $link compose } ". When inline, a word is itself considered to be a combinator, and its callers must in turn satisfy these conditions." }
 | |
| }
 | |
| "If neither condition holds, the stack checker throws an " { $link unknown-macro-input } " or " { $link bad-macro-input } " error. To make the code compile, a runtime checking combinator such as " { $link POSTPONE: call( } " must be used instead. See " { $link "inference-escape" } " for details. An inline combinator can be called with an unknown quotation by " { $link curry } "ing the quotation onto a literal quotation that uses " { $link POSTPONE: call( } "."
 | |
| { $heading "Input stack effects" }
 | |
| "Inline combinators will verify the stack effect of their input quotations if they are declared in the combinator's stack effect. See " { $link "effects-variables" } " for details."
 | |
| { $heading "Examples" }
 | |
| { $subheading "Calling a combinator" }
 | |
| "The following usage of " { $link map } " passes the stack checker, because the quotation is the result of " { $link curry } ":"
 | |
| { $example "USING: math sequences ;" "[ [ + ] curry map ] infer." "( x x -- x )" }
 | |
| "The equivalent code using " { $vocab-link "fry" } " and " { $vocab-link "locals" } " likewise passes the stack checker:"
 | |
| { $example "USING: fry math sequences ;" "[ '[ _ + ] map ] infer." "( x x -- x )" }
 | |
| { $example "USING: locals math sequences ;" "[| a | [ a + ] map ] infer." "( x x -- x )" }
 | |
| { $subheading "Defining an inline combinator" }
 | |
| "The following word calls a quotation twice; the word is declared " { $link POSTPONE: inline } ", since it invokes " { $link call } " on the result of " { $link compose } " on an input parameter:"
 | |
| { $code ": twice ( value quot -- result ) dup compose call ; inline" }
 | |
| "The following code now passes the stack checker; it would fail were " { $snippet "twice" } " not declared " { $link POSTPONE: inline } ":"
 | |
| { $unchecked-example "USE: math.functions" "[ [ sqrt ] twice ] infer." "( x -- x )" }
 | |
| { $subheading "Defining a combinator for unknown quotations" }
 | |
| "In the next example, " { $link POSTPONE: call( } " must be used because the quotation is the result of calling a runtime accessor, and the compiler cannot make any static assumptions about this quotation at all:"
 | |
| { $code
 | |
|   "TUPLE: action name quot ;"
 | |
|   ": perform ( value action -- result ) quot>> call( value -- result ) ;"
 | |
| }
 | |
| { $subheading "Passing an unknown quotation to an inline combinator" }
 | |
| "Suppose we want to write:"
 | |
| { $code ": perform ( values action -- results ) quot>> map ;" }
 | |
| "However this fails to pass the stack checker since there is no guarantee the quotation has the right stack effect for " { $link map } ". It can be wrapped in a new quotation with a declaration:"
 | |
| { $code ": perform ( values action -- results )" "    quot>> [ call( value -- result ) ] curry map ;" }
 | |
| { $heading "Explanation" }
 | |
| "This restriction exists because without further information, one cannot say what the stack effect of " { $link call } " is; it depends on the given quotation. If the stack checker encounters a " { $link call } " without further information, a " { $link unknown-macro-input } " or " { $link bad-macro-input } " error is raised."
 | |
| $nl
 | |
| "On the other hand, the stack effect of applying " { $link call } " to a literal quotation or a " { $link curry } " of a literal quotation is easy to compute; it behaves as if the quotation was substituted at that point."
 | |
| { $heading "Limitations" }
 | |
| "The stack checker cannot guarantee that a literal quotation is still literal if it is passed on the data stack to an inlined recursive combinator such as " { $link each } " or " { $link map } ". For example, the following will not infer:"
 | |
| { $example
 | |
|   "[ [ reverse ] swap [ reverse ] map swap call ] infer." "Cannot apply “call” to a run-time computed value\nmacro call"
 | |
| }
 | |
| "To make this work, use " { $link dip } " to pass the quotation instead:"
 | |
| { $example
 | |
|   "[ [ reverse ] [ [ reverse ] map ] dip call ] infer." "( x -- x )"
 | |
| } ;
 | |
| 
 | |
| ARTICLE: "inference-branches" "Branch stack effects"
 | |
| "Conditionals such as " { $link if } " and combinators built on top have the same restrictions as " { $link POSTPONE: inline } " combinators (see " { $link "inference-combinators" } ") with the additional requirement that all branches leave the stack at the same height. If this is not the case, the stack checker throws an " { $link unbalanced-branches-error } "."
 | |
| $nl
 | |
| "If all branches leave the stack at the same height, then the stack effect of the conditional is just the maximum of the stack effect of each branch. For example,"
 | |
| { $example "[ [ + ] [ drop ] if ] infer." "( x x x -- x )" }
 | |
| "The call to " { $link if } " takes one value from the stack, a generalized boolean. The first branch " { $snippet "[ + ]" } " has stack effect " { $snippet "( x x -- x )" } " and the second has stack effect " { $snippet "( x -- )" } ". Since both branches decrease the height of the stack by one, we say that the stack effect of the two branches is " { $snippet "( x x -- x )" } ", and together with the boolean popped off the stack by " { $link if } ", this gives a total stack effect of " { $snippet "( x x x -- x )" } "." ;
 | |
| 
 | |
| ARTICLE: "inference-recursive-combinators" "Recursive combinator stack effects"
 | |
| "Most combinators do not call themselves recursively directly; instead, they are implemented in terms of existing combinators, for example " { $link while } ", " { $link map } ", and the " { $link "compositional-combinators" } ". In these cases, the rules outlined in " { $link "inference-combinators" } " apply."
 | |
| $nl
 | |
| "Combinators which are recursive require additional care. In addition to being declared " { $link POSTPONE: inline } ", they must be declared " { $link POSTPONE: recursive } ". There are three restrictions that only apply to combinators with this declaration:"
 | |
| { $heading "Input quotation declaration" }
 | |
| "Input parameters which are quotations must be annotated as such in the stack effect. For example, the following will not infer:"
 | |
| { $unchecked-example ": bad ( quot -- ) [ call ] keep bad ; inline recursive" "[ [ ] bad ] infer." "Cannot apply “call” to a run-time computed value\nmacro call" }
 | |
| "The following is correct:"
 | |
| { $example ": good ( quot: ( -- ) -- ) [ call ] keep good ; inline recursive" "[ [ ] good ] infer." "( -- )" }
 | |
| "The effect of the nested quotation itself is only present for documentation purposes; the mere presence of a nested effect is sufficient to mark that value as a quotation parameter."
 | |
| { $heading "Data flow restrictions" }
 | |
| "The stack checker does not trace data flow in two instances."
 | |
| $nl
 | |
| "An inline recursive word cannot pass a quotation on the data stack through the recursive call. For example, the following will not infer:"
 | |
| { $unchecked-example ": bad ( ? quot: ( ? -- ) -- ) 2dup [ not ] dip bad call ; inline recursive" "[ [ drop ] bad ] infer." "Cannot apply “call” to a run-time computed value\nmacro call" }
 | |
| "However a small change can be made:"
 | |
| { $example ": good ( ? quot: ( ? -- ) -- ) [ good ] 2keep [ not ] dip call ; inline recursive" "[ [ drop ] good ] infer." "( x -- )" }
 | |
| "An inline recursive word must have a fixed stack effect in its base case. The following will not infer:"
 | |
| { $unchecked-example
 | |
|     ": foo ( quot ? -- ) [ f foo ] [ call ] if ; inline"
 | |
|     "[ [ 5 ] t foo ] infer."
 | |
|     "The inline recursive word “foo” must be declared recursive\nword foo"
 | |
| } ;
 | |
| 
 | |
| ARTICLE: "tools.inference" "Stack effect tools"
 | |
| { $link "inference" } " can be used interactively to print stack effects of quotations without running them. It can also be used from " { $link "combinators.smart" } "."
 | |
| { $subsections
 | |
|     infer
 | |
|     infer.
 | |
| }
 | |
| "There are also some words for working with " { $link effect } " instances. Getting a word's declared stack effect:"
 | |
| { $subsections stack-effect }
 | |
| "Converting a stack effect to a string form:"
 | |
| { $subsections effect>string }
 | |
| "Comparing effects:"
 | |
| { $subsections
 | |
|     effect-height
 | |
|     effect<=
 | |
|     effect=
 | |
| }
 | |
| "The class of stack effects:"
 | |
| { $subsections
 | |
|     effect
 | |
|     effect?
 | |
| } ;
 | |
| 
 | |
| ARTICLE: "inference-escape" "Stack effect checking escape hatches"
 | |
| "In a static checking regime, sometimes it is necessary to step outside the boundaries and run some code which cannot be statically checked; perhaps this code is constructed at run-time. There are two ways to get around the static stack checker."
 | |
| $nl
 | |
| "If the stack effect of a word or quotation is known, but the word or quotation itself is not, " { $link POSTPONE: execute( } " or " { $link POSTPONE: call( } " can be used. See " { $link "call" } " for details."
 | |
| $nl
 | |
| "If the stack effect is not known, the code being called cannot manipulate the datastack directly. Instead, it must reflect the datastack into an array:"
 | |
| { $subsections with-datastack }
 | |
| "The surrounding code has a static stack effect since " { $link with-datastack } " has one. However, the array passed in as input may be transformed arbitrarily by calling this combinator." ;
 | |
| 
 | |
| ARTICLE: "inference" "Stack effect checking"
 | |
| "The " { $link "compiler" } " checks the " { $link "effects" } " of words before they can be run. This ensures that words take exactly the number of inputs and outputs that the programmer declares in source."
 | |
| $nl
 | |
| "Words that do not pass the stack checker are rejected and cannot be run, and so essentially this defines a very simple and permissive type system that nevertheless catches some invalid programs and enables compiler optimizations."
 | |
| $nl
 | |
| "If a word's stack effect cannot be inferred, a compile error is reported. See " { $link "compiler-errors" } "."
 | |
| $nl
 | |
| "The following articles describe how different control structures are handled by the stack checker."
 | |
| { $subsections
 | |
|     "inference-simple"
 | |
|     "inference-combinators"
 | |
|     "inference-recursive-combinators"
 | |
|     "inference-branches"
 | |
| }
 | |
| "Stack checking catches several classes of errors."
 | |
| { $subsections "inference-errors" }
 | |
| "Sometimes code with a dynamic stack effect has to be run."
 | |
| { $subsections "inference-escape" }
 | |
| { $see-also "effects" "tools.inference" "tools.errors" } ;
 | |
| 
 | |
| ABOUT: "inference"
 | |
| 
 | |
| HELP: inference-error
 | |
| { $values { "class" class } }
 | |
| { $description "Creates an instance of " { $snippet "class" } ", wraps it in an " { $link inference-error } " and throws the result." }
 | |
| { $error-description
 | |
|     "Thrown by " { $link infer } " when the stack effect of a quotation cannot be inferred."
 | |
|     $nl
 | |
|     "The " { $snippet "error" } " slot contains one of several possible " { $link "inference-errors" } "."
 | |
| } ;
 | |
| 
 | |
| HELP: infer
 | |
| { $values { "quot" quotation } { "effect" "an instance of " { $link effect } } }
 | |
| { $description "Attempts to infer the quotation's stack effect. For interactive testing, the " { $link infer. } " word should be called instead since it presents the output in a nicely formatted manner." }
 | |
| { $errors "Throws an " { $link inference-error } " if stack effect inference fails." } ;
 | |
| 
 | |
| HELP: infer.
 | |
| { $values { "quot" quotation } }
 | |
| { $description "Attempts to infer the quotation's stack effect, and prints this data to " { $link output-stream } "." }
 | |
| { $errors "Throws an " { $link inference-error } " if stack effect inference fails." } ;
 | |
| 
 | |
| { infer infer. } related-words
 |