Commits

Jesse McGrew committed d4d27c9

Added hooks for locating include files
Changed symbol handling and messages

Comments (0)

Files changed (8)

Rellor.Core/Analyzer.cs

-using System.Diagnostics.Contracts;
+using System;
+using System.Diagnostics.Contracts;
+using Antlr.Runtime;
 
 namespace Rellor.Core
 {
         }
 
         public IGlobalScope GlobalScope { get; set; }
+
+        public void Load(ICharStream input)
+        {
+            throw new NotImplementedException();
+        }
     }
 }

Rellor.Core/Inform6.g3

 
 directive[bool nested]
 options { k=1; }
+@init { bool omit = false; }
+@after { if (omit) $tree = (CommonTree)adaptor.Nil(); }
     :	include_directive
     |	version_directive
     |	release_directive
     |	serial_directive
     |	object_directive
-		{DefineSymbol($Symbols::scope, SymbolType.Object, $object_directive.name, $object_directive.tree);}
+		{DefineSymbol($Symbols::scope, SymbolType.Object, $object_directive.identifier, $object_directive.tree);}
     |	routine_directive
-		{DefineSymbol($Symbols::scope, SymbolType.Routine, $routine_directive.name, $routine_directive.tree);}
+		{if ($nested) {
+			logger.Log(CoreMessages.RoutinesNotNestable, $routine_directive.start);
+			omit = true;
+		 } else
+			DefineSymbol($Symbols::scope, SymbolType.Routine, $routine_directive.identifier, $routine_directive.tree);}
     |	array_directive
-		{DefineSymbol($Symbols::scope, SymbolType.Array, $array_directive.name, $array_directive.tree);}
+		{DefineSymbol($Symbols::scope, SymbolType.Array, $array_directive.identifier, $array_directive.tree);}
     |	global_directive
-		{DefineSymbol($Symbols::scope, SymbolType.GlobalVar, $global_directive.name, $global_directive.tree);}
+		{DefineSymbol($Symbols::scope, SymbolType.GlobalVar, $global_directive.identifier, $global_directive.tree);}
     |	verb_directive
 	|	BEGIN_INCLUDE
 	|	END_INCLUDE
 
 include_directive
     :	kINCLUDEci^ fn=DQ_STRING ';'!
-		{IncludeFile(ParseDqString($fn.text));}
+		{var fntext = ParseDqString($fn.text);
+		 if (!IncludeFile(fntext))
+			logger.Log(CoreMessages.IncludeNotFound, $fn, fntext);}
     ;
 
 version_directive
     :	kSERIALci^ DQ_STRING ';'!
     ;
 
-object_directive returns [ITree name]
+object_directive returns [RellorToken identifier]
 	:   (	kOBJECTci
 			ARROW*
 			n=id									// object identifier
-			{$name = $n.tree;}
 			sn=DQ_STRING?							// textual short name
 			p=id_not_seg?							// parent object
 			object_parts
 			object_parts
 			-> ^(kCLASSci $n object_parts)
 		)
+		{$identifier = $n.start;}
 		';'
     ;
 
     :	{!IsKeyword((RellorToken)input.LT(1), CLASS, HAS, WITH, PRIVATE)}? id
     ;
     
-routine_directive returns [IScope routineScope, ITree name]
+routine_directive returns [IScope routineScope, RellorToken identifier]
 scope Symbols;
     :	LSQUARE n=id
 		{$routineScope = $Symbols::scope = new RoutineScope($Symbols[-1]::scope, $n.text);
-		 $name = $n.tree;}
+		 $identifier = $n.start;}
 		STAR?
-		(l+=id {DefineSymbol($routineScope, SymbolType.LocalVar, l.Tree);})* SEMI
+		(l+=id {DefineSymbol($routineScope, SymbolType.LocalVar, l.Start);})* SEMI
 		switch_body
 		RSQUARE ';'
 		-> ^(ROUTINE[$LSQUARE] $n ^(FLAGS STAR?)? ^(LOCALS $l*)? switch_body)
     ;
 
-array_directive returns [ITree name]
+array_directive returns [RellorToken identifier]
     :	kARRAYci^ n=id array_initializer ';'
-		{$name = $n.tree;}
+		{$identifier = $n.start;}
     ;
 
 array_initializer
         )
     ;
 
-global_directive returns [ITree name]
+global_directive returns [RellorToken identifier]
     :	kGLOBALci n=id
-		{$name = $n.tree;}
+		{$identifier = $n.start;}
         (	/* nada */
             -> ^(kGLOBALci $n)
         |	EQ any_expr

Rellor.Core/Inform6.g3.parser.cs

 
 namespace Rellor.Core
 {
+    public sealed class IncludeStreamEventArgs : EventArgs
+    {
+        public IncludeStreamEventArgs(string filename)
+        {
+            Contract.Requires(filename != null);
+
+            this.FileName = filename;
+        }
+
+        [ContractInvariantMethod]
+        private void ObjectInvariant()
+        {
+            Contract.Invariant(FileName != null);
+        }
+
+        public string FileName { get; set; }
+        public ICharStream Stream { get; set; }
+    }
+
+    public enum Compatibility
+    {
+        Extended,
+        Relaxed,
+        Strict,
+    }
+
     partial class Inform6Parser : ILogSink
     {
         private MsgLogger<CoreMessages> logger;
 
         public ILogSink LogSink { get; set; }
+        public Compatibility CompatibilityLevel { get; set; }
+
+        public event EventHandler<IncludeStreamEventArgs> IncludeStreamNeeded;
 
         partial void OnCreated()
         {
-            logger = new MsgLogger<CoreMessages>(this, "RLR");
+            logger = new MsgLogger<CoreMessages>(this, "R");
+            CompatibilityLevel = Compatibility.Relaxed;
+        }
+
+        private bool Extended
+        {
+            get { return CompatibilityLevel <= Compatibility.Extended; }
+        }
+
+        private bool Relaxed
+        {
+            get { return CompatibilityLevel <= Compatibility.Relaxed; }
+        }
+
+        private bool StrictCompatible
+        {
+            get { return CompatibilityLevel >= Compatibility.Strict; }
         }
 
         private static int ParseDecInt(string str)
             var value = float.Parse(str.Substring(1));
             var bytes = BitConverter.GetBytes(value);
 
-            /*if (BitConverter.IsLittleEndian)
-            {
-                byte a = bytes[0], b = bytes[1], c = bytes[2], d = bytes[3];
-                bytes[0] = d;
-                bytes[1] = c;
-                bytes[2] = b;
-                bytes[3] = a;
-            }*/
-
             return BitConverter.ToInt32(bytes, 0);
         }
 
             return tk == kw1 || tk == kw2 || tk == kw3 || tk == kw4;
         }
 
-        private void DefineSymbol(IScope scope, SymbolType type, ITree identifier, ITree value = null)
+        private Symbol DefineSymbol(IScope scope, SymbolType type, RellorToken token, ITree definition = null)
         {
             Contract.Requires(scope != null);
-            Contract.Requires(identifier is CommonTree);
+            Contract.Requires(token != null);
 
-            IToken token = ((CommonTree)identifier).Token;
-            Symbol symbol = new Symbol()
+            string name = token.Text;
+
+            Symbol existing = scope.Resolve(name);
+            if (existing != null)
             {
-                Name = token.Text,
-                Defined = true,
-                Declaration = identifier,
-                Type = type,
-                Value = value,
-            };
-            scope.Define(token.Text, symbol);
+                if (existing.Defined)
+                {
+                    logger.Log(CoreMessages.SymbolAlreadyDefined, token, token.Text, Messages.FormatSymbolType(existing.Type));
+                    logger.Log(CoreMessages.SymbolAlreadyDefined_PrevDef, existing.DefiningToken);
+                }
+                else
+                {
+                    existing.Defined = true;
+                    existing.Definition = definition;
+                }
+                return existing;
+            }
+            else
+            {
+                Symbol symbol = new Symbol()
+                {
+                    Name = token.Text,
+                    Defined = true,
+                    Definition = definition,
+                    DefiningToken = token,
+                    Type = type,
+                };
+                scope.Define(token.Text, symbol);
+                return symbol;
+            }
+        }
+
+        private bool TestSymbol(IScope scope, RellorToken token, Predicate<Symbol> predicate)
+        {
+            Contract.Requires(scope != null);
+            Contract.Requires(token != null);
+
+            string name = token.Text;
+            Symbol symbol = scope.Resolve(name);
+            return predicate(symbol);
+        }
+
+        private void ValidateSymbolIfDefined(IScope scope, RellorToken token, Predicate<Symbol> predicate,
+            Action onFailure)
+        {
+            if (TestSymbol(scope, token, s => s == null || predicate(s)) == false)
+                onFailure();
         }
 
         protected override object GetMissingSymbol(IIntStream input,
             adaptor = new RellorTreeAdaptor();
         }
 
-        private void IncludeFile(string filespec)
+        private bool IncludeFile(string filespec)
         {
-            //XXX include file
-            var stream = new ANTLRStringStream(filespec);
-            InsertTokens(stream, "literal");
+            var handler = this.IncludeStreamNeeded;
+
+            if (handler != null)
+            {
+                try
+                {
+                    var args = new IncludeStreamEventArgs(filespec);
+                    handler(this, args);
+
+                    if (args.Stream != null)
+                    {
+                        InsertTokens(args.Stream, args.FileName);
+                        return true;
+                    }
+                }
+                catch (Exception)
+                {
+                    return false;
+                }
+            }
+
+            return false;
         }
 
         private void InsertTokens(ICharStream charStream, string sourceName)

Rellor.Core/Messages.cs

 using System.Diagnostics.Contracts;
 using System.ComponentModel;
 using Rellor.Core.Token;
+using System.Reflection;
 
 namespace Rellor.Core
 {
         Notice,
         /// <summary>
         /// Suggestions on how to address more severe messages, such as how to suppress a warning if the
-        /// suspicious meaning is really intended. Protips are automatically suppressed in certain
-        /// circumstances to avoid repeating the same tip for the same error.
+        /// suspicious meaning is really intended. Also used for references to other entities involved
+        /// in a message, such as a duplicate definition of a symbol. Protips are automatically
+        /// suppressed in certain circumstances to avoid repeating the same tip for the same error.
         /// </summary>
         Protip,
         /// <summary>
         NoMainRoutine,
         [Msg(0003, LogLevel.Error, "this Inform construct is not supported at all in Rellor")]
         NotSupported,
-        [Msg(0004, LogLevel.Error, "this Inform construct is only supported in Inform compatibility mode")]
+        [Msg(0004, LogLevel.Error, "this Inform construct ('{0}') is only supported in Inform compatibility mode")]
         CompatibilityModeRequired,
-        [Msg(0005, LogLevel.Error, "this Rellor extension is only supported in extended mode")]
+        [Msg(0005, LogLevel.Error, "this Rellor extension ('{0}') is only supported in extended mode")]
         ExtendedModeRequired,
+        [Msg(0006, LogLevel.Fatal, "too many errors, terminating")]
+        TooManyErrors,
 
         // 0500 lexical issues
         [Msg(0500, LogLevel.Error, "unexpected character '{0}'")]
         SemicolonUsedInFor,
 
         // 1500 symbol usage: duplicate, undefined, unused
-        [Msg(1501, LogLevel.Error, "symbol '{0}' has already been defined as a '{1}'")]
+        [Msg(1501, LogLevel.Error, "symbol '{0}' has already been defined as {1}")]
         SymbolAlreadyDefined,
-        [Msg(1502, LogLevel.Protip, "...previous definition is up here", Continues = 1501)]
-        PrevDefinitionHere,
+        [Msg(1502, LogLevel.Protip, "...previous definition was here", Continues = 1501)]
+        SymbolAlreadyDefined_PrevDef,
         [Msg(1503, LogLevel.Error, "symbol '{0}' is used but never defined")]
         SymbolNotDefined,
-        [Msg(1504, LogLevel.Error, "symbol '{0}' must be defined before it is used")]
-        SymbolNotDefinedYet,
-        [Msg(1505, LogLevel.Protip, "...but the definition is down here", Continues = 1504)]
-        LateDefinitionHere,
-        [Msg(1506, LogLevel.Notice, "symbol '{0}' is defined but never used")]
+        [Msg(1504, LogLevel.Notice, "symbol '{0}' is defined but never used")]
         SymbolUnused,
-        [Msg(1507, LogLevel.Error, "symbol '{0}' cannot be used in its own definition")]
+        [Msg(1505, LogLevel.Error, "symbol '{0}' cannot be used in its own definition")]
         SymbolRecursive,
 
         // 2000 vocab/grammar: dict collisions, verb semantics, action subs
         [Msg(2000, LogLevel.Warning, "the following dictionary words are truncated to '{0}':")]
         DictWordCollision,
         [Msg(2001, LogLevel.Warning, "...'{0}'", Continues = 2000)]
-        DictWordCollisionOriginal,
+        DictWordCollision_Original,
         [Msg(2002, LogLevel.Protip, "try increasing DICT_WORD_LENGTH to avoid truncation", Continues = 2000, OneShot = true)]
-        ProtipDictWordTruncated,
+        DictWordCollision_IncreaseLength,
         [Msg(2003, LogLevel.Error, "action '{0}' has no matching routine '{0}Sub'")]
         ActionSubMissing,
-        [Msg(2004, LogLevel.Protip, "it's not used by any verbs, consider using Fake_Action", Continues = 2003, OneShot = true)]
-        ProtipActionSubMissingFake,
+        [Msg(2004, LogLevel.Protip, "it's not used by any verbs... typo? Fake_Action?", Continues = 2003, OneShot = true)]
+        ActionSubMissing_NoVerb,
 
         // 2500 text: character sets, escape codes
 
         // 3000 objects: object tree, inheritance, segment semantics
+        [Msg(3000, LogLevel.Error, "'{0}' has already been used as an individual property and cannot be made common now")]
+        PropertyAlreadyIndividual,
+        [Msg(3001, LogLevel.Protip, "...first use as a property was here", Continues = 3000)]
+        PropertyAlreadyIndividual_PrevDef,
 
-        // 3500 directives (besides objects, text, routines)
+        // 3500 directives (besides objects, text)
         [Msg(3500, LogLevel.Error, "'{0}' without matching 'If'")]
         HeadlessIfDirective,
         [Msg(3501, LogLevel.Error, "this '{0}' directive never terminates")]
         RunawayIfDirective,
-        [Msg(3502, LogLevel.Error, "directive '{0}' may not be used inside a routine")]
-        DirectiveNotNestable,
+        [Msg(3502, LogLevel.Error, "routines may not be nested with '#['")]
+        RoutinesNotNestable,
         [Msg(3503, LogLevel.Error, "directive '{0}' not supported by target '{1}'")]
         DirectiveNotSupportedByTarget,
         [Msg(3504, LogLevel.Error, "include file '{0}' not found")]
         IncludeNotFound,
+        [Msg(3505, LogLevel.Error, "serial must be 6 characters")]
+        SerialWrongLength,
+
 
         // 4000 statements
         [Msg(4000, LogLevel.Error, "statement '{0}' not supported by target '{1}'")]
         // 4500 expressions
         [Msg(4500, LogLevel.Error, "expression here must be a compile-time constant")]
         ExprNotConstant,
-        [Msg(4501, LogLevel.Protip, "it isn't because it contains '{0}'", Continues = 4500)]
-        ProtipExprNotConstantBecause,
+        [Msg(4501, LogLevel.Protip, "it isn't because it contains {0}", Continues = 4500)]
+        ExprNotConstant_Reason,
         [Msg(4502, LogLevel.Warning, "'=' used in a condition context, did you mean '=='?")]
         AssignmentAsCondition,
         [Msg(4503, LogLevel.Protip, "explicitly compare the result to 0 if that's your intent", Continues = 4502)]
-        ProtipAssignmentAsConditionIntended,
-        [Msg(4504, LogLevel.Error, "'or' may only appear on the right-hand side of '==' or '~=', did you mean '||'?")]
+        AssignmentAsCondition_ExplicitCompare,
+        [Msg(4504, LogLevel.Error, "'or' may only appear on the right-hand side of '==' or '~='")]
         MisplacedOrOperator,
+        [Msg(4505, LogLevel.Protip, "did you mean '||'?", Continues = 4504)]
+        MisplacedOrOperator_Protip,
     }
 
     [ContractClass(typeof(ILogSinkContract))]
 
         public void CountSuppressedMessage(LogLevel level)
         {
+            // nada
         }
     }
 
         {
             Contract.Requires(token != null);
 
+            LogCore(key, token, s => s);
+        }
+
+        public void Log(T key, RellorToken token, object arg0)
+        {
+            Contract.Requires(token != null);
+            Contract.Requires(arg0 != null);
+
+            LogCore(key, token, s => string.Format(s, arg0));
+        }
+
+        public void Log(T key, RellorToken token, object arg0, object arg1)
+        {
+            Contract.Requires(token != null);
+            Contract.Requires(arg0 != null);
+            Contract.Requires(arg1 != null);
+
+            LogCore(key, token, s => string.Format(s, arg0, arg1));
+        }
+
+        public void Log(T key, RellorToken token, params string[] args)
+        {
+            Contract.Requires(token != null);
+            Contract.Requires(args != null);
+
+            LogCore(key, token, s => string.Format(s, args));
+        }
+
+        private void LogCore(T key, RellorToken token, Func<string, string> textFormatter)
+        {
+            Contract.Requires(token != null);
+            Contract.Requires(textFormatter != null);
+
             var msg = attributes[key];
             var code = msg.Code;
             var level = msg.Level;
+            var text = textFormatter(msg.Text);
 
             bool isSuppressed = false;
 
-            string textWithoutSource = null;
+            string formattedWithoutSource = null;
 
             if (level < minLevel || suppressed.Contains(code) || (msg.Continues > 0 && suppressed.Contains(msg.Continues)))
             {
             }
             else if (msg.Level == LogLevel.Protip)
             {
-                textWithoutSource = FormatMessage(null, level, prefix, code, msg.Text);
-                string checkMsg = textWithoutSource;
+                formattedWithoutSource = Messages.FormatMessage(null, level, prefix, code, text);
+                string checkMsg = formattedWithoutSource;
 
                 if (msg.Continues > 0)
                     checkMsg = lastNonContinuationText + checkMsg;
 
             if (msg.Continues == 0)
             {
-                if (textWithoutSource == null)
-                    textWithoutSource = FormatMessage(null, level, prefix, code, msg.Text);
+                if (formattedWithoutSource == null)
+                    formattedWithoutSource = Messages.FormatMessage(null, level, prefix, code, text);
 
-                lastNonContinuationText = textWithoutSource;
+                lastNonContinuationText = formattedWithoutSource;
             }
 
-            string text = FormatMessage(token, level, prefix, code, msg.Text);
-            sink.LogMessage(level, text, true);
+            string formatted = Messages.FormatMessage(token, level, prefix, code, text);
+            sink.LogMessage(level, formatted, true);
 
             if (msg.OneShot)
                 suppressed.Add(code);
         }
+    }
 
-        private static string GetLevelStr(LogLevel level)
+    public static class Messages
+    {
+        private static readonly Dictionary<LogLevel, string> logLevelNames;
+        private static readonly Dictionary<SymbolType, string> symbolTypeNames;
+
+        static Messages()
         {
-            switch (level)
+            logLevelNames = DictFromEnum<LogLevel>(fi => fi.Name.ToLower());
+
+            symbolTypeNames = DictFromEnum<SymbolType>(fi =>
             {
-                case LogLevel.Debug:
-                    return "debug";
-                case LogLevel.Error:
-                    return "error";
-                case LogLevel.Fatal:
-                    return "fatal";
-                case LogLevel.Info:
-                    return "info";
-                case LogLevel.Notice:
-                    return "notice";
-                case LogLevel.Protip:
-                    return "protip";
-                case LogLevel.Verbose:
-                    return "verbose";
-                case LogLevel.Warning:
-                    return "warning";
-                default:
-                    throw new ArgumentException("invalid level");
-            }
+                var attr = (DescriptionAttribute)fi.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault();
+                return (attr == null) ? fi.Name.ToLower() : attr.Description;
+            });
         }
 
-        private string FormatMessage(RellorToken token, LogLevel level, string prefix, int code, string text)
+        private static Dictionary<T, string> DictFromEnum<T>(Func<FieldInfo, string> getName)
+        {
+            Contract.Requires(typeof(T).IsEnum);
+            Contract.Requires(getName != null);
+
+            var result = new Dictionary<T, string>();
+
+            foreach (var fi in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static))
+            {
+                T key = (T)fi.GetValue(null);
+                string name = getName(fi);
+                result.Add(key, name);
+            }
+
+            return result;
+        }
+
+        public static string FormatMessage(RellorToken token, LogLevel level, string prefix, int code, string text)
         {
             Contract.Requires(prefix != null);
             Contract.Requires(!string.IsNullOrEmpty(text));
             Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
 
             if (token == null)
-                return string.Format("{0} {1}{2}: {3}", GetLevelStr(level), prefix, code, text);
+                return string.Format("{0} {1}{2}: {3}", Messages.FormatLevel(level), prefix, code, text);
             else
                 return string.Format("{0}:{1}:{2}: {3} {4}{5}: {6}",
-                    token.InputStream.SourceName, token.Line, token.CharPositionInLine,
-                    GetLevelStr(level), prefix, code, text);
+                    token.InputStream.SourceName, token.Line, token.CharPositionInLine + 1,
+                    Messages.FormatLevel(level), prefix, code, text);
+        }
+
+        public static string FormatLevel(LogLevel level)
+        {
+            return logLevelNames[level];
+        }
+
+        public static string FormatSymbolType(SymbolType stype)
+        {
+            return symbolTypeNames[stype];
         }
     }
 }

Rellor.Core/Rellor.Core.csproj

     <Compile Include="SpecializeOperators.g3.cs">
       <DependentUpon>SpecializeOperators.g3</DependentUpon>
     </Compile>
+    <Compile Include="Tree\SymbolTree.cs" />
     <Compile Include="Vocab\GrammarLine.cs" />
     <Compile Include="Vocab\Verb.cs" />
     <Compile Include="Vocab\Word.cs" />

Rellor.Core/Symbol.cs

 using System.Text;
 using Antlr.Runtime.Tree;
 using System.ComponentModel;
+using Rellor.Core.Token;
 
 namespace Rellor.Core
 {
         Object,
         Class,
         Routine,
-        Property,
+        CommonProperty,
+        IndivProperty,
         Attribute,
         [Description("global variable")]
         GlobalVar,
 
         public string Name { get; set; }
         public SymbolType Type { get; set; }
-        public ITree Declaration { get; set; }
+        public RellorToken FirstReference { get; set; }
+        public RellorToken DefiningToken { get; set; }
+        public ITree Definition { get; set; }
         public bool Defined { get; set; }
         public bool Referenced { get; set; }
         public object Value { get; set; }

Rellor.Core/Tree/SymbolTree.cs

+using System.Diagnostics.Contracts;
+using Antlr.Runtime;
+using Antlr.Runtime.Tree;
+using Rellor.Core.Token;
+
+namespace Rellor.Core.Tree
+{
+    public class SymbolTree : CommonTree
+    {
+        public Symbol Symbol { get; private set; }
+
+        public SymbolTree(int ttype, Symbol symbol)
+            : this(ttype, new RellorToken(ttype), symbol)
+        {
+            Contract.Requires(symbol != null);
+        }
+
+        public SymbolTree(int ttype, IToken token, Symbol symbol)
+            : base(token.Type == ttype ? token : new RellorToken(token) { Type = ttype })
+        {
+            Contract.Requires(token != null);
+            Contract.Requires(symbol != null);
+
+            this.Symbol = symbol;
+        }
+    }
+}

RellorC/Program.cs

         static void Main(string[] args)
         {
             const string SText =
-@"[ Main; for(;;) print ""RELLOR""; ];";
+@"Include ""asdasjdas""; [ Main; #include ""dsfsagrg"";
+#[ foo; ];
+for(;;) print ""RELLOR""; ];";
 /*@"
 #include ""Global blah = 1;"";
 Array foo -> 1 2 3;