1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle
.Rook
.Compiler
.Services
.Passes
18 using System
.Collections
;
20 using Castle
.Rook
.Compiler
.AST
;
21 using Castle
.Rook
.Compiler
.Visitors
;
24 public class DeclarationBinding
: BreadthFirstVisitor
, ICompilerPass
26 private readonly IErrorReport errorReport
;
27 private readonly IIdentifierNameService identifierService
;
28 private int globalClassesCount
= 1;
30 public DeclarationBinding(IErrorReport errorReport
, IIdentifierNameService identifierService
)
32 this.errorReport
= errorReport
;
33 this.identifierService
= identifierService
;
36 public void ExecutePass(CompilationUnit unit
)
40 if (errorReport
.HasErrors
) return;
42 // Now we register the single var declaration as type members (fields)
43 // and ensure they are on the symbol table - sanity check
44 FieldsVisitor visitor
= new FieldsVisitor();
45 visitor
.VisitCompilationUnit(unit
);
48 public override bool VisitSourceUnit(SourceUnit unit
)
50 NamespaceGraph nsGraph
= unit
.SymbolTable
.TypeGraphView
.DefineNamespace("Castle.Rook.Code");
52 unit
.SymbolTable
.CurrentNamespace
= nsGraph
;
54 TypeDefinitionStatement typeDef
= new TypeDefinitionStatement(unit
.SymbolTable
, AccessLevel
.Private
, String
.Format("Global{0}", globalClassesCount
++));
56 unit
.SymbolTable
.CurrentTypeDefinition
= typeDef
.SymbolTable
.CurrentNamespace
.AddTransientType( typeDef
);
58 return base.VisitSourceUnit(unit
);
61 public override bool VisitNamespace(NamespaceDeclaration ns
)
63 if (!identifierService
.IsValidNamespaceName(ns
.Name
))
65 errorReport
.Error( "TODOFILENAME", ns
.Position
, "'{0}' is an invalid namespace name.", ns
.Name
);
70 ns
.SymbolTable
.CurrentNamespace
= ns
.SymbolTable
.TypeGraphView
.DefineNamespace(ns
.Name
);
72 return base.VisitNamespace(ns
);
75 public override bool VisitTypeDefinitionStatement(TypeDefinitionStatement typeDef
)
77 if (!identifierService
.IsValidTypeName(typeDef
.Name
))
79 errorReport
.Error( "TODOFILENAME", typeDef
.Position
, "'{0}' is an invalid type name.", typeDef
.Name
);
84 System
.Diagnostics
.Debug
.Assert( typeDef
.SymbolTable
.CurrentNamespace
!= null );
86 typeDef
.NamespaceName
= typeDef
.SymbolTable
.CurrentNamespace
.Name
;
88 if (typeDef
.SymbolTable
.CurrentNamespace
.GetType(typeDef
.Name
) != null)
90 errorReport
.Error( "TODOFILENAME", typeDef
.Position
, "'{0}' has multiple definitions.", typeDef
.Name
);
95 typeDef
.SymbolTable
.CurrentTypeDefinition
= typeDef
.SymbolTable
.CurrentNamespace
.AddTransientType( typeDef
);
97 return base.VisitTypeDefinitionStatement(typeDef
);
100 public override bool VisitMethodDefinitionStatement(MethodDefinitionStatement methodDef
)
102 if (!identifierService
.IsValidMethodName(methodDef
.Name
))
104 errorReport
.Error( "TODOFILENAME", methodDef
.Position
, "'{0}' is an invalid method name.", methodDef
.Name
);
109 TransientType owner
= methodDef
.SymbolTable
.CurrentTypeDefinition
;
111 System
.Diagnostics
.Debug
.Assert( owner
!= null );
115 owner
.AddMethod(methodDef
);
119 errorReport
.Error( "TODOFILENAME", methodDef
.Position
, ex
.Message
, methodDef
.Name
);
124 return base.VisitMethodDefinitionStatement(methodDef
);
127 public override bool VisitRequireStatement(RequireStatement statement
)
129 String name
= statement
.QualifiedIdentifier
.Name
;
131 if (!identifierService
.IsValidNamespaceName( name
))
133 errorReport
.Error( "TODOFILENAME", statement
.Position
, "You used a required statement for an invalid namespace '{0}'", name
);
140 statement
.SymbolTable
.TypeGraphView
.AddRequire( name
);
144 errorReport
.Error( "TODOFILENAME", statement
.Position
, ex
.Message
);
149 return base.VisitRequireStatement(statement
);
152 public override bool VisitVariableReferenceExpression(VariableReferenceExpression varRef
)
154 if (varRef
.Identifier
.Type
== IdentifierType
.Local
)
156 String name
= varRef
.Identifier
.Name
;
157 ISymbolTable table
= varRef
.SymbolTable
;
159 // It might be a variable or might be something else.
160 // First of all, let's see whether it is a var
162 bool isDefinedAsVar
= false;
164 isDefinedAsVar
= varRef
.SymbolTable
.IsDefined(name
);
168 if (table
.ScopeType
== ScopeType
.Block
|| table
.ScopeType
== ScopeType
.Compound
)
170 isDefinedAsVar
= table
.IsDefinedInParent(name
);
176 // Ok, this might be two things now:
177 // an undeclared variable, a type ref or a member access... Let's check the type graph
179 TypeDefinition typeDef
= varRef
.SymbolTable
.CurrentTypeDefinition
;
181 System
.Diagnostics
.Debug
.Assert( typeDef
!= null );
183 if (typeDef
.HasInstanceMember(name
) || typeDef
.HasStaticMember(name
))
187 // TODO: Replace node by MemberAccess or method invocation, depending on the case
191 NamespaceGraph nsGraph
= table
.TypeGraphView
.GetNamespace( name
);
195 TypeDefinition typedef
= table
.TypeGraphView
.GetType( name
);
199 errorReport
.Error( "TODOFILENAME", varRef
.Position
, "'{0}' undeclared identifier", name
);
206 return base.VisitVariableReferenceExpression(varRef
);
209 public override bool VisitMultipleVariableDeclarationStatement(MultipleVariableDeclarationStatement varDecl
)
211 ProcessMultipleVariableDeclarationStatement(varDecl
);
213 return base.VisitMultipleVariableDeclarationStatement(varDecl
);
216 public override bool VisitAssignmentExpression(AssignmentExpression assignExp
)
218 if (assignExp
.Target
.NodeType
== NodeType
.VariableRefExpression
)
220 // Convert to declaration if not found on the scope
222 VariableReferenceExpression varRef
= (VariableReferenceExpression
) assignExp
.Target
;
224 ISymbolTable scope
= varRef
.SymbolTable
;
226 System
.Diagnostics
.Debug
.Assert( scope
!= null );
228 String name
= varRef
.Identifier
.Name
;
230 if (!scope
.IsDefined(name
)) // TODO: The rules are slighly more complicated than that.
232 errorReport
.Disable();
234 SingleVariableDeclarationStatement varDecl
= new SingleVariableDeclarationStatement(varRef
.Identifier
);
236 IStatement stmt
= ASTUtils
.GetParentStatement(varRef
);
238 System
.Diagnostics
.Debug
.Assert( stmt
!= null );
240 IStatementContainer stmts
= stmt
.Parent
as IStatementContainer
;
242 int index
= stmts
.Statements
.IndexOf(stmt
);
244 varDecl
.InitExp
= assignExp
.Value
;
246 // stmts.Statements.Insert(index, varDecl);
247 stmts
.Statements
.Replace(stmt
, varDecl
);
249 if (!ApplyDeclarationRules(varRef
.Identifier
, scope
, varDecl
, stmt
))
251 stmts
.Statements
.Remove(varDecl
);
254 errorReport
.Enable();
258 return base.VisitAssignmentExpression(assignExp
);
261 public override bool VisitParameterIdentifier(ParameterIdentifier parameterIdentifier
)
263 ISymbolTable namescope
= parameterIdentifier
.Parent
.SymbolTable
;
265 System
.Diagnostics
.Debug
.Assert( namescope
!= null );
266 System
.Diagnostics
.Debug
.Assert( namescope
.ScopeType
== ScopeType
.Method
|| namescope
.ScopeType
== ScopeType
.Block
);
268 if (!identifierService
.IsValidFormatParameterName(parameterIdentifier
.Name
))
270 errorReport
.Error( "TODOFILENAME", parameterIdentifier
.Position
, "'{0}' is an invalid parameter name.", parameterIdentifier
.Name
);
274 System
.Diagnostics
.Debug
.Assert( !namescope
.IsDefined(parameterIdentifier
.Name
) );
276 namescope
.AddVariable( parameterIdentifier
);
278 return base.VisitParameterIdentifier(parameterIdentifier
);
281 public override bool VisitMemberAccessExpression(MemberAccessExpression accessExpression
)
283 return base.VisitMemberAccessExpression(accessExpression
);
286 private void ProcessMultipleVariableDeclarationStatement(MultipleVariableDeclarationStatement decl
)
288 IList stmts
= ConvertToSingleDeclarationStatements(decl
);
290 ReplaceOriginalMultipleVariableDeclarationStatement(decl
, stmts
);
292 EnsureTypeDeclarationsBelongsToThisScope(decl
, stmts
);
295 private void ReplaceOriginalMultipleVariableDeclarationStatement(MultipleVariableDeclarationStatement decl
, IList stmts
)
299 // Replace the VariableDeclarationStatement node by a
300 // (possible) sequence of SingleVariableDeclarationStatement
302 IStatementContainer stmtContainer
= decl
.Parent
as IStatementContainer
;
304 index
= stmtContainer
.Statements
.IndexOf(decl
);
306 stmtContainer
.Statements
.RemoveAt(index
);
308 foreach(SingleVariableDeclarationStatement svDecl
in stmts
)
310 stmtContainer
.Statements
.Insert( index
++, svDecl
);
314 private void EnsureTypeDeclarationsBelongsToThisScope(MultipleVariableDeclarationStatement varDecl
, IList stmts
)
316 ISymbolTable namescope
= varDecl
.Parent
.SymbolTable
;
318 System
.Diagnostics
.Debug
.Assert( namescope
!= null );
320 foreach(SingleVariableDeclarationStatement typeDecl
in stmts
)
322 Identifier ident
= typeDecl
.Identifier
;
324 // Most simple of cases: duplicated declaration
325 if (namescope
.IsDefined(ident
.Name
))
327 errorReport
.Error( "TODOFILENAME", typeDecl
.Position
, "Sorry but '{0}' is already defined.", ident
.Name
);
331 ApplyDeclarationRules(ident
, namescope
, typeDecl
, varDecl
);
335 private bool ApplyDeclarationRules(Identifier ident
, ISymbolTable namescope
, SingleVariableDeclarationStatement typeDecl
, IStatement statem
)
337 // Second simple case: a local var and we are on the right place to
339 if (ident
.Type
== IdentifierType
.Local
&&
340 (namescope
.ScopeType
== ScopeType
.Method
||
341 namescope
.ScopeType
== ScopeType
.Compound
||
342 namescope
.ScopeType
== ScopeType
.Block
))
344 namescope
.AddVariable(ident
);
348 // More complex: a block or compound tries to redefine a variable
349 if (ident
.Type
== IdentifierType
.Local
&&
350 (namescope
.ScopeType
== ScopeType
.Compound
||
351 namescope
.ScopeType
== ScopeType
.Block
))
353 if (namescope
.Parent
.IsDefined(ident
.Name
))
355 errorReport
.Error( "TODOFILENAME", typeDecl
.Position
, "Sorry but '{0}' is already defined in a parent scope.", ident
.Name
);
360 // Local variables at class level?
361 // We will support that as a type initializer, but not now.
362 if (ident
.Type
== IdentifierType
.Local
&& namescope
.ScopeType
== ScopeType
.Type
)
364 errorReport
.Error( "TODOFILENAME", typeDecl
.Position
, "At type level, just instance or static fields are allowed (yet)" );
368 // Static or instance in a method/block/compound are moved
369 // to the parent class or source unit level
370 if (ident
.Type
== IdentifierType
.InstanceField
||
371 ident
.Type
== IdentifierType
.StaticField
)
373 if (namescope
.ScopeType
== ScopeType
.SourceUnit
||
374 namescope
.ScopeType
== ScopeType
.Type
)
376 namescope
.AddVariable(ident
);
378 else if (namescope
.ScopeType
== ScopeType
.Method
||
379 namescope
.ScopeType
== ScopeType
.Compound
||
380 namescope
.ScopeType
== ScopeType
.Block
)
382 IASTNode node
= statem
.Parent
;
384 while(node
!= null &&
385 node
.NodeType
!= NodeType
.TypeDefinition
&&
386 node
.NodeType
!= NodeType
.SourceUnit
)
391 if (node
== null || node
.SymbolTable
== null)
393 errorReport
.Error( "TODOFILENAME", typeDecl
.Position
,
394 "Compiler error: The instance of static declaration '{0}' could not be mapped to a parent type", ident
.Name
);
398 ISymbolTable parentScope
= node
.SymbolTable
;
400 IStatementContainer typeStmtsContainer
= node
as IStatementContainer
;
402 System
.Diagnostics
.Debug
.Assert( parentScope
!= null );
403 System
.Diagnostics
.Debug
.Assert( typeStmtsContainer
!= null );
405 if (parentScope
.IsDefined(ident
.Name
))
407 errorReport
.Error( "TODOFILENAME", typeDecl
.Position
,
408 "Sorry but '{0}' is already defined.", ident
.Name
);
413 parentScope
.AddVariable(ident
);
415 // We can replace the declaration on the method
416 // body with an assignment if and only if this type decl has
417 // an init expression, so CreateAssignmentFromTypeDecl can return null
418 AssignmentExpression assignExp
= CreateAssignmentFromTypeDecl(typeDecl
);
419 ExpressionStatement assignExpStmt
= new ExpressionStatement(assignExp
);
421 typeDecl
.ConvertInitExpressionToDependency();
423 // Replace the declaration with an assignment
424 (statem
.Parent
as IStatementContainer
).Statements
.Replace(typeDecl
, assignExpStmt
);
426 // Add the member/field declaration to the parent
427 typeStmtsContainer
.Statements
.Add( typeDecl
);
429 // TODO: Link assignment expression and typeDecl to help
430 // find out the type of the field later
440 private IList
ConvertToSingleDeclarationStatements(MultipleVariableDeclarationStatement varDecl
)
442 IList newStmts
= new ArrayList();
445 ExpressionCollection initExps
= varDecl
.InitExpressions
;
447 foreach(Identifier ident
in varDecl
.Identifiers
)
449 // Here we are converting expression from
450 // x:int, y:long = 1, 2L
451 // to an AST representation equivalent to
452 // x:int = 1; y:long = 2L
454 SingleVariableDeclarationStatement svStmt
= new SingleVariableDeclarationStatement(ident
);
456 if (index
< initExps
.Count
)
458 svStmt
.InitExp
= initExps
[index
];
459 EnsureNoPostFixStatement(svStmt
.InitExp
);
464 newStmts
.Add(svStmt
);
467 // We don't need them anymore
473 private void EnsureNoPostFixStatement(IExpression initExpression
)
475 if (initExpression
.PostFixStatement
!= null)
477 errorReport
.Error( "TODOFILENAME", initExpression
.Position
,
478 "Sorry but a variable initializer can not be conditional or " +
479 "has a while/until statement attached.");
483 private AssignmentExpression
CreateAssignmentFromTypeDecl(SingleVariableDeclarationStatement decl
)
485 if (decl
.InitExp
== null) return null;
487 return new AssignmentExpression( new VariableReferenceExpression(decl
.Identifier
), decl
.InitExp
);
491 public class FieldsVisitor
: BreadthFirstVisitor
493 public override bool VisitSingleVariableDeclarationStatement(SingleVariableDeclarationStatement varDecl
)
495 PerformSanityCheck(varDecl
);
497 CreateField(varDecl
);
499 return base.VisitSingleVariableDeclarationStatement(varDecl
);
502 private void CreateField(SingleVariableDeclarationStatement decl
)
504 if (decl
.Identifier
.Type
== IdentifierType
.Local
) return;
506 TransientType type
= decl
.SymbolTable
.CurrentTypeDefinition
;
508 System
.Diagnostics
.Debug
.Assert( type
!= null );
510 type
.AddField( decl
);
513 private void PerformSanityCheck(SingleVariableDeclarationStatement decl
)
515 // TODO: Ensure that the symbol table are capable of holding a var declaration
517 if (!decl
.SymbolTable
.IsDefined( decl
.Identifier
.Name
))
519 throw new CompilerException("Found single var declaration node that was not on the symbol table: " + decl
.Identifier
.Name
);