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); }
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); } } }
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); } }
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;
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); } } }
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); } }
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); } } } }
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); } }
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); } }
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); } } }
/// <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)); } } }