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
|