Exemple #1
0
        IOperand GetGlobalDefaultValue([NotNull] ZilGlobal global)
        {
            if (global.Value == null)
            {
                return(null);
            }

            IOperand result = null;

            try
            {
                using (DiagnosticContext.Push(global.SourceLine))
                {
                    result = CompileConstant(global.Value, AmbiguousConstantMode.Optimistic);
                    if (result == null)
                    {
                        Context.HandleError(new CompilerError(
                                                global,
                                                CompilerMessages.Nonconstant_Initializer_For_0_1_2,
                                                "global",
                                                global.Name,
                                                global.Value.ToStringContext(Context, false)));
                    }
                }
            }
            catch (ZilError ex)
            {
                Context.HandleError(ex);
            }

            return(result);
        }
Exemple #2
0
        void PreparePropertyDefaults()
        {
            // default values for properties
            foreach (var pair in Context.ZEnvironment.PropertyDefaults)
            {
                try
                {
                    using (DiagnosticContext.Push(
                               pair.Value.SourceLine ??
                               new StringSourceLine($"<property default for '{pair.Key}'>")))
                    {
                        var pb = Properties[pair.Key];

                        pb.DefaultValue = CompileConstant(pair.Value);

                        if (pb.DefaultValue == null)
                        {
                            throw new CompilerError(
                                      CompilerMessages.Nonconstant_Initializer_For_0_1_2,
                                      "property default",
                                      pair.Key,
                                      pair.Value.ToStringContext(Context, false));
                        }
                    }
                }
                catch (ZilError ex)
                {
                    Context.HandleError(ex);
                }
            }
        }
Exemple #3
0
        Action ValidateAction([NotNull] Dictionary <ZilAtom, Action> actions, [NotNull] Syntax line)
        {
            try
            {
                using (DiagnosticContext.Push(line.SourceLine))
                {
                    if (actions.TryGetValue(line.ActionName, out var act) == false)
                    {
                        if (Routines.TryGetValue(line.Action, out var routine) == false)
                        {
                            throw new CompilerError(CompilerMessages.Undefined_0_1, "action routine", line.Action);
                        }

                        IRoutineBuilder preRoutine = null;
                        if (line.Preaction != null &&
                            Routines.TryGetValue(line.Preaction, out preRoutine) == false)
                        {
                            throw new CompilerError(CompilerMessages.Undefined_0_1, "preaction routine", line.Preaction);
                        }

                        var actionName = line.ActionName;
                        int index      = Context.ZEnvironment.NextAction++;

                        if (index >= Context.ZEnvironment.VocabFormat.MaxActionCount)
                        {
                            throw new InterpreterError(
                                      InterpreterMessages.Too_Many_0_Only_1_Allowed_In_This_Vocab_Format,
                                      "actions",
                                      Context.ZEnvironment.VocabFormat.MaxActionCount);
                        }

                        var number   = Game.MakeOperand(index);
                        var constant = Game.DefineConstant(actionName.Text, number);
                        Constants.Add(actionName, constant);
                        if (WantDebugInfo)
                        {
                            Debug.Assert(Game.DebugFile != null);
                            Game.DebugFile.MarkAction(constant, actionName.Text);
                        }

                        act = new Action(index, constant, routine, preRoutine, line.Action, line.Preaction);
                        actions.Add(actionName, act);
                    }
                    else
                    {
                        WarnIfActionRoutineDiffers(line, "action routine", line.Action, act.RoutineName);
                        WarnIfActionRoutineDiffers(line, "preaction routine", line.Preaction, act.PreRoutineName);
                    }

                    return(act);
                }
            }
            catch (ZilError ex)
            {
                Context.HandleError(ex);
                return(null);
            }
        }
Exemple #4
0
        internal void ExpandInPlace([NotNull] Context ctx)
        {
            IEnumerable <ZilObject> RecursiveExpandWithSplice(ZilObject zo)
            {
                ZilObject result;

                ZilObject SetSourceLine(ZilResult zr)
                {
                    var newObj = (ZilObject)zr;

                    newObj.SourceLine = zo.SourceLine;
                    return(newObj);
                }

                switch (zo)
                {
                case ZilList list:
                    result = new ZilList(list.SelectMany(RecursiveExpandWithSplice));
                    break;

                case ZilVector vector:
                    result = new ZilVector(vector.SelectMany(RecursiveExpandWithSplice).ToArray());
                    break;

                case ZilForm form:
                    ZilObject expanded;
                    try
                    {
                        using (DiagnosticContext.Push(form.SourceLine))
                        {
                            expanded = (ZilObject)form.Expand(ctx);
                        }
                    }
                    catch (InterpreterError ex)
                    {
                        ctx.HandleError(ex);
                        return(new[] { ctx.FALSE });
                    }
                    if (expanded is IMayExpandAfterEvaluation expandAfter &&
                        expandAfter.ShouldExpandAfterEvaluation)
                    {
                        return(expandAfter.ExpandAfterEvaluation().AsResultSequence()
                               .Select(SetSourceLine)
                               .Select(xo => ReferenceEquals(xo, form) ? xo : new ZilMacroResult(xo)));
                    }
                    else if (!ReferenceEquals(expanded, form))
                    {
                        expanded.SourceLine = zo.SourceLine;
                        return(RecursiveExpandWithSplice(expanded)
                               .Select(xo => new ZilMacroResult(xo)));
                    }
                    else
                    {
                        result = new ZilForm(form.SelectMany(RecursiveExpandWithSplice));
                    }
                    break;
Exemple #5
0
 void BuildObjects()
 {
     // build objects
     foreach (var obj in Context.ZEnvironment.ObjectsInInsertionOrder())
     {
         var ob = Objects[obj.Name];
         try
         {
             using (DiagnosticContext.Push(obj.SourceLine))
             {
                 BuildObject(obj, ob);
             }
         }
         catch (ZilError ex)
         {
             Context.HandleError(ex);
         }
     }
 }
Exemple #6
0
        public static ZilObject Evaluate([NotNull] Context ctx, [NotNull] IEnumerable <char> chars, bool wantExceptions = false)
        {
            try
            {
                var ztree = Parse(ctx, chars);

                ZilObject result = null;
                bool      first  = true;
                foreach (var node in ztree)
                {
                    try
                    {
                        using (DiagnosticContext.Push(node.SourceLine))
                        {
                            if (first)
                            {
                                // V4 games can identify themselves this way instead of using <VERSION EZIP>
                                if (node is ZilString str &&
                                    str.Text.StartsWith("EXTENDED", StringComparison.Ordinal) &&
                                    ctx.ZEnvironment.ZVersion == 3)
                                {
                                    ctx.SetZVersion(4);
                                }

                                first = false;
                            }
                            result = (ZilObject)node.Eval(ctx);
                        }
                    }
                    catch (InterpreterError ex) when(wantExceptions == false)
                    {
                        ctx.HandleError(ex);
                    }
                }

                return(result);
            }
            catch (InterpreterError ex) when(wantExceptions == false)
            {
                ctx.HandleError(ex);
                return(null);
            }
        }
Exemple #7
0
        public static ZilObject Unwrap([NotNull] this ZilObject zo, [NotNull] Context ctx)
        {
            var src = zo.SourceLine;

            using (DiagnosticContext.Push(src))
            {
                while (true)
                {
                    zo = (ZilObject)zo.Expand(ctx);

                    switch (zo)
                    {
                    case ZilForm form when form.IsEmpty:
                        return(ctx.FALSE);

                    case ZilAdecl adecl:
                        // TODO: check DECL
                        zo = adecl.First;
                        break;

                    case IMayExpandAfterEvaluation expandAfter when expandAfter.ShouldExpandAfterEvaluation:
                        // TODO: don't use Parse here
                        zo = expandAfter.ExpandAfterEvaluation()
                             .FirstOrCombine(zos =>
                                             Program.Parse(ctx, src, "<BIND () {0:SPLICE}>", new ZilList(zos))
                                             .Single());
                        break;

                    case ZilMacroResult macroResult:
                        zo = macroResult.Inner;
                        break;

                    default:
                        return(zo);
                    }
                }
            }
        }
Exemple #8
0
        void GenerateRoutineCode()
        {
            // compile routines
            IRoutineBuilder mainRoutine = null;

            foreach (var routine in Context.ZEnvironment.Routines)
            {
                var entryPoint = routine.Name == Context.ZEnvironment.EntryRoutineName;
                Debug.Assert(routine.Name != null);
                Debug.Assert(Routines.ContainsKey(routine.Name));
                var rb = Routines[routine.Name];
                try
                {
                    using (DiagnosticContext.Push(routine.SourceLine))
                    {
                        BuildRoutine(routine, rb, entryPoint, Context.TraceRoutines);
                    }
                }
                catch (ZilError ex)
                {
                    // could be a compiler error, or an interpreter error thrown by macro evaluation
                    Context.HandleError(ex);
                }
                rb.Finish();

                if (entryPoint)
                {
                    mainRoutine = rb;
                }
            }

            if (mainRoutine == null)
            {
                throw new CompilerError(CompilerMessages.Missing_GO_Routine);
            }
        }
Exemple #9
0
        void BuildOldFormatSyntaxTables([NotNull] IDictionary <string, ITableBuilder> tables)
        {
            // TODO: emit VTBL as the first impure table, followed by syntax lines, which is what ztools expects?
            var verbTable      = Game.DefineTable("VTBL", true);
            var actionTable    = Game.DefineTable("ATBL", true);
            var preactionTable = Game.DefineTable("PATBL", true);

            tables.Add("VTBL", verbTable);
            tables.Add("ATBL", actionTable);
            tables.Add("PATBL", preactionTable);

            // compact syntaxes?
            var compact = Context.GetGlobalOption(StdAtom.COMPACT_SYNTAXES_P);

            var vf = Context.ZEnvironment.VocabFormat;

            // verb table
            var query = from s in Context.ZEnvironment.Syntaxes
                        group s by s.Verb into g
                        orderby vf.GetVerbValue(g.Key) descending
                        select g;

            var actions = new Dictionary <ZilAtom, Action>();

            foreach (var verb in query)
            {
                // syntax table
                var stbl = Game.DefineTable("ST?" + verb.Key.Atom, true);
                verbTable.AddShort(stbl);

                stbl.AddByte((byte)verb.Count());

                // make two passes over the syntax line definitions:
                // first in definition order to create/validate the Actions, second in reverse order to emit the syntax lines
                foreach (var line in verb)
                {
                    ValidateAction(actions, line);
                }

                foreach (var line in verb.Reverse())
                {
                    if (actions.TryGetValue(line.ActionName, out var act) == false)
                    {
                        // this can happen if an exception (e.g. undefined action routine) stops us from adding the action during the first pass.
                        continue;
                    }

                    try
                    {
                        using (DiagnosticContext.Push(line.SourceLine))
                        {
                            if (compact)
                            {
                                if (line.Preposition1 != null)
                                {
                                    var pn = vf.GetPrepositionValue(line.Preposition1);
                                    stbl.AddByte((byte)((pn & 63) | (line.NumObjects << 6)));
                                }
                                else
                                {
                                    stbl.AddByte((byte)(line.NumObjects << 6));
                                }
                                stbl.AddByte(act.Constant);

                                if (line.NumObjects > 0)
                                {
                                    stbl.AddByte((IOperand)GetFlag(line.FindFlag1) ?? Game.Zero);
                                    stbl.AddByte(line.Options1);

                                    if (line.NumObjects > 1)
                                    {
                                        if (line.Preposition2 != null)
                                        {
                                            var pn = vf.GetPrepositionValue(line.Preposition2);
                                            stbl.AddByte((byte)(pn & 63));
                                        }
                                        else
                                        {
                                            stbl.AddByte(0);
                                        }

                                        stbl.AddByte((IOperand)GetFlag(line.FindFlag2) ?? Game.Zero);
                                        stbl.AddByte(line.Options2);
                                    }
                                }
                            }
                            else
                            {
                                stbl.AddByte((byte)line.NumObjects);
                                stbl.AddByte(GetPreposition(line.Preposition1) ?? Game.Zero);
                                stbl.AddByte(GetPreposition(line.Preposition2) ?? Game.Zero);
                                stbl.AddByte((IOperand)GetFlag(line.FindFlag1) ?? Game.Zero);
                                stbl.AddByte((IOperand)GetFlag(line.FindFlag2) ?? Game.Zero);
                                stbl.AddByte(line.Options1);
                                stbl.AddByte(line.Options2);
                                stbl.AddByte(act.Constant);
                            }
                        }
                    }
                    catch (ZilError ex)
                    {
                        Context.HandleError(ex);
                    }
                }
            }

            // action and preaction table
            var actquery = from a in actions
                           orderby a.Value.Index
                           select a.Value;

            foreach (var act in actquery)
            {
                actionTable.AddShort(act.Routine);
                preactionTable.AddShort((IOperand)act.PreRoutine ?? Game.Zero);
            }
        }
Exemple #10
0
        internal IOperand CompileForm([NotNull] IRoutineBuilder rb, [NotNull] ZilForm form, bool wantResult,
                                      IVariable resultStorage)
        {
            using (DiagnosticContext.Push(form.SourceLine))
            {
                var unwrapped = form.Unwrap(Context);

                if (!ReferenceEquals(unwrapped, form))
                {
                    switch (unwrapped)
                    {
                    case ZilForm newForm:
                        form = newForm;
                        break;

                    default:
                        return(wantResult ? CompileAsOperand(rb, unwrapped, form.SourceLine, resultStorage) : null);
                    }
                }

                if (!(form.First is ZilAtom head))
                {
                    Context.HandleError(new CompilerError(form, CompilerMessages.FORM_Must_Start_With_An_Atom));
                    return(wantResult ? Game.Zero : null);
                }

                // built-in statements handled by ZBuiltins
                var zversion = Context.ZEnvironment.ZVersion;
                Debug.Assert(form.Rest != null);
                var argCount = form.Rest.Count();

                if (wantResult)
                {
                    // prefer the value version, then value+predicate, predicate, void
                    if (ZBuiltins.IsBuiltinValueCall(head.Text, zversion, argCount))
                    {
                        return(ZBuiltins.CompileValueCall(head.Text, this, rb, form, resultStorage));
                    }
                    if (ZBuiltins.IsBuiltinValuePredCall(head.Text, zversion, argCount))
                    {
                        var label1 = rb.DefineLabel();
                        resultStorage = resultStorage ?? rb.Stack;
                        ZBuiltins.CompileValuePredCall(head.Text, this, rb, form, resultStorage, label1, true);
                        rb.MarkLabel(label1);
                        return(resultStorage);
                    }
                    if (ZBuiltins.IsBuiltinPredCall(head.Text, zversion, argCount))
                    {
                        var label1 = rb.DefineLabel();
                        var label2 = rb.DefineLabel();
                        resultStorage = resultStorage ?? rb.Stack;
                        ZBuiltins.CompilePredCall(head.Text, this, rb, form, label1, true);
                        rb.EmitStore(resultStorage, Game.Zero);
                        rb.Branch(label2);
                        rb.MarkLabel(label1);
                        rb.EmitStore(resultStorage, Game.One);
                        rb.MarkLabel(label2);
                        return(resultStorage);
                    }
                    if (ZBuiltins.IsBuiltinVoidCall(head.Text, zversion, argCount))
                    {
                        ZBuiltins.CompileVoidCall(head.Text, this, rb, form);
                        return(Game.One);
                    }
                }
                else
                {
                    // prefer the void version, then predicate, value, value+predicate
                    // (predicate saves a cleanup instruction)
                    if (ZBuiltins.IsBuiltinVoidCall(head.Text, zversion, argCount))
                    {
                        ZBuiltins.CompileVoidCall(head.Text, this, rb, form);
                        return(null);
                    }
                    if (ZBuiltins.IsBuiltinPredCall(head.Text, zversion, argCount))
                    {
                        var dummy = rb.DefineLabel();
                        ZBuiltins.CompilePredCall(head.Text, this, rb, form, dummy, true);
                        rb.MarkLabel(dummy);
                        return(null);
                    }
                    if (ZBuiltins.IsBuiltinValueCall(head.Text, zversion, argCount))
                    {
                        if (ZBuiltins.CompileValueCall(head.Text, this, rb, form, null) == rb.Stack)
                        {
                            rb.EmitPopStack();
                        }
                        return(null);
                    }
                    if (ZBuiltins.IsBuiltinValuePredCall(head.Text, zversion, argCount))
                    {
                        var label1 = rb.DefineLabel();
                        ZBuiltins.CompileValuePredCall(head.Text, this, rb, form, rb.Stack, label1, true);
                        rb.MarkLabel(label1);
                        rb.EmitPopStack();
                        return(null);
                    }
                }

                // routine calls
                var obj = Context.GetZVal(Context.ZEnvironment.InternGlobalName(head));

                while (obj is ZilConstant cnst)
                {
                    obj = cnst.Value;
                }

                switch (obj)
                {
                case ZilRoutine rtn:
                    // check argument count
                    var args = form.Skip(1).ToArray();
                    if (args.Length < rtn.ArgSpec.MinArgCount ||
                        rtn.ArgSpec.MaxArgCount != null && args.Length > rtn.ArgSpec.MaxArgCount)
                    {
                        Context.HandleError(CompilerError.WrongArgCount(
                                                rtn.Name?.ToString() ?? "<unnamed routine>",
                                                new ArgCountRange(rtn.ArgSpec.MinArgCount, rtn.ArgSpec.MaxArgCount)));
                        return(wantResult ? Game.Zero : null);
                    }

                    // compile routine call
                    resultStorage = wantResult ? (resultStorage ?? rb.Stack) : null;
                    using (var argOperands = CompileOperands(rb, form.SourceLine, args))
                    {
                        rb.EmitCall(Routines[head], argOperands.AsArray(), resultStorage);
                    }
                    return(resultStorage);

                case ZilFalse _:
                    // this always returns 0. we can eliminate the call if none of the arguments have side effects.
                    var argsWithSideEffects = form.Skip(1).Where(HasSideEffects).ToArray();

                    if (argsWithSideEffects.Length <= 0)
                    {
                        return(Game.Zero);
                    }

                    resultStorage = wantResult ? (resultStorage ?? rb.Stack) : null;
                    using (var argOperands = CompileOperands(rb, form.SourceLine, argsWithSideEffects))
                    {
                        var operands = argOperands.AsArray();
                        if (operands.Any(o => o == rb.Stack))
                        {
                            rb.EmitCall(Game.Zero, operands.Where(o => o == rb.Stack).ToArray(), resultStorage);
                        }
                    }
                    return(resultStorage);

                default:
                    // unrecognized
                    if (!ZBuiltins.IsNearMatchBuiltin(head.Text, zversion, argCount, out var error))
                    {
                        error = new CompilerError(CompilerMessages.Unrecognized_0_1, "routine or instruction", head);
                    }
                    Context.HandleError(error);
                    return(wantResult ? Game.Zero : null);
                }
            }
        }
Exemple #11
0
        /// <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));
                            }
                        }
                    }