void PrepareConstantBuilders([CanBeNull] ZilModelObject lastObject) { // builders and values for constants (which may refer to vocabulary, // routines, tables, objects, properties, or flags) foreach (var constant in Context.ZEnvironment.Constants) { IOperand value; if (constant.Name.StdAtom == StdAtom.LAST_OBJECT && lastObject != null) { value = Objects[lastObject.Name]; } else { value = CompileConstant(constant.Value); } if (value == null) { Context.HandleError(new CompilerError( constant, CompilerMessages.Nonconstant_Initializer_For_0_1_2, "constant", constant.Name, constant.Value.ToStringContext(Context, false))); value = Game.Zero; } Constants.Add(constant.Name, Game.DefineConstant(constant.Name.Text, value)); } }
void PrepareObjectBuilders([CanBeNull] out ZilModelObject lastObject) { // builders for objects lastObject = null; string GetGlobalSymbolType(ZilAtom atom) => Game.IsGloballyDefined(atom.Text, out var type) ? type : null; foreach (var obj in Context.ZEnvironment.ObjectsInDefinitionOrder(GetGlobalSymbolType)) { lastObject = obj; Objects.Add(obj.Name, Game.DefineObject(obj.Name.Text)); // builders for the rest of the properties and flags, // and vocabulary for names PreBuildObject(obj); } }
/// <summary> /// Looks through a <see cref="ZilModelObject"/>'s property definitions, creating the /// property builders, flag builders, and vocab word builders that it will need. /// </summary> /// <remarks> /// <para>This does not create the <see cref="IObjectBuilder"/> or add any data to it.</para> /// <para>It does, however, call the <c>PROPSPEC</c> routines for any properties that have /// custom handlers installed, and replaces the corresponding property definitions with /// whatever the handler returned.</para> /// </remarks> /// <param name="model">The object to examine.</param> void PreBuildObject([NotNull] ZilModelObject model) { var globalsByName = Context.ZEnvironment.Globals.ToDictionary(g => g.Name); var propertiesSoFar = new HashSet <ZilAtom>(); var preBuilders = new ComplexPropDef.ElementPreBuilders { CreateVocabWord = (atom, partOfSpeech, src) => { // ReSharper disable once SwitchStatementMissingSomeCases switch (partOfSpeech.StdAtom) { case StdAtom.ADJ: case StdAtom.ADJECTIVE: Context.ZEnvironment.GetVocabAdjective(atom, src); break; case StdAtom.NOUN: case StdAtom.OBJECT: Context.ZEnvironment.GetVocabNoun(atom, src); break; case StdAtom.BUZZ: Context.ZEnvironment.GetVocabBuzzword(atom, src); break; case StdAtom.PREP: Context.ZEnvironment.GetVocabPreposition(atom, src); break; case StdAtom.DIR: Context.ZEnvironment.GetVocabDirection(atom, src); break; case StdAtom.VERB: Context.ZEnvironment.GetVocabVerb(atom, src); break; default: Context.HandleError(new CompilerError(model, CompilerMessages.Unrecognized_0_1, "part of speech", partOfSpeech)); break; } }, ReserveGlobal = atom => { if (globalsByName.TryGetValue(atom, out var g)) { g.StorageType = GlobalStorageType.Hard; } } }; // for detecting implicitly defined directions var directionPattern = Context.GetProp( Context.GetStdAtom(StdAtom.DIRECTIONS), Context.GetStdAtom(StdAtom.PROPSPEC)) as ComplexPropDef; // create property builders for all properties on this object as needed, // and set up P?FOO constants for them. also create vocabulary words for // SYNONYM and ADJECTIVE property values, and constants for FLAGS values. foreach (var prop in model.Properties) { using (DiagnosticContext.Push(prop.SourceLine)) { // the first element must be an atom identifying the property if (!prop.IsCons(out var first, out var propBody) || !(first is ZilAtom atom)) { Context.HandleError(new CompilerError(model, CompilerMessages.Property_Specification_Must_Start_With_An_Atom)); continue; } ZilAtom uniquePropertyName; // exclude phony built-in properties bool phony; bool? isSynonym = null; Synonym synonym = null; /* We also detect direction properties here, which are tricky for a few reasons: * - They can be implicitly defined by a property spec that looks sufficiently direction-like. * - (IN ROOMS) is not a direction, even if IN has been explicitly defined as a direction... * but (IN "string") is! * - (FOO BAR) is not enough to implicitly define FOO as a direction, even if (DIR R:ROOM) * is a pattern for directions. * * Thus, there are a few ways to write a property that ZILF will recognize as a direction. * * If the property name has already been defined as one (e.g. by <DIRECTIONS>), you can either: * - Put two or more values after the property name: (NORTH TO FOREST), (NORTH 123 456) * - Put one value after the property name that isn't an atom: (NORTH "You can't go that way.") * * If it hasn't been defined as a direction, you can still implicitly define it right here: * - Put two or more values after the property name, *and* match the PROPDEF for DIRECTIONS: * (STARBOARD TO BRIG), (PORT SORRY "You can't jump that far.") */ var isKnownDirectionName = Context.ZEnvironment.Directions.Contains(atom); var isDirectionProp = isKnownDirectionName ? propBody.HasLengthAtLeast(2) || !(propBody.IsEmpty || propBody.First is ZilAtom) : propBody.HasLengthAtLeast(2) && directionPattern?.Matches(Context, prop) == true; if (isDirectionProp) { // it's a direction phony = false; // could be a new implicitly defined direction if (!isKnownDirectionName) { synonym = Context.ZEnvironment.Synonyms.FirstOrDefault(s => s.SynonymWord.Atom == atom); if (synonym == null) { isSynonym = false; Context.ZEnvironment.Directions.Add(atom); Context.ZEnvironment.GetVocabDirection(atom, prop.SourceLine); Context.SetPropDef(atom, directionPattern); uniquePropertyName = atom; } else { isSynonym = true; uniquePropertyName = synonym.OriginalWord.Atom; } } else { uniquePropertyName = atom; } } else { // ReSharper disable once SwitchStatementMissingSomeCases switch (atom.StdAtom) { case StdAtom.DESC: phony = true; uniquePropertyName = PseudoPropertyAtoms.Desc; break; case StdAtom.IN: // (IN FOO) is a location, but (IN "foo") is a property if (propBody.First is ZilAtom) { goto case StdAtom.LOC; } goto default; case StdAtom.LOC: phony = true; uniquePropertyName = PseudoPropertyAtoms.Location; break; case StdAtom.FLAGS: phony = true; // multiple FLAGS definitions are OK uniquePropertyName = null; break; default: phony = false; uniquePropertyName = atom; break; } } if (uniquePropertyName != null) { if (propertiesSoFar.Contains(uniquePropertyName)) { Context.HandleError(new CompilerError( prop, CompilerMessages.Duplicate_0_Definition_1, phony ? "pseudo-property" : "property", atom.ToStringContext(Context, false))); } else { propertiesSoFar.Add(uniquePropertyName); } } if (!phony && !Properties.ContainsKey(atom)) { if (isSynonym == null) { synonym = Context.ZEnvironment.Synonyms.FirstOrDefault(s => s.SynonymWord.Atom == atom); isSynonym = (synonym != null); } if ((bool)isSynonym) { var origAtom = synonym.OriginalWord.Atom; if (Properties.TryGetValue(origAtom, out var origPb) == false) { DefineProperty(origAtom); origPb = Properties[origAtom]; } Properties.Add(atom, origPb); var pAtom = ZilAtom.Parse("P?" + atom.Text, Context); Constants.Add(pAtom, origPb); var origSpec = Context.GetProp(origAtom, Context.GetStdAtom(StdAtom.PROPSPEC)); Context.PutProp(atom, Context.GetStdAtom(StdAtom.PROPSPEC), origSpec); } else { DefineProperty(atom); } } // check for a PROPSPEC var propspec = Context.GetProp(atom, Context.GetStdAtom(StdAtom.PROPSPEC)); if (propspec != null) { if (propspec is ComplexPropDef complexDef) { // PROPDEF pattern if (complexDef.Matches(Context, prop)) { complexDef.PreBuildProperty(Context, prop, preBuilders); } } else { // name of a custom property builder function var form = new ZilForm(new[] { propspec, prop }) { SourceLine = prop.SourceLine }; var specOutput = (ZilObject)form.Eval(Context); if (specOutput is ZilListoidBase list && list.StdTypeAtom == StdAtom.LIST && list.Rest is var customBody && !customBody.IsEmpty) { // replace the property body with the propspec's output prop.Rest = customBody; } else { Context.HandleError(new CompilerError(model, CompilerMessages.PROPSPEC_For_Property_0_Returned_A_Bad_Value_1, atom, specOutput)); } } }