-
Notifications
You must be signed in to change notification settings - Fork 2
Yadriggy Walker
A tree walker for the abstract syntax trees (ASTs),
or an implementation of Visitor pattern.
This DSL is used for traversing the ASTs
obtained by calling Yadriggy::reify
.
It can take account of the user types defined by the
syntax checker of Yadriggy.
The implementation of this DSL does not use Yadriggy.
It is a normal DSL embedded in Ruby.
This is the root class of the tree walker.
Its behavior is similar to
a case
statement (or a switch
statement) for the ASTs:
class SimpleChecker < Yadriggy::Checker
rule(Number) do
'number'
end
rule(Identifier) do
'identifier'
end
rule(Binary) do
v1 = check(ast.left)
v2 = check(ast.right)
"#{v1}#{ast.op}#{v2}"
end
end
This class is used as follows:
checker = SimpleChecker.new
ast = Yadriggy::reify { k + 3 }.tree.body
checker.check(ast) # `identifier+number`
The check
method selects a rule for the type of
the AST node given as the argument. The rules are given
by the rule
method. In the case above, three rules
are given. They are for the Number
class, the Identifier
class,
and the Binary
class.
Since ast
is an instance
of Binary
node representing k + 3
, the rule for Binary
is selected. Then, the check
method executes the body
of the selected rule and returns the result.
Note that the rule for Binary
includes nested calls to check
.
They also select and execute the corresponding rules;
the first one is for Identifier
and the second one is for Number
.
The check
method first tries to select the rule for the
user type of the given AST node. The user type is attached
by the syntax checker Yadriggy::Syntax
.
If no such rule is found (or the user type is not attached), then it
selects the rule for the concrete class of the AST node.
If no rule for that class is found, it next selects the rule
for its super class, the super class of that super class,
and so on. Finally, if no rule is selected, an exception
is thrown.
The check
method executes the body of the selected rule.
The result of this execution is returned by the check
method.
The Checker
class defines several methods. They are available
in the body of the rules and in the subclasses of Checker
.
This applies the rule supplied for the super class.
Type | ||
---|---|---|
an_ast | ASTnode | the current AST |
envi | Object | the current environment |
return | Object | The result of the application of the rule |
an_ast
and envi
are used when executing that rule.
The proceed
method
is used in the body of the rule overriding the corresponding one
in the super class:
class VerboseChecker < SimpleChecker
rule(Number) do
puts 'Number'
proceed(ast) # invokes rule(Number) in SimpleChecker
end
end
This obtains the current AST.
Type | ||
---|---|---|
return | ASTnode | the current AST |
This obtains the current environment.
Type | ||
---|---|---|
return | Object | the current environment |
The Checker
class does not require that the type of the environments is
a specific class. It can be any class since the Checker
class does
not access it. (The programmer of) a subclass of Checker
will choose
the type of the environments.
For example, when a subclass of Checker
is an AST walker for
type checking, the environment will be an object representing a type
environment, which is a map from names to types.
This applies a rule to the given AST.
It returns the result of the application of the rule or throws
a CheckError
.
This is the entry point of the checker. It first invokes check
with an_ast
and then executes the blocks recorded by check_later
.
Type | ||
---|---|---|
an_ast | ASTree or ASTnode | the AST |
return | Object | the result |
This selects a rule for the AST given for an_ast
and then executes the rule. It returns the result of executing
the rule.
Type | ||
---|---|---|
an_ast | ASTree or ASTnode | the AST |
envi | Object | the environment |
return | Object | the result |
This records a block, which will be executed later by check_all
.
See the type_assert_later
method shown below.
This reports an error.
Type | ||
---|---|---|
an_ast | ASTree or ASTnode | the AST causing the error |
msg | String | the error message |
It is used in the body of a rule:
class NumberChecker < Yadriggy::Checker
rule(Number) do
'number'
end
rule(Identifier) do
error!(ast, "#{ast.name} is not valid")
end
end
This is the same as error!
except it raises an exception.
This returns true
when the execution of a rule causes an error.
It will be called after check
.
checker = NumberChecker.new
ast = Yadriggy::reify { foo }.tree.body
checker.check(ast)
if checker.errors?
puts checker.error_messages
end
This gets an array of error messages.
It returns an array of String
.
This gets the last error messages. It returns a String
.
This is a subclass of Checker
.
It provides utility methods for using Checker
for type checking.
This performs the type checking for the given AST and returns the type
of the given AST.
It is the entry point of the type checker.
It can be regarded as an alias of check_all
.
The returned value is a Yadriggy::Type
object or an instance of
its subclass.
Type | ||
---|---|---|
an_ast | ASTree, ASTnode, or nil | the AST |
return | Type | the type of the given AST |
This returns a Yadriggy::TypeChecker::TypeEnv
object representing
the current type environment.
It is equivalent to the ast_env
method.
This computes the type of the given AST under the given type environment.
The pair of the given AST and the resulting type is memoized in this
TypeChecker
object
to avoid redundant computation of the type.
Except this memoization, type
can be regarded as an alias of check
.
Type | ||
---|---|---|
an_ast | ASTnode or nil | the AST |
ast_tenv | TypeEnv | the type environment |
return | Type | the type of the given AST |
This method sets the type of the given AST an_ast
to the given type a_type
.
The pair of the given AST and its type is memoized in this TypeChecker
object.
Type | ||
---|---|---|
an_ast | ASTnode or nil | the AST |
ast_tenv | TypeEnv | the type environment |
return | Type | the type of the given AST |
This method returns the type of the given AST an_ast
.
The type has to have been already computed by the type
method.
This method searches this TypeChecker
object. It returns
the type if the pair of the given AST and the type is
memoized. Otherwise, if the pair is not memoized, it returns nil
.
This method can be regarded as an alias of the check_later
method in Checker
.
When this method is called within the body of a rule,
the given block is executed after the execution of all the
rules currently running finishes. (Note that the rule may include
a nested call to type
, which runs another rule.
Thus, the execution of the given block is postponed until all these rules
finish.)
For example, when a function definition is type-checked, it might be necessary to memoize the function signature (the parameter types and the return type) before the function body is type-checked. Without memoizing the function signature, a recursive call to the function could not be typed. For example,
rule(Def) do # rule for function definition
type_assert_later do
# check the type of the function body
s = type_env.new_tenv
body_t = type(ast.body, s)
end
# return the function signature
get_function_signature(ast)
end
The function body ast.body
is type-checked after the function signature
returned by get_function_signature
is memoized as the function's type.
This makes a new TypeDef
object and binds it to the class (or
instance) given by key
.
If there is the TypeDef
object for key
, this method returns it.
The TypeDef
object is a map to types from the instance variables
and the methods declared in the class (or the particular instance) given by key
.
Type | ||
---|---|---|
key | Module or Object | the class or the instance |
return | TypeDef | the TypeDef object for the given key
|
This method returns the TypeDef
object bound to key
.
If key
is nil, then it returns nil.
Type | ||
---|---|---|
key | Module, Object, or nil | the class or the instance |
return | TypeDef or nil | the TypeDef object for the given key
|
The TypeEnv
class represents a type environment.
It is a map from local variable names to their types.
This makes and returns a new type environment. Its initial contents are
the same as the callee type environment. For example, type_env.new_tenv
makes a copy of type_env
.
This updates the environment to map the given name to the given type.
Type | ||
---|---|---|
name | Name, String, Symbol, or nil | the name |
type | Type | the type |
return | Type | the given type |
This returns the type bound to the given name. If no type is bound to the name, this method returns nil.
Type | ||
---|---|---|
name | Name, String, Symbol, or nil | the name |
return | Type or nil | the type bound to the name |
The TypeDef
class represents a map from the names of instance variables
and method to their types.
This method updates the TypeDef
object to map the given name to the given type.
The name should denote an instance variable or a method.
Type | ||
---|---|---|
name | String, Symbol | the name |
type | Type | the type of the name |
return | Type | the given type |
This method returns the type of the instance variable or method with the given name.
Type | ||
---|---|---|
name | String, Symbol | the name |
return | Type or nil | the type of the name |