/* Copyright 2010-2018 Jesse McGrew
*
* This file is part of ZILF.
*
* ZILF is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ZILF is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ZILF. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
using Zilf.Interpreter.Values;
using Zilf.Language;
using Zilf.Diagnostics;
using Zilf.Common;
using JetBrains.Annotations;
namespace Zilf.Interpreter
{
static partial class Subrs
{
[NotNull]
[Subr("EMPTY?")]
public static ZilObject EMPTY_P([NotNull] Context ctx, [NotNull] IStructure st)
{
return st.IsEmpty ? ctx.TRUE : ctx.FALSE;
}
/*[Subr]
public static ZilObject FIRST(Context ctx, ZilObject[] args)
{
if (args.Length != 1)
throw new InterpreterError("FIRST", 1, 1);
IStructure st = args[0] as IStructure;
if (st == null)
throw new InterpreterError("FIRST: arg must be a structure");
return st.GetFirst();
}*/
/// has fewer than elements.
[NotNull]
[Subr]
public static ZilObject REST(Context ctx, [NotNull] IStructure st, int skip = 1)
{
var result = (ZilObject)st.GetRest(skip);
if (result == null)
throw new InterpreterError(InterpreterMessages._0_Not_Enough_Elements, "REST");
return result.GetPrimitive(ctx);
}
/// The type of does not support this operation, or has not been RESTed at least elements.
[NotNull]
[Subr]
public static ZilObject BACK(Context ctx, [NotNull] IStructure st, int skip = 1)
{
try
{
var result = (ZilObject)st.GetBack(skip);
if (result == null)
throw new InterpreterError(InterpreterMessages._0_Not_Enough_Elements);
return result;
}
catch (NotSupportedException)
{
throw new InterpreterError(InterpreterMessages._0_Not_Supported_By_Type, "BACK");
}
}
/// The type of does not support this operation.
[NotNull]
[Subr]
public static ZilObject TOP(Context ctx, [NotNull] IStructure st)
{
try
{
return (ZilObject)st.GetTop();
}
catch (NotSupportedException)
{
throw new InterpreterError(InterpreterMessages._0_Not_Supported_By_Type, "TOP");
}
}
/// or are negative, or the type of does not support this operation.
[NotNull]
[Subr]
public static ZilObject GROW(Context ctx, [NotNull] IStructure st, int end, int beginning)
{
if (end < 0 || beginning < 0)
{
throw new InterpreterError(InterpreterMessages._0_Sizes_Must_Be_Nonnegative, "GROW");
}
try
{
if (end > 0 || beginning > 0)
{
st.Grow(end, beginning, ctx.FALSE);
}
return (ZilObject)st.GetTop();
}
catch (NotSupportedException)
{
throw new InterpreterError(InterpreterMessages._0_Not_Supported_By_Type, "GROW");
}
}
/// is past the end of .
[NotNull]
[Subr]
public static ZilObject NTH(Context ctx, [NotNull] IStructure st, int idx)
{
var result = st[idx - 1];
if (result == null)
throw new InterpreterError(InterpreterMessages._0_Reading_Past_End_Of_Structure, "NTH");
return result;
}
/// is past the end of , or is read-only.
[NotNull]
[Subr]
public static ZilObject PUT(Context ctx, [NotNull] IStructure st, int idx, ZilObject newValue)
{
try
{
st[idx - 1] = newValue;
}
catch (ArgumentOutOfRangeException)
{
throw new InterpreterError(InterpreterMessages._0_Writing_Past_End_Of_Structure, "PUT");
}
catch (NotSupportedException)
{
throw new InterpreterError(InterpreterMessages._0_Element_1_Is_Read_Only, "PUT", idx);
}
return (ZilObject)st;
}
[NotNull]
[Subr]
public static ZilObject OFFSET(Context ctx, int offset, [NotNull] ZilObject structurePattern, [CanBeNull] ZilObject valuePattern = null)
{
return new ZilOffset(offset, structurePattern, valuePattern ?? ctx.GetStdAtom(StdAtom.ANY));
}
[NotNull]
[Subr]
public static ZilObject INDEX(Context ctx, [NotNull] ZilOffset offset)
{
return new ZilFix(offset.Index);
}
[NotNull]
[Subr]
public static ZilObject LENGTH(Context ctx, [NotNull] IStructure st)
{
return new ZilFix(st.GetLength());
}
[NotNull]
[Subr("LENGTH?")]
public static ZilObject LENGTH_P(Context ctx, [NotNull] IStructure st, int limit)
{
var length = st.GetLength(limit);
return length != null ? new ZilFix((int)length) : ctx.FALSE;
}
/// is empty.
[NotNull]
[Subr]
public static ZilObject PUTREST(Context ctx, [NotNull] ZilListoidBase list, [NotNull] ZilListoidBase newRest)
{
if (list.IsEmpty)
throw new InterpreterError(InterpreterMessages._0_Writing_Past_End_Of_Structure, "PUTREST");
if (newRest is ZilList newRestList)
{
list.Rest = newRestList;
}
else
{
list.Rest = new ZilList(newRest);
}
return list;
}
/// is negative, or or are too short, or the types of and are incompatible.
[NotNull]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
[Subr]
public static ZilObject SUBSTRUC(Context ctx, [NotNull] IStructure from, int rest = 0, int? amount = null,
[CanBeNull] IStructure dest = null)
{
if (amount != null)
{
var max = from.GetLength(rest + (int)amount);
if (max != null && max.Value - rest < amount)
throw new InterpreterError(
InterpreterMessages._0_1_Element1s_Requested_But_Only_2_Available,
"SUBSTRUC",
amount,
max.Value - rest);
}
else
{
amount = from.GetLength() - rest;
}
if (amount < 0)
throw new InterpreterError(InterpreterMessages._0_Negative_Element_Count, "SUBSTRUC");
var fromObj = (ZilObject)from;
var destObj = (ZilObject)dest;
var primitive = (IStructure)fromObj.GetPrimitive(ctx);
if (destObj != null)
{
// modify an existing structure
if (destObj.PrimType != fromObj.PrimType)
throw new InterpreterError(InterpreterMessages._0_Destination_Must_Have_Same_Primtype_As_Source, "SUBSTRUC");
int i;
switch (dest)
{
case ZilListoidBase list:
foreach (var item in primitive.Skip(rest).Take((int)amount))
{
if (list.IsEmpty)
throw new InterpreterError(InterpreterMessages._0_Destination_Too_Short, "SUBSTRUC");
Debug.Assert(list.Rest != null);
list.First = item;
list = list.Rest;
}
break;
case ZilString str:
// this is crazy inefficient, but works with ZilString and OffsetString
// TODO: method on ZilString to do this more efficiently?
for (i = 0; i < amount; i++)
str[i] = primitive[i + rest];
break;
case ZilVector vector:
i = 0;
foreach (var item in primitive.Skip(rest).Take((int)amount))
{
if (i >= vector.GetLength())
throw new InterpreterError(InterpreterMessages._0_Destination_Too_Short, "SUBSTRUC");
vector[i++] = item;
}
break;
default:
throw new InterpreterError(InterpreterMessages._0_Destination_Type_Not_Supported_1, "SUBSTRUC", destObj.GetTypeAtom(ctx));
}
return destObj;
}
// no destination, return a new structure
switch (fromObj.PrimType)
{
case PrimType.LIST:
return new ZilList(primitive.Skip(rest).Take((int)amount));
case PrimType.STRING:
return ZilString.FromString(((ZilString)primitive).Text.Substring(rest, (int)amount));
case PrimType.TABLE:
throw new InterpreterError(InterpreterMessages._0_Primtype_TABLE_Not_Supported, "SUBSTRUC");
case PrimType.VECTOR:
return new ZilVector(((ZilVector)primitive).Skip(rest).Take((int)amount).ToArray());
default:
throw UnhandledCaseException.FromEnum(fromObj.PrimType, "structured primtype");
}
}
[NotNull]
[Subr]
public static ZilObject MEMBER(Context ctx, [NotNull] ZilObject needle, [NotNull] IStructure haystack)
{
if (needle.PrimType == PrimType.STRING && ((ZilObject)haystack).PrimType == PrimType.STRING)
{
string n = ((ZilString)needle.GetPrimitive(ctx)).Text;
if (n != "")
{
string h = ((ZilString)((ZilObject)haystack).GetPrimitive(ctx)).Text;
int pos = h.IndexOf(n, StringComparison.Ordinal);
if (pos >= 0)
{
return (ZilObject)haystack.GetRest(pos) ?? ctx.FALSE;
}
}
}
return PerformMember(ctx, needle, haystack, (a, b) => a.StructurallyEquals(b));
}
[NotNull]
[Subr]
public static ZilObject MEMQ(Context ctx, [NotNull] ZilObject needle, [NotNull] IStructure haystack)
{
return PerformMember(ctx, needle, haystack, (a, b) => a.ExactlyEquals(b));
}
[NotNull]
static ZilObject PerformMember(Context ctx, [NotNull] ZilObject needle, [NotNull] IStructure haystack,
[NotNull] Func equality)
{
while (haystack != null && !haystack.IsEmpty)
{
if (equality(needle, haystack.GetFirst()))
return (ZilObject)haystack;
haystack = haystack.GetRest(1);
}
return ctx.FALSE;
}
#pragma warning disable CS0649
[ZilSequenceParam]
public struct AdditionalSortParam
{
public ZilVector Vector;
[ZilOptional(Default = 1)]
public int RecordSize;
}
#pragma warning restore CS0649
[Serializable]
class SortAbortedException : Exception
{
public ZilResult ZilResult { get; }
public SortAbortedException(ZilResult zilResult)
{
ZilResult = zilResult;
}
protected SortAbortedException(
[NotNull] SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}
[Subr]
public static ZilResult SORT(Context ctx,
[NotNull] [Decl("")] ZilObject predicate,
[NotNull] ZilVector vector, int recordSize = 1, int keyOffset = 0,
[CanBeNull] AdditionalSortParam[] additionalSorts = null)
{
if (keyOffset < 0 || keyOffset >= recordSize)
throw new InterpreterError(InterpreterMessages._0_Expected_0__Key_Offset__Record_Size, "SORT");
var vectorLength = vector.GetLength();
int numRecords = Math.DivRem(vectorLength, recordSize, out var remainder);
if (remainder != 0)
throw new InterpreterError(InterpreterMessages._0_Vector_Length_Must_Be_A_Multiple_Of_Record_Size, "SORT");
if (additionalSorts != null)
{
foreach (var asp in additionalSorts)
{
if (asp.RecordSize < 1)
throw new InterpreterError(InterpreterMessages._0_Expected_0__Key_Offset__Record_Size, "SORT");
var len = asp.Vector.GetLength();
int recs = Math.DivRem(len, asp.RecordSize, out var rem);
if (rem != 0)
throw new InterpreterError(InterpreterMessages._0_Vector_Length_Must_Be_A_Multiple_Of_Record_Size, "SORT");
if (recs != numRecords)
throw new InterpreterError(InterpreterMessages._0_All_Vectors_Must_Have_The_Same_Number_Of_Records, "SORT");
}
}
ZilObject KeySelector(int i) => vector[i * recordSize + keyOffset];
Comparison comparison;
if (predicate.IsTrue)
{
// user-provided comparison
var applicable = predicate.AsApplicable(ctx);
Debug.Assert(applicable != null);
var args = new ZilObject[2];
comparison = (a, b) =>
{
// greater?
args[0] = a;
args[1] = b;
var zr = applicable.ApplyNoEval(ctx, args);
if (zr.ShouldPass())
throw new SortAbortedException(zr);
if (((ZilObject)zr).IsTrue)
return 1;
// less?
args[0] = b;
args[1] = a;
zr = applicable.ApplyNoEval(ctx, args);
if (zr.ShouldPass())
throw new SortAbortedException(zr);
if (((ZilObject)zr).IsTrue)
return -1;
// equal
return 0;
};
}
else
{
// default comparison
comparison = (a, b) =>
{
if (a.GetTypeAtom(ctx) != b.GetTypeAtom(ctx))
{
throw new InterpreterError(InterpreterMessages._0_Keys_Must_Have_The_Same_Type_To_Use_Default_Comparison, "SORT");
}
a = a.GetPrimitive(ctx);
b = b.GetPrimitive(ctx);
switch (a.PrimType)
{
case PrimType.ATOM:
return string.Compare(((ZilAtom)a).Text, ((ZilAtom)b).Text, StringComparison.Ordinal);
case PrimType.FIX:
return ((ZilFix)a).Value.CompareTo(((ZilFix)b).Value);
case PrimType.STRING:
return string.Compare(((ZilString)a).Text, ((ZilString)b).Text, StringComparison.Ordinal);
default:
throw new InterpreterError(InterpreterMessages._0_Key_Primtypes_Must_Be_ATOM_FIX_Or_STRING_To_Use_Default_Comparison, "SORT");
}
};
}
try
{
// sort
var sortedIndexes =
Enumerable.Range(0, numRecords)
.OrderBy(KeySelector, Comparer.Create(comparison))
.ToArray();
// write output
RearrangeVector(vector, recordSize, sortedIndexes);
if (additionalSorts != null)
foreach (var asp in additionalSorts)
RearrangeVector(asp.Vector, asp.RecordSize, sortedIndexes);
return vector;
}
catch (SortAbortedException ex)
{
return ex.ZilResult;
}
catch (Exception wrapper) when (wrapper.InnerException is SortAbortedException ex)
{
return ex.ZilResult;
}
}
static void RearrangeVector([NotNull] ZilVector vector, int recordSize, [NotNull] int[] desiredIndexOrder)
{
var output = new List(vector.GetLength());
foreach (var srcIndex in desiredIndexOrder)
{
for (int i = 0; i < recordSize; i++)
{
output.Add(vector[srcIndex * recordSize + i]);
}
}
for (int i = 0; i < output.Count; i++)
{
vector[i] = output[i];
}
}
}
}