pax_global_header00006660000000000000000000000064142560614620014520gustar00rootroot0000000000000052 comment=83d06d779c05e259bf1e020aca9b850d3c6f010b Serpent-serpent-1.41/000077500000000000000000000000001425606146200145435ustar00rootroot00000000000000Serpent-serpent-1.41/.github/000077500000000000000000000000001425606146200161035ustar00rootroot00000000000000Serpent-serpent-1.41/.github/workflows/000077500000000000000000000000001425606146200201405ustar00rootroot00000000000000Serpent-serpent-1.41/.github/workflows/main-ci.yml000066400000000000000000000036671425606146200222140ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Run CI Tests on: push: branches: [ master ] pull_request: branches: [ master ] # allow manual trigger workflow_dispatch: jobs: test-python: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10'] steps: - name: Checkout source uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install pytz attrs pytest - name: build and install run: pip install . - name: Test with pytest run: | pytest -v tests test-dotnet: name: build, pack, test .Net runs-on: ubuntu-latest env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_NOLOGO: true NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} steps: - uses: actions/checkout@v2 - name: Install .Net uses: actions/setup-dotnet@v1 with: dotnet-version: '6.0.x' - name: Restore, Build, test, and pack uses: Elskom/build-dotnet@main with: SOLUTION_FILE_PATH: 'dotnet/Serpent' TEST: true PACK: true PUSH: false test-java: name: build, test Java runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 8 uses: actions/setup-java@v3 with: java-version: '8' distribution: 'temurin' - name: Build with Maven run: mvn --batch-mode --update-snapshots -f java/pom.xml verify Serpent-serpent-1.41/.gitignore000066400000000000000000000015141425606146200165340ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg[s] *.egg-info dist build eggs parts bin target var sdist develop-eggs .installed.cfg lib lib64 .idea/ # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) [Bb]in/ [Oo]bj/ build target # mstest test results TestResults TEST-*.xml TestResult.xml ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.sln.docstates # Build results [Dd]ebug/ [Rr]elease/ x64/ *_i.c *_p.c *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.log *.vspscc *.vssscc .builds *.nupkg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # java *.class Serpent-serpent-1.41/.pylintrc000066400000000000000000000002001425606146200164000ustar00rootroot00000000000000[MESSAGES CONTROL] disable=missing-docstring [BASIC] include-naming-hint=yes max-line-length=120 good-names=i,j,k,x,y,z,t,ex,_ Serpent-serpent-1.41/LICENSE000066400000000000000000000020541425606146200155510ustar00rootroot00000000000000MIT License Copyright (c) by Irmen de Jong Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Serpent-serpent-1.41/MANIFEST.in000066400000000000000000000004651425606146200163060ustar00rootroot00000000000000include LICENSE include README.md include tox.ini recursive-include tests * global-exclude */.svn/* global-exclude */.idea/* global-exclude *.class global-exclude *.pyc global-exclude *.pyo global-exclude *.coverage global-exclude .git global-exclude .gitignore global-exclude .tox global-exclude __pycache__ Serpent-serpent-1.41/Makefile000066400000000000000000000020731425606146200162050ustar00rootroot00000000000000.PHONY: all dist install upload clean test check lint all: @echo "targets include dist, upload, install, clean, test, lint" dist: python setup.py sdist bdist_wheel @echo "Look in the dist/ directory" upload: dist @echo "Uploading to Pypi using twine...." twine upload dist/* install: python setup.py install test: pytest -v tests lint: pycodestyle clean: @echo "Cleaning up..." find . -name __pycache__ -print0 | xargs -0 rm -rf find . -name \*_log -print0 | xargs -0 rm -f find . -name \*.log -print0 | xargs -0 rm -f find . -name \*.pyo -print0 | xargs -0 rm -f find . -name \*.pyc -print0 | xargs -0 rm -f find . -name \*.class -print0 | xargs -0 rm -f find . -name \*.DS_Store -print0 | xargs -0 rm -f find . -name TEST-*.xml -print0 | xargs -0 rm -f find . -name TestResult.xml -print0 | xargs -0 rm -f rm -f MANIFEST rm -rf build rm -rf dotnet/Serpent/obj dotnet/Serpent.Test/obj rm -rf dotnet/Serpent/bin dotnet/Serpent.Test/bin find . -name '.#*' -print0 | xargs -0 rm -f find . -name '#*#' -print0 | xargs -0 rm -f @echo "clean!" Serpent-serpent-1.41/README.md000066400000000000000000000072421425606146200160270ustar00rootroot00000000000000Serpent serialization library (Python/.NET/Java) ================================================ [![Latest Version](https://img.shields.io/pypi/v/Serpent.svg)](https://pypi.python.org/pypi/Serpent/) [![Maven Central](https://img.shields.io/maven-central/v/net.razorvine/serpent.svg)](http://search.maven.org/#search|ga|1|g%3A%22net.razorvine%22%20AND%20a%3A%22serpent%22) [![NuGet](https://img.shields.io/nuget/v/Razorvine.Serpent.svg)](https://www.nuget.org/packages/Razorvine.Serpent/) [![Anaconda-Server Badge](https://anaconda.org/conda-forge/serpent/badges/version.svg)](https://anaconda.org/conda-forge/serpent) Serpent provides ast.literal_eval() compatible object tree serialization. It serializes an object tree into bytes (utf-8 encoded string) that can be decoded and then passed as-is to ast.literal_eval() to rebuild it as the original object tree. As such it is safe to send serpent data to other machines over the network for instance (because only 'safe' literals are encoded). More info on Pypi: https://pypi.python.org/pypi/serpent Source code is on Github: https://github.com/irmen/Serpent Copyright by Irmen de Jong (irmen@razorvine.net) This software is released under the MIT software license. This license, including disclaimer, is available in the 'LICENSE' file. PYTHON ------ Compatible with Python 3.7+ (use a serpent version before 1.30 for Python 2.7 support) It can be found on Pypi as 'serpent': https://pypi.python.org/pypi/serpent Example usage can be found in ./tests/example.py C#/.NET ------- Package is available on www.nuget.org as 'Razorvine.Serpent'. Full source code can be found in ./dotnet/ directory. Example usage can be found in ./dotnet/Serpent.Test/Example.cs The project is a dotnet core project targeting NetStandard 2.0. JAVA ---- Maven-artefact is available on maven central, groupid 'net.razorvine' artifactid 'serpent'. Full source code can be found in ./java/ directory. Example usage can be found in ./java/test/SerpentExample.java Versions before 1.23 require Java 7 or Java 8 (JDK 1.7 or 1.8) to compile and run. Version 1.23 and later require Java 8 (JDK 1.8) at a minimum to compile and run. SOME MORE DETAILS ----------------- Serpent handles several special Python types to make life easier: - bytes, bytearrays, memoryview --> string, base-64 (or bytes-literal if selected) (you'll have to manually un-base64. Can use serpent.tobytes function for that.) - uuid.UUID, datetime.{datetime, date, time, timespan} --> appropriate string/number - decimal.Decimal --> string (to not lose precision) - array.array typecode 'u' --> string - array.array other typecode --> list - Exception --> dict with some fields of the exception (message, args) - collections module types --> mostly equivalent primitive types or dict - enums --> the value of the enum - namedtuple --> treated as just a tuple - attr dataclasses and python 3.7 native dataclasses: treated as just a class, so will become a dict - all other types --> dict with the ``__getstate__`` or ``vars()`` of the object, and a ``__class__`` element with the name of the class Notes: The serializer is not thread-safe. Make sure you're not making changes to the object tree that is being serialized, and don't use the same serializer in different threads. Because the serialized format is just valid Python source code, it can contain comments. Serpent does not add comments by itself apart from the single header line. Floats +inf and -inf are handled via a trick, Float 'nan' cannot be handled and is represented by the special value: ``{'__class__':'float','value':'nan'}`` We chose not to encode it as just the string 'NaN' because that could cause memory issues when used in multiplications. Serpent-serpent-1.41/dotnet/000077500000000000000000000000001425606146200160405ustar00rootroot00000000000000Serpent-serpent-1.41/dotnet/Serpent/000077500000000000000000000000001425606146200174605ustar00rootroot00000000000000Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/000077500000000000000000000000001425606146200230565ustar00rootroot00000000000000Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/Ast.cs000066400000000000000000000226021425606146200241360ustar00rootroot00000000000000using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text; using System.Linq; // ReSharper disable MemberCanBeProtected.Global // ReSharper disable UnusedParameter.Global namespace Razorvine.Serpent { /// /// Abstract syntax tree for the literal expression. This is what the parser returns. /// public class Ast { public INode Root; public override string ToString() { return "# serpent utf-8 .net\n" + Root.ToString(); } /// /// Get the actual parsed data as C# object(s). /// public object GetData() { var visitor = new ObjectifyVisitor(); Root.Accept(visitor); return visitor.GetObject(); } /// /// Get the actual parsed data as C# object(s). /// /// functin to convert dicts to actual instances for a class, /// instead of leaving them as dictionaries. Requires the __class__ key to be present /// in the dict node. If it returns null, the normal processing is done. public object GetData(Func dictToInstance) { var visitor = new ObjectifyVisitor(dictToInstance); Root.Accept(visitor); return visitor.GetObject(); } public interface INodeVisitor { void Visit(ComplexNumberNode complex); void Visit(DictNode dict); void Visit(ListNode list); void Visit(NoneNode none); void Visit(IntegerNode value); void Visit(LongNode value); void Visit(DoubleNode value); void Visit(BooleanNode value); void Visit(StringNode value); void Visit(BytesNode value); void Visit(DecimalNode value); void Visit(SetNode setnode); void Visit(TupleNode tuple); } /// /// Visitor pattern: visit all nodes in the Ast with the given visitor. /// public void Accept(INodeVisitor visitor) { Root.Accept(visitor); } [SuppressMessage("ReSharper", "UnusedMember.Global")] public interface INode { string ToString(); bool Equals(object obj); void Accept(INodeVisitor visitor); } [SuppressMessage("ReSharper", "UnusedMember.Global")] public abstract class PrimitiveNode : INode, IComparable> { public readonly T Value; protected PrimitiveNode(T value) { Value=value; } public override int GetHashCode() { return Value!=null? Value.GetHashCode() : 0; } public override bool Equals(object obj) { var node = obj as PrimitiveNode; return node != null && Equals(Value, node.Value); } public bool Equals(PrimitiveNode other) { return Equals(Value, other.Value); } public int CompareTo(PrimitiveNode other) { var cv = Value as IComparable; var otherCv = other.Value as IComparable; if (cv != null && otherCv != null) return cv.CompareTo(otherCv); return 0; } public override string ToString() { var s = Value as string; if(s != null) { var sb=new StringBuilder(); sb.Append("'"); foreach(var c in s) { switch(c) { case '\\': sb.Append("\\\\"); break; case '\'': sb.Append("\\'"); break; case '\a': sb.Append("\\a"); break; case '\b': sb.Append("\\b"); break; case '\f': sb.Append("\\f"); break; case '\n': sb.Append("\\n"); break; case '\r': sb.Append("\\r"); break; case '\t': sb.Append("\\t"); break; case '\v': sb.Append("\\v"); break; default: sb.Append(c); break; } } sb.Append("'"); return sb.ToString(); } if (!(Value is double) && !(Value is float)) return Value.ToString(); var d = Convert.ToString(Value, CultureInfo.InvariantCulture); if (d == null) throw new ParseException("ast value is null"); if(d.IndexOfAny(new [] {'.', 'e', 'E'})<=0) d+=".0"; return d; } public abstract void Accept(INodeVisitor visitor); } public class IntegerNode: PrimitiveNode { public IntegerNode(int value) : base(value) { } public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public class LongNode: PrimitiveNode { public LongNode(long value) : base(value) { } public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public class DoubleNode: PrimitiveNode { public DoubleNode(double value) : base(value) { } public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public class StringNode: PrimitiveNode { public StringNode(string value) : base(value) { } public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public class BytesNode: PrimitiveNode { public BytesNode(byte[] value) : base(value) { } public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public class DecimalNode: PrimitiveNode { public DecimalNode(decimal value) : base(value) { } public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public class BooleanNode: PrimitiveNode { public BooleanNode(bool value) : base(value) { } public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public struct ComplexNumberNode: INode { public double Real; public double Imaginary; public void Accept(INodeVisitor visitor) { visitor.Visit(this); } public override string ToString() { string strReal = Real.ToString(CultureInfo.InvariantCulture); string strImag = Imaginary.ToString(CultureInfo.InvariantCulture); return string.Format(Imaginary>=0 ? "({0}+{1}j)" : "({0}{1}j)", strReal, strImag); } } public class NoneNode: INode { public static readonly NoneNode Instance = new NoneNode(); private NoneNode() { } public override string ToString() { return "None"; } public void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public abstract class SequenceNode: INode { public List Elements = new List(); public virtual char OpenChar => '?'; public virtual char CloseChar => '?'; public override int GetHashCode() { int hashCode = 0; unchecked { // ReSharper disable once NonReadonlyMemberInGetHashCode foreach(var elt in Elements) hashCode += 1000000007 * elt.GetHashCode(); } return hashCode; } public override bool Equals(object obj) { var other = obj as SequenceNode; return other != null && Elements.SequenceEqual(other.Elements); } public override string ToString() { var sb=new StringBuilder(); sb.Append(OpenChar); if(Elements != null) { foreach(var elt in Elements) { sb.Append(elt.ToString()); sb.Append(','); } } // ReSharper disable once PossibleNullReferenceException if(Elements.Count>0) sb.Remove(sb.Length-1, 1); // remove last comma sb.Append(CloseChar); return sb.ToString(); } public abstract void Accept(INodeVisitor visitor); } public class TupleNode : SequenceNode { public override string ToString() { var sb=new StringBuilder(); sb.Append('('); if(Elements != null) { foreach(var elt in Elements) { sb.Append(elt.ToString()); sb.Append(","); } if(Elements.Count>1) sb.Remove(sb.Length-1, 1); } sb.Append(')'); return sb.ToString(); } public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public class ListNode : SequenceNode { public override char OpenChar => '['; public override char CloseChar => ']'; public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public abstract class UnorderedSequenceNode : SequenceNode { public override bool Equals(object obj) { if(!(obj is UnorderedSequenceNode)) return false; var set1 = ElementsAsSet(); var set2 = ((UnorderedSequenceNode) obj).ElementsAsSet(); return set1.SetEquals(set2); } public override int GetHashCode() { return ElementsAsSet().GetHashCode(); } public HashSet ElementsAsSet() { var set = new HashSet(); foreach(var kv in Elements) set.Add(kv); return set; } } public class SetNode : UnorderedSequenceNode { public override char OpenChar => '{'; public override char CloseChar => '}'; public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public class DictNode : UnorderedSequenceNode { public override char OpenChar => '{'; public override char CloseChar => '}'; public override void Accept(INodeVisitor visitor) { visitor.Visit(this); } } public struct KeyValueNode : INode { public INode Key; public INode Value; public KeyValueNode(INode key, INode value) { Key = key; Value = value; } public override string ToString() { return $"{Key}:{Value}"; } public void Accept(INodeVisitor visitor) { throw new NotSupportedException("don't visit a keyvaluenode"); } } } } Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/ComplexNumber.cs000066400000000000000000000042401425606146200261650ustar00rootroot00000000000000using System; using System.Text; // ReSharper disable UnusedMember.Global namespace Razorvine.Serpent { /// /// A Complex Number class. /// public class ComplexNumber { public double Real {get; } public double Imaginary {get; } public ComplexNumber(double r, double i) { Real=r; Imaginary=i; } public override string ToString() { StringBuilder sb=new StringBuilder(); sb.Append(Real); if(Imaginary>0) sb.Append('+'); return sb.Append(Imaginary).Append('i').ToString(); } public double Magnitude() { return Math.Sqrt(Real * Real + Imaginary * Imaginary); } public static ComplexNumber operator +(ComplexNumber c1, ComplexNumber c2) { return new ComplexNumber(c1.Real + c2.Real, c1.Imaginary + c2.Imaginary); } public static ComplexNumber operator -(ComplexNumber c1, ComplexNumber c2) { return new ComplexNumber(c1.Real - c2.Real, c1.Imaginary - c2.Imaginary); } public static ComplexNumber operator *(ComplexNumber c1, ComplexNumber c2) { return new ComplexNumber(c1.Real * c2.Real - c1.Imaginary * c2.Imaginary, c1.Real * c2.Imaginary + c1.Imaginary * c2.Real); } public static ComplexNumber operator /(ComplexNumber c1, ComplexNumber c2) { return new ComplexNumber((c1.Real * c2.Real + c1.Imaginary * c2.Imaginary) / (c2.Real * c2.Real + c2.Imaginary * c2.Imaginary), (c1.Imaginary * c2.Real - c1.Real * c2.Imaginary) / (c2.Real * c2.Real + c2.Imaginary * c2.Imaginary)); } #region Equals and GetHashCode implementation public override bool Equals(object obj) { if(!(obj is ComplexNumber)) return false; ComplexNumber other = (ComplexNumber) obj; // ReSharper disable CompareOfFloatsByEqualityOperator return Real==other.Real && Imaginary==other.Imaginary; } public override int GetHashCode() { return Real.GetHashCode() ^ Imaginary.GetHashCode(); } public static bool operator ==(ComplexNumber lhs, ComplexNumber rhs) { if (ReferenceEquals(lhs, rhs)) return true; if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null)) return false; return lhs.Equals(rhs); } public static bool operator !=(ComplexNumber lhs, ComplexNumber rhs) { return !(lhs == rhs); } #endregion } }Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/DebugVisitor.cs000066400000000000000000000053371425606146200260230ustar00rootroot00000000000000using System.Diagnostics.CodeAnalysis; using System.Text; namespace Razorvine.Serpent { /// /// Ast nodevisitor that prints out the Ast as a string for debugging purposes /// [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public class DebugVisitor: Ast.INodeVisitor { private readonly StringBuilder _result = new StringBuilder(); private int _indent; /// /// Get the debug string representation result. /// public override string ToString() { return _result.ToString(); } protected void Indent() { for(int i=0; i<_indent; ++i) _result.Append(" "); } public void Visit(Ast.ComplexNumberNode complex) { _result.AppendFormat("complexnumber ({0}r,{1}i)", complex.Real, complex.Imaginary); } public void Visit(Ast.DictNode dict) { _result.AppendLine("(dict"); _indent++; foreach(var node in dict.Elements) { var kv = (Ast.KeyValueNode) node; Indent(); kv.Key.Accept(this); _result.Append(" = "); kv.Value.Accept(this); _result.AppendLine(","); } _indent--; Indent(); _result.Append(")"); } public void Visit(Ast.ListNode list) { _result.AppendLine("(list"); _indent++; foreach(Ast.INode node in list.Elements) { Indent(); node.Accept(this); _result.AppendLine(","); } _indent--; Indent(); _result.Append(")"); } public void Visit(Ast.NoneNode none) { _result.Append("None"); } public void Visit(Ast.IntegerNode value) { _result.AppendFormat("int {0}", value.Value); } public void Visit(Ast.LongNode value) { _result.AppendFormat("long {0}", value.Value); } public void Visit(Ast.DoubleNode value) { _result.AppendFormat("double {0}", value.Value); } public void Visit(Ast.BooleanNode value) { _result.AppendFormat("bool {0}", value.Value); } public void Visit(Ast.StringNode value) { _result.AppendFormat("string '{0}'", value.Value); } public void Visit(Ast.BytesNode value) { _result.AppendFormat("bytes {0}", value.Value); } public void Visit(Ast.DecimalNode value) { _result.AppendFormat("decimal {0}", value.Value); } public void Visit(Ast.SetNode setnode) { _result.AppendLine("(set"); _indent++; foreach(Ast.INode node in setnode.Elements) { Indent(); node.Accept(this); _result.AppendLine(","); } _indent--; Indent(); _result.Append(")"); } public void Visit(Ast.TupleNode tuple) { _result.AppendLine("(tuple"); _indent++; foreach(Ast.INode node in tuple.Elements) { Indent(); node.Accept(this); _result.AppendLine(","); } _indent--; Indent(); _result.Append(")"); } } } Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/ObjectifyVisitor.cs000066400000000000000000000063001425606146200267020ustar00rootroot00000000000000using System; using System.Collections.Generic; using System.Collections; namespace Razorvine.Serpent { /// /// Ast nodevisitor that turns the AST into actual .NET objects (array, int, IDictionary, string, etc...) /// public class ObjectifyVisitor: Ast.INodeVisitor { private readonly Stack _generated = new Stack(); private readonly Func _dictToInstance; /// /// Create the visitor that converts AST in actual objects. /// public ObjectifyVisitor() { } /// /// Create the visitor that converts AST in actual objects. /// /// functin to convert dicts to actual instances for a class, /// instead of leaving them as dictionaries. Requires the __class__ key to be present /// in the dict node. If it returns null, the normal processing is done. public ObjectifyVisitor(Func dictToInstance) { _dictToInstance = dictToInstance; } /// /// get the resulting object tree. /// public object GetObject() { return _generated.Pop(); } public void Visit(Ast.ComplexNumberNode complex) { _generated.Push(new ComplexNumber(complex.Real, complex.Imaginary)); } public void Visit(Ast.DictNode dict) { IDictionary obj = new Dictionary(dict.Elements.Count); foreach(var node in dict.Elements) { var kv = (Ast.KeyValueNode) node; kv.Key.Accept(this); object key = _generated.Pop(); kv.Value.Accept(this); object value = _generated.Pop(); obj[key] = value; } if(_dictToInstance==null || !obj.Contains("__class__")) { _generated.Push(obj); } else { object result = _dictToInstance(obj); _generated.Push(result ?? obj); } } public void Visit(Ast.ListNode list) { IList obj = new List(list.Elements.Count); foreach(Ast.INode node in list.Elements) { node.Accept(this); obj.Add(_generated.Pop()); } _generated.Push(obj); } public void Visit(Ast.NoneNode none) { _generated.Push(null); } public void Visit(Ast.IntegerNode value) { _generated.Push(value.Value); } public void Visit(Ast.LongNode value) { _generated.Push(value.Value); } public void Visit(Ast.DoubleNode value) { _generated.Push(value.Value); } public void Visit(Ast.BooleanNode value) { _generated.Push(value.Value); } public void Visit(Ast.StringNode value) { _generated.Push(value.Value); } public void Visit(Ast.BytesNode value) { _generated.Push(value.Value); } public void Visit(Ast.DecimalNode value) { _generated.Push(value.Value); } public void Visit(Ast.SetNode setnode) { var obj = new HashSet(); foreach(Ast.INode node in setnode.Elements) { node.Accept(this); obj.Add(_generated.Pop()); } _generated.Push(obj); } public void Visit(Ast.TupleNode tuple) { var array = new object[tuple.Elements.Count]; int index=0; foreach(Ast.INode node in tuple.Elements) { node.Accept(this); array[index++] = _generated.Pop(); } _generated.Push(array); } } } Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/ParseException.cs000066400000000000000000000006441425606146200263420ustar00rootroot00000000000000using System; // ReSharper disable once UnusedMember.Global namespace Razorvine.Serpent { /// /// A problem occurred during parsing. /// public class ParseException : Exception { public ParseException() { } public ParseException(string message) : base(message) { } public ParseException(string message, Exception innerException) : base(message, innerException) { } } }Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/Parser.cs000066400000000000000000000464561425606146200246600ustar00rootroot00000000000000using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text; namespace Razorvine.Serpent { /// /// Parse a Python literal into an Ast (abstract syntax tree). /// [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Local")] public class Parser { /// /// Parse from a byte array (containing utf-8 encoded string with the Python literal expression in it) /// public Ast Parse(byte[] serialized) { return Parse(Encoding.UTF8.GetString(serialized, 0, serialized.Length)); } /// /// Parse from a string with the Python literal expression /// public Ast Parse(string expression) { Ast ast=new Ast(); if(string.IsNullOrEmpty(expression)) return ast; SeekableStringReader sr = new SeekableStringReader(expression); if(sr.Peek()=='#') sr.ReadUntil('\n'); // skip comment line try { ast.Root = ParseExpr(sr); sr.SkipWhitespace(); if(sr.HasMore()) throw new ParseException("garbage at end of expression"); return ast; } catch (ParseException x) { string faultLocation = ExtractFaultLocation(sr); throw new ParseException(x.Message + " (at position "+sr.Bookmark()+"; '"+faultLocation+"')", x); } } private string ExtractFaultLocation(SeekableStringReader sr) { string left, right; sr.Context(-1, 20, out left, out right); return $"...{left}>>><<<{right}..."; } private Ast.INode ParseExpr(SeekableStringReader sr) { // expr = [ ] single | compound [ ] . sr.SkipWhitespace(); if(!sr.HasMore()) throw new ParseException("unexpected end of line, missing expression or close/open character"); char c = sr.Peek(); Ast.INode node; if(c=='{' || c=='[' || c=='(') node = ParseCompound(sr); else node = ParseSingle(sr); sr.SkipWhitespace(); return node; } private Ast.INode ParseCompound(SeekableStringReader sr) { // compound = tuple | dict | list | set . sr.SkipWhitespace(); switch(sr.Peek()) { case '[': return ParseList(sr); case '{': { int bm = sr.Bookmark(); try { return ParseSet(sr); } catch(ParseException) { sr.FlipBack(bm); return ParseDict(sr); } } case '(': // tricky case here, it can be a tuple but also a complex number: // if the last character before the closing parenthesis is a 'j', it is a complex number { int bm = sr.Bookmark(); string betweenparens = sr.ReadUntil(')', '\n').TrimEnd(); sr.FlipBack(bm); return betweenparens.EndsWith("j") ? (Ast.INode) ParseComplex(sr) : ParseTuple(sr); } default: throw new ParseException("invalid sequencetype char"); } } private Ast.TupleNode ParseTuple(SeekableStringReader sr) { //tuple = tuple_empty | tuple_one | tuple_more //tuple_empty = '()' . //tuple_one = '(' expr ',' ')' . //tuple_more = '(' expr_list trailing_comma ')' . // trailing_comma = '' | ',' . sr.Read(); // ( sr.SkipWhitespace(); Ast.TupleNode tuple = new Ast.TupleNode(); if(sr.Peek() == ')') { sr.Read(); return tuple; // empty tuple } Ast.INode firstelement = ParseExpr(sr); if(sr.Peek() == ',') { sr.Read(); sr.SkipWhitespace(); if(sr.Read() == ')') { // tuple with just a single element tuple.Elements.Add(firstelement); return tuple; } sr.Rewind(1); // undo the thing that wasn't a ) } tuple.Elements = ParseExprList(sr); tuple.Elements.Insert(0, firstelement); // handle trailing comma if present sr.SkipWhitespace(); if(!sr.HasMore()) throw new ParseException("missing ')'"); if(sr.Peek() == ',') sr.Read(); if(!sr.HasMore()) throw new ParseException("missing ')'"); char closechar = sr.Read(); if(closechar==',') closechar = sr.Read(); if(closechar!=')') throw new ParseException("expected ')'"); return tuple; } private List ParseExprList(SeekableStringReader sr) { //expr_list = expr { ',' expr } . var exprList = new List {ParseExpr(sr)}; while(sr.HasMore() && sr.Peek() == ',') { sr.Read(); try { exprList.Add(ParseExpr(sr)); } catch (ParseException) { sr.Rewind(1); break; } } return exprList; } private List ParseKeyValueList(SeekableStringReader sr) { //keyvalue_list = keyvalue { ',' keyvalue } . var kvs = new List {ParseKeyValue(sr)}; while(sr.HasMore() && sr.Peek()==',') { sr.Read(); try { kvs.Add(ParseKeyValue(sr)); } catch (ParseException) { sr.Rewind(1); break; } } return kvs; } private Ast.KeyValueNode ParseKeyValue(SeekableStringReader sr) { //keyvalue = expr ':' expr . Ast.INode key = ParseExpr(sr); if (!sr.HasMore() || sr.Peek() != ':') throw new ParseException("expected ':'"); sr.Read(); // : Ast.INode value = ParseExpr(sr); return new Ast.KeyValueNode { Key = key, Value = value }; } private Ast.SetNode ParseSet(SeekableStringReader sr) { // set = '{' expr_list trailing_comma '}' . // trailing_comma = '' | ',' . sr.Read(); // { sr.SkipWhitespace(); Ast.SetNode setnode = new Ast.SetNode(); var elts = ParseExprList(sr); // handle trailing comma if present sr.SkipWhitespace(); if(!sr.HasMore()) throw new ParseException("missing '}'"); if(sr.Peek() == ',') sr.Read(); if(!sr.HasMore()) throw new ParseException("missing '}'"); char closechar = sr.Read(); if(closechar!='}') throw new ParseException("expected '}'"); // make sure it has set semantics (remove duplicate elements) var h = new HashSet(elts); setnode.Elements = new List(h); return setnode; } private Ast.ListNode ParseList(SeekableStringReader sr) { // list = list_empty | list_nonempty . // list_empty = '[]' . // list_nonempty = '[' expr_list trailing_comma ']' . // trailing_comma = '' | ',' . sr.Read(); // [ sr.SkipWhitespace(); Ast.ListNode list = new Ast.ListNode(); if(sr.Peek() == ']') { sr.Read(); return list; // empty list } list.Elements = ParseExprList(sr); // handle trailing comma if present sr.SkipWhitespace(); if(!sr.HasMore()) throw new ParseException("missing ']'"); if(sr.Peek() == ',') sr.Read(); if(!sr.HasMore()) throw new ParseException("missing ']'"); char closechar = sr.Read(); if(closechar!=']') throw new ParseException("expected ']'"); return list; } private Ast.INode ParseDict(SeekableStringReader sr) { //dict = '{' keyvalue_list trailing_comma '}' . //keyvalue_list = keyvalue { ',' keyvalue } . //keyvalue = expr ':' expr . // trailing_comma = '' | ',' . sr.Read(); // { sr.SkipWhitespace(); Ast.DictNode dict = new Ast.DictNode(); if(sr.Peek() == '}') { sr.Read(); return dict; // empty dict } var elts = ParseKeyValueList(sr); // handle trailing comma if present sr.SkipWhitespace(); if(!sr.HasMore()) throw new ParseException("missing '}'"); if(sr.Peek() == ',') sr.Read(); if(!sr.HasMore()) throw new ParseException("missing '}'"); char closechar = sr.Read(); if(closechar!='}') throw new ParseException("expected '}'"); // make sure it has dict semantics (remove duplicate keys) var fixedDict = new Dictionary(elts.Count); foreach(var node in elts) { var kv = (Ast.KeyValueNode) node; fixedDict[kv.Key] = kv.Value; } foreach(var kv in fixedDict) { dict.Elements.Add(new Ast.KeyValueNode { Key=kv.Key, Value=kv.Value }); } // SPECIAL CASE: {'__class__':'float','value':'nan'} ---> Double.NaN if (dict.Elements.Count != 2) return dict; if (!dict.Elements.Contains(new Ast.KeyValueNode(new Ast.StringNode("__class__"), new Ast.StringNode("float")))) return dict; if(dict.Elements.Contains(new Ast.KeyValueNode(new Ast.StringNode("value"), new Ast.StringNode("nan")))) { return new Ast.DoubleNode(double.NaN); } return dict; } public Ast.INode ParseSingle(SeekableStringReader sr) { // single = int | float | complex | string | bool | none . sr.SkipWhitespace(); switch(sr.Peek()) { case 'N': return ParseNone(sr); case 'T': case 'F': return ParseBool(sr); case '\'': case '"': return ParseString(sr); case 'b': return ParseBytes(sr); } // int or float or complex. int bookmark = sr.Bookmark(); try { return ParseComplex(sr); } catch (ParseException) { sr.FlipBack(bookmark); try { return ParseFloat(sr); } catch (ParseException) { sr.FlipBack(bookmark); return ParseInt(sr); } } } private const string IntegerChars = "-0123456789"; private const string FloatChars = "-+.eE0123456789"; private Ast.INode ParseInt(SeekableStringReader sr) { // int = ['-'] digitnonzero {digit} . string numberstr = sr.ReadWhile(IntegerChars); if(numberstr.Length==0) throw new ParseException("invalid int character"); try { try { return new Ast.IntegerNode(int.Parse(numberstr)); } catch (OverflowException) { // try long try { return new Ast.LongNode(long.Parse(numberstr)); } catch (OverflowException) { // try decimal, but it can still overflow because it's not arbitrary precision try { return new Ast.DecimalNode(decimal.Parse(numberstr)); } catch (OverflowException) { throw new ParseException("number too large"); } } } } catch (FormatException x) { throw new ParseException("invalid integer format", x); } } private Ast.PrimitiveNode ParseFloat(SeekableStringReader sr) { string numberstr = sr.ReadWhile(FloatChars); if(numberstr.Length==0) throw new ParseException("invalid float character"); // little bit of a hack: // if the number doesn't contain a decimal point and no 'e'/'E', it is an integer instead. // in that case, we need to reject it as a float. if(numberstr.IndexOfAny(new [] {'.','e','E'}) < 0) throw new ParseException("number is not a float (might be an integer though)"); try { return new Ast.DoubleNode(ParseDouble(numberstr)); } catch (FormatException x) { throw new ParseException("invalid float format", x); } } private Ast.ComplexNumberNode ParseComplex(SeekableStringReader sr) { //complex = complextuple | imaginary . //imaginary = ['+' | '-' ] ( float | int ) 'j' . //complextuple = '(' ( float | int ) imaginary ')' . if(sr.Peek()=='(') { // complextuple sr.Read(); // ( string numberstr; if(sr.Peek()=='-' || sr.Peek()=='+') { // starts with a sign, read that first otherwise the readuntil will return immediately numberstr = sr.Read(1) + sr.ReadUntil('+', '-'); } else { numberstr = sr.ReadUntil('+', '-'); } sr.Rewind(1); // rewind the +/- // because we're a bit more cautious here with reading chars than in the float parser, // it can be that the parser now stopped directly after the 'e' in a number like "3.14e+20". // ("3.14e20" is fine) So, check if the last char is 'e' and if so, continue reading 0..9. if(numberstr.EndsWith("e", StringComparison.InvariantCultureIgnoreCase)) { // if the next symbol is + or -, accept it, then read the exponent integer if(sr.Peek()=='-' || sr.Peek()=='+') numberstr+=sr.Read(1); numberstr += sr.ReadWhile("0123456789"); } sr.SkipWhitespace(); double realpart; try { realpart = ParseDouble(numberstr); } catch (FormatException x) { throw new ParseException("invalid float format", x); } double imaginarypart = ParseImaginaryPart(sr); if(sr.Read()!=')') throw new ParseException("expected ) to end a complex number"); return new Ast.ComplexNumberNode { Real = realpart, Imaginary = imaginarypart }; } // imaginary double imag = ParseImaginaryPart(sr); return new Ast.ComplexNumberNode { Real=0, Imaginary=imag }; } private double ParseImaginaryPart(SeekableStringReader sr) { //imaginary = ['+' | '-' ] ( float | int ) 'j' . // string numberstr = sr.ReadUntil('j'); // try { // return this.ParseDouble(numberstr); // } catch(FormatException x) { // throw new ParseException("invalid float format", x); // } if(!sr.HasMore()) throw new ParseException("unexpected end of input string"); char signOrDigit = sr.Peek(); if(signOrDigit=='+') sr.Read(); // skip the '+' // now an int or float follows. double doubleValue; int bookmark = sr.Bookmark(); try { doubleValue = ParseFloat(sr).Value; } catch (ParseException) { sr.FlipBack(bookmark); var integerPart = ParseInt(sr); var integerNode = integerPart as Ast.IntegerNode; if (integerNode != null) doubleValue = integerNode.Value; else { var longNode = integerPart as Ast.LongNode; if (longNode != null) doubleValue = longNode.Value; else { var decimalNode = integerPart as Ast.DecimalNode; if (decimalNode != null) doubleValue = Convert.ToDouble(decimalNode.Value); else throw new ParseException("not an integer for the imaginary part"); } } } // now a 'j' must follow! sr.SkipWhitespace(); try { if(sr.Read()!='j') throw new ParseException("not an imaginary part"); } catch (IndexOutOfRangeException) { throw new ParseException("not an imaginary part"); } return doubleValue; } private Ast.PrimitiveNode ParseString(SeekableStringReader sr) { char quotechar = sr.Read(); // ' or " StringBuilder sb = new StringBuilder(10); while(sr.HasMore()) { char c = sr.Read(); if(c=='\\') { // backslash unescape c = sr.Read(); switch(c) { case '\\': sb.Append('\\'); break; case '\'': sb.Append('\''); break; case '"': sb.Append('"'); break; case 'a': sb.Append('\a'); break; case 'b': sb.Append('\b'); break; case 'f': sb.Append('\f'); break; case 'n': sb.Append('\n'); break; case 'r': sb.Append('\r'); break; case 't': sb.Append('\t'); break; case 'v': sb.Append('\v'); break; case 'x': // "\x00" sb.Append((char)int.Parse(sr.Read(2), NumberStyles.HexNumber)); break; case 'u': // "\u0000" sb.Append((char)int.Parse(sr.Read(4), NumberStyles.HexNumber)); break; default: sb.Append(c); break; } } else if(c==quotechar) { // end of string return new Ast.StringNode(sb.ToString()); } else { sb.Append(c); } } throw new ParseException("unclosed string"); } private Ast.PrimitiveNode ParseBytes(SeekableStringReader sr) { sr.Read(); // skip the 'b' char quotechar = sr.Read(); // ' or " var bytes = new List(); while(sr.HasMore()) { char c = sr.Read(); if(c=='\\') { // backslash unescape c = sr.Read(); switch(c) { case '\\': bytes.Add((byte)'\\'); break; case '\'': bytes.Add((byte)'\''); break; case '"': bytes.Add((byte)'"'); break; case 'a': bytes.Add((byte)'\a'); break; case 'b': bytes.Add((byte)'\b'); break; case 'f': bytes.Add((byte)'\f'); break; case 'n': bytes.Add((byte)'\n'); break; case 'r': bytes.Add((byte)'\r'); break; case 't': bytes.Add((byte)'\t'); break; case 'v': bytes.Add((byte)'\v'); break; case 'x': // "\x00" bytes.Add(byte.Parse(sr.Read(2), NumberStyles.HexNumber)); break; default: bytes.Add((byte)c); break; } } else if(c==quotechar) { // end of bytes return new Ast.BytesNode(bytes.ToArray()); } else { bytes.Add((byte)c); } } throw new ParseException("unclosed bytes"); } private Ast.PrimitiveNode ParseBool(SeekableStringReader sr) { // True,False string b = sr.ReadUntil('e'); switch (b) { case "Tru": return new Ast.BooleanNode(true); case "Fals": return new Ast.BooleanNode(false); } throw new ParseException("expected bool, True or False"); } private Ast.NoneNode ParseNone(SeekableStringReader sr) { // None string n = sr.ReadUntil('e'); if(n=="Non") return Ast.NoneNode.Instance; throw new ParseException("expected None"); } private double ParseDouble(string numberstr) { switch (numberstr) { // the number is possibly +Inf/-Inf, these are encoded as "1e30000" and "-1e30000" case "1e30000": return double.PositiveInfinity; case "-1e30000": return double.NegativeInfinity; } return double.Parse(numberstr, CultureInfo.InvariantCulture); } /// /// Utility function to convert obj back to actual bytes if it is a serpent-encoded bytes dictionary /// (a IDictionary with base-64 encoded 'data' in it and 'encoding'='base64'). /// If obj is already a byte array, return obj unmodified. /// If it is something else, throw an ArgumentException /// public static byte[] ToBytes(object obj) { Hashtable hashtable = obj as Hashtable; if(hashtable!=null) { string data = null; string encoding = null; if(hashtable.Contains("data")) data = (string)hashtable["data"]; if(hashtable.Contains("encoding")) encoding = (string)hashtable["encoding"]; if(data==null || "base64"!=encoding) { throw new ArgumentException("argument is neither bytearray nor serpent base64 encoded bytes dict"); } return Convert.FromBase64String(data); } var dict = obj as IDictionary; if(dict!=null) { string data; string encoding; bool hasData = dict.TryGetValue("data", out data); bool hasEncoding = dict.TryGetValue("encoding", out encoding); if(!hasData || !hasEncoding || encoding!="base64") { throw new ArgumentException("argument is neither bytearray nor serpent base64 encoded bytes dict"); } return Convert.FromBase64String(data); } var dict2 = obj as IDictionary; if(dict2!=null) { object dataobj; object encodingobj; bool hasData = dict2.TryGetValue("data", out dataobj); bool hasEncoding = dict2.TryGetValue("encoding", out encodingobj); string data = (string)dataobj; string encoding = (string)encodingobj; if(!hasData || !hasEncoding || encoding!="base64") { throw new ArgumentException("argument is neither bytearray nor serpent base64 encoded bytes dict"); } return Convert.FromBase64String(data); } var bytearray = obj as byte[]; if(bytearray!=null) { return bytearray; } throw new ArgumentException("argument is neither bytearray nor serpent base64 encoded bytes dict"); } } } Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/Razorvine.Serpent.csproj000066400000000000000000000031451425606146200277010ustar00rootroot00000000000000 netstandard2.0 6 Irmen de Jong Serpent Python literal expression serialization Razorvine.Serpent 1.40.0 1.40.0.0 1.40.0.0 true false Serpent.snk Serpent provides Python ast.literal_eval() compatible object tree serialization. Serpent provides Python ast.literal_eval() compatible object tree serialization. It serializes an object tree into bytes that can be transferred to Python and decoded there (using ast.literal_eval()). It can ofcourse also deserialize such a Python expression itself, back into the equivalent .NET datatypes. More info for the Python version is on Pypi: https://pypi.python.org/pypi/serpent Copyright Irmen de Jong MIT https://github.com/irmen/serpent https://github.com/irmen/Serpent.git git serialization python pyro assembly is now strong-name signed (so other strong-named assemblies can reference it, most notably, Razorvine.Pyrolite) Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/SeekableStringReader.cs000066400000000000000000000107351425606146200274400ustar00rootroot00000000000000using System; namespace Razorvine.Serpent { /// /// A special string reader that is suitable for the parser to read through /// the expression string. You can rewind it, set bookmarks to flip back to, etc. /// public class SeekableStringReader : IDisposable { // ReSharper disable RedundantDefaultMemberInitializer private string _str; private int _cursor = 0; private int _bookmark = -1; public SeekableStringReader(string str) { if(str==null) throw new ArgumentNullException(nameof(str)); _str = str; } /// /// Make a nested reader with its own cursor and bookmark. /// The cursor starts at the same position as the parent. /// /// public SeekableStringReader(SeekableStringReader parent) { _str = parent._str; _cursor = parent._cursor; } /// /// Is there more to read? /// public bool HasMore() { return _cursor<_str.Length; } /// /// What is the next character? /// public char Peek() { return _str[_cursor]; } /// /// What are the next characters that will be read? /// public string Peek(int count) { return _str.Substring(_cursor, Math.Min(count, _str.Length-_cursor)); } /// /// Read a single character. /// public char Read() { return _str[_cursor++]; } /// /// Read a number of characters. /// public string Read(int count) { if(count<0) throw new ParseException("use Rewind to seek back"); int safecount = Math.Min(count, _str.Length-_cursor); if(safecount==0 && count>0) throw new ParseException("no more data"); string result = _str.Substring(_cursor, safecount); _cursor += safecount; return result; } /// /// Read everything until one of the sentinel(s), which must exist in the string. /// Sentinel char is read but not returned in the result. /// public string ReadUntil(params char[] sentinels) { int index = _str.IndexOfAny(sentinels, _cursor); if (index < 0) throw new ParseException("terminator not found"); string result = _str.Substring(_cursor, index-_cursor); _cursor = index+1; return result; } /// /// Read everything as long as the char occurs in the accepted characters. /// public string ReadWhile(string accepted) { int start = _cursor; while(_cursor < _str.Length) { if(accepted.IndexOf(_str[_cursor])>=0) ++_cursor; else break; } return _str.Substring(start, _cursor-start); } /// /// Read away any whitespace. /// If a comment follows ('# bla bla') read away that as well /// public void SkipWhitespace() { while(HasMore()) { char c=Read(); if(c=='#') { ReadUntil('\n'); return; } if(!char.IsWhiteSpace(c)) { Rewind(1); return; } } } /// /// Returns the rest of the data until the end. /// public string Rest() { if(_cursor>=_str.Length) throw new ParseException("no more data"); string result=_str.Substring(_cursor); _cursor = _str.Length; return result; } /// /// Rewind a number of characters. /// public void Rewind(int count) { _cursor = Math.Max(0, _cursor-count); } /// /// Return a bookmark to rewind to later. /// public int Bookmark() { return _cursor; } /// /// Flip back to previously set bookmark. /// public void FlipBack(int towhichbookmark) { _cursor = towhichbookmark; } /// /// Sync the position and bookmark with the current position in another reader. /// public void Sync(SeekableStringReader inner) { _bookmark = inner._bookmark; _cursor = inner._cursor; } /// /// Extract a piece of context around the current cursor (if you set cursor to -1) /// or around a given position in the string (if you set cursor>=0). /// public void Context(int crsr, int width, out string left, out string right) { if(crsr<0) crsr=_cursor; int leftStrt = Math.Max(0, crsr-width); int leftLen = crsr-leftStrt; int rightLen = Math.Min(width, _str.Length-crsr); left = _str.Substring(leftStrt, leftLen); right = _str.Substring(crsr, rightLen); } public void Dispose() { _str = null; } } } Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/Serializer.cs000066400000000000000000000370331425606146200255240ustar00rootroot00000000000000using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Runtime.Serialization; using System.Text; using System.Xml; namespace Razorvine.Serpent { /// /// Serialize an object tree to a byte stream. /// It is not thread-safe: make sure you're not making changes to the object tree that is being serialized. /// [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "UnusedParameter.Global")] [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] public class Serializer { /// /// indent output? /// public bool Indent; /// /// include namespace prefix for classes that are serialized to dict? /// public bool NamespaceInClassName; /// /// Use bytes literal representation instead of base-64 encoding? /// public bool BytesRepr; /// /// The maximum nesting level of the object graphs that you want to serialize. /// This limit has been set to avoid troublesome stack overflow errors. /// (If it is reached, an IllegalArgumentException is thrown instead with a clear message) /// public int MaximumLevel = 500; // avoids stackoverflow errors private static readonly IDictionary> ClassToDictRegistry = new Dictionary>(); /// /// Initialize the serializer. /// /// indent the output over multiple lines (default=false) /// include namespace prefix for class names or only use the class name itself /// use bytes literal representation instead of base-64 encoding for bytes types? (default=false) public Serializer(bool indent=false, bool namespaceInClassName=false, bool bytesRepr=false) { Indent = indent; NamespaceInClassName = namespaceInClassName; BytesRepr = bytesRepr; } /// /// Register a custom class-to-dict converter. /// public static void RegisterClass(Type clazz, Func converter) { ClassToDictRegistry[clazz] = converter; } /// /// Serialize the object tree to bytes. /// public byte[] Serialize(object obj) { using(StringWriter tw = new StringWriter()) { tw.Write("# serpent utf-8 python3.2\n"); Serialize(obj, tw, 0); tw.Flush(); return Encoding.UTF8.GetBytes(tw.ToString()); } } protected void Serialize(object obj, TextWriter tw, int level) { if(level>MaximumLevel) throw new ArgumentException("Object graph nesting too deep. Increase serializer.MaximumLevel if you think you need more."); // null -> None // hashtables/dictionaries -> dict // hashset -> set // array -> tuple // byte arrays --> base64 // any other icollection --> list // date/timespan/uuid/exception -> custom mapping // random class --> public properties to dict // primitive types --> simple mapping Type t = obj?.GetType(); if(obj==null) { tw.Write("None"); } else if(obj is string) { Serialize_string((string)obj, tw, level); } else if(t.IsPrimitive) { Serialize_primitive(obj, tw, level); } else if(obj is decimal) { Serialize_decimal((decimal)obj, tw, level); } else if(obj is Enum) { Serialize_string(obj.ToString(), tw, level); } else if(obj is IDictionary) { Serialize_dict((IDictionary)obj, tw, level); } else if(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(HashSet<>)) { IEnumerable x = (IEnumerable) obj; var list = new List(); foreach(object elt in x) list.Add(elt); var setvalues = list.ToArray(); Serialize_set(setvalues, tw, level); } else if(obj is byte[]) { Serialize_bytes((byte[])obj, tw, level); } else if(obj is Array) { Serialize_tuple((ICollection) obj, tw, level); } else if(obj is ICollection) { Serialize_list((ICollection) obj, tw, level); } else if(obj is DateTimeOffset) { Serialize_datetimeoffset((DateTimeOffset)obj, tw, level); } else if(obj is DateTime) { Serialize_datetime((DateTime)obj, tw, level); } else if(obj is TimeSpan) { Serialize_timespan((TimeSpan)obj, tw, level); } else if(obj is Exception) { Serialize_exception((Exception)obj, tw, level); } else if(obj is Guid) { Serialize_guid((Guid) obj, tw, level); } else if(obj is ComplexNumber) { Serialize_complex((ComplexNumber) obj, tw, level); } else { Serialize_class(obj, tw, level); } } protected void Serialize_tuple(ICollection array, TextWriter tw, int level) { tw.Write("("); Serialize_sequence_elements(array, array.Count==1, tw, level+1); if(Indent && array.Count>0) tw.Write(string.Join(" ", new string[level+1])); tw.Write(")"); } protected void Serialize_list(ICollection list, TextWriter tw, int level) { tw.Write("["); Serialize_sequence_elements(list, false, tw, level+1); if(Indent && list.Count>0) tw.Write(string.Join(" ", new string[level+1])); tw.Write("]"); } protected int DictentryCompare(DictionaryEntry d1, DictionaryEntry d2) { IComparable c1 = d1.Key as IComparable; IComparable c2 = d2.Key as IComparable; return c1?.CompareTo(c2) ?? 0; } protected void Serialize_dict(IDictionary dict, TextWriter tw, int level) { if(dict.Count==0) { tw.Write("{}"); return; } int counter=0; if(Indent) { string innerindent = string.Join(" ", new string[level+2]); tw.Write("{\n"); var entries = new DictionaryEntry[dict.Count]; dict.CopyTo(entries, 0); try { Array.Sort(entries, DictentryCompare); } catch (InvalidOperationException) { // ignore sorting of incomparable elements } catch (ArgumentException) { // ignore sorting of incomparable elements } foreach(DictionaryEntry x in entries) { tw.Write(innerindent); Serialize(x.Key, tw, level+1); tw.Write(": "); Serialize(x.Value, tw, level+1); counter++; if(counter0) { tw.Write("{"); if(Indent) { try { Array.Sort(set); } catch (InvalidOperationException) { // ignore sorting of incomparable elements. } catch (ArgumentException) { // ignore sorting of incomparable elements. } } Serialize_sequence_elements(set, false, tw, level+1); if(Indent) tw.Write(string.Join(" ", new string[level+1])); tw.Write("}"); } else { // empty set literal doesn't exist, replace with empty tuple Serialize_tuple(new object[0], tw, level+1); } } protected void Serialize_sequence_elements(ICollection elements, bool trailingComma, TextWriter tw, int level) { if(elements.Count==0) return; int count=0; if(Indent) { tw.Write("\n"); string innerindent = string.Join(" ", new string[level+1]); foreach(object e in elements) { tw.Write(innerindent); Serialize(e, tw, level); count++; if(count { {"data", str}, {"encoding", "base64"} }; Serialize_dict(dict, tw, level); } } private static void HandleQuotes(TextWriter tw, bool containsSingleQuote, StringBuilder b, bool containsQuote, bool isBytes) { if (!containsSingleQuote) { b.Insert(0, '\''); b.Append('\''); if(isBytes) tw.Write('b'); tw.Write(b.ToString()); } else if (!containsQuote) { b.Insert(0, '"'); b.Append('"'); if(isBytes) tw.Write('b'); tw.Write(b.ToString()); } else { string str2 = b.ToString(); str2 = str2.Replace("'", "\\'"); if(isBytes) tw.Write('b'); tw.Write("'"); tw.Write(str2); tw.Write("'"); } } // the repr translation table for characters 0x00-0xff private static readonly string[] Repr255; private static readonly string[] BytesRepr255; static Serializer() { Repr255=new string[256]; BytesRepr255=new string[256]; for(int c=0; c<32; ++c) { Repr255[c] = "\\x"+c.ToString("x2"); BytesRepr255[c] = "\\x"+c.ToString("x2"); } for(int c=0x20; c<0x7f; ++c) { Repr255[c] = Convert.ToString((char)c); BytesRepr255[c] = Convert.ToString((char)c); } for(int c=0x7f; c<=0xa0; ++c) { Repr255[c] = "\\x"+c.ToString("x2"); } for(int c=0xa1; c<=0xff; ++c) { Repr255[c] = Convert.ToString((char)c); } for(int c=0x7f; c<=0xff; ++c) { BytesRepr255[c] = "\\x"+c.ToString("x2"); } // odd ones out: Repr255['\t'] = "\\t"; Repr255['\n'] = "\\n"; Repr255['\r'] = "\\r"; Repr255['\\'] = "\\\\"; Repr255[0xad] = "\\xad"; BytesRepr255['\t'] = "\\t"; BytesRepr255['\n'] = "\\n"; BytesRepr255['\r'] = "\\r"; BytesRepr255['\\'] = "\\\\"; } // ReSharper disable once UnusedParameter.Global // ReSharper disable once MemberCanBePrivate.Global protected void Serialize_string(string str, TextWriter tw, int level) { // create a 'repr' string representation following the same escaping rules as python 3.x repr() does. StringBuilder b=new StringBuilder(str.Length*2); bool containsSingleQuote=false; bool containsQuote=false; foreach(char c in str) { containsSingleQuote |= c=='\''; containsQuote |= c=='"'; if(c<256) { // characters 0..255 via quick lookup table b.Append(Repr255[c]); } else { if(char.IsLetterOrDigit(c) || char.IsNumber(c) || char.IsPunctuation(c) || char.IsSymbol(c)) { b.Append(c); } else { b.Append("\\u"); b.Append(((int)c).ToString("x4")); } } } HandleQuotes(tw, containsSingleQuote, b, containsQuote, false); } protected void Serialize_datetime(DateTime dt, TextWriter tw, int level) { string s = dt.Millisecond == 0 ? XmlConvert.ToString(dt, "yyyy-MM-ddTHH:mm:ss") : XmlConvert.ToString(dt, "yyyy-MM-ddTHH:mm:ss.fff"); Serialize_string(s, tw, level); } protected void Serialize_datetimeoffset(DateTimeOffset dto, TextWriter tw, int level) { string s = XmlConvert.ToString(dto); Serialize_string(s, tw, level); } protected void Serialize_timespan(TimeSpan span, TextWriter tw, int level) { Serialize_primitive(span.TotalSeconds, tw, level); } protected void Serialize_exception(Exception exc, TextWriter tw, int level) { IDictionary dict; Func converter; ClassToDictRegistry.TryGetValue(exc.GetType(), out converter); if(converter!=null) { // build a custom property dict from the object. dict = converter(exc); } else { string className = NamespaceInClassName ? exc.GetType().FullName : exc.GetType().Name; dict = new Dictionary { {"__class__", className}, {"__exception__", true}, {"args", new []{exc.Message} }, {"attributes", exc.Data} }; } Serialize_dict(dict, tw, level); } protected void Serialize_guid(Guid guid, TextWriter tw, int level) { // simple string representation of the guid Serialize_string(guid.ToString(), tw, level); } protected void Serialize_decimal(decimal dec, TextWriter tw, int level) { Serialize_string(dec.ToString(CultureInfo.InvariantCulture), tw, level); } protected void Serialize_primitive(object obj, TextWriter tw, int level) { if(obj is float) { float f = (float)obj; double d = f; Serialize_primitive(d, tw, level); } else if(obj is double) { double d = (double) obj; if(double.IsPositiveInfinity(d)) { // output a literal expression that overflows the float and results in +/-INF tw.Write("1e30000"); } else if(double.IsNegativeInfinity(d)) { tw.Write("-1e30000"); } else if(double.IsNaN(d)) { // there's no literal expression for a float NaN... tw.Write("{'__class__':'float','value':'nan'}"); } else { tw.Write(Convert.ToString(obj, CultureInfo.InvariantCulture)); } } else { tw.Write(Convert.ToString(obj, CultureInfo.InvariantCulture)); } } protected void Serialize_complex(ComplexNumber cplx, TextWriter tw, int level) { tw.Write("("); Serialize_primitive(cplx.Real, tw, level); if(cplx.Imaginary>=0) tw.Write("+"); Serialize_primitive(cplx.Imaginary, tw, level); tw.Write("j)"); } protected void Serialize_class(object obj, TextWriter tw, int level) { Type objType = obj.GetType(); IDictionary dict; var converter = GetCustomConverter(objType); if(converter!=null) { // build a custom property dict from the object. dict = converter(obj); } else { bool isAnonymousClass = objType.Name.StartsWith("<>"); dict = new Dictionary(); if(!isAnonymousClass) { // only provide the class name when it is not an anonymous class if(NamespaceInClassName) dict["__class__"] = objType.FullName; else dict["__class__"] = objType.Name; } var properties=objType.GetProperties(); foreach(var propinfo in properties) { if (!propinfo.CanRead) continue; string name=propinfo.Name; try { dict[name]=propinfo.GetValue(obj, null); } catch (Exception x) { throw new SerializationException("cannot serialize a property:",x); } } } Serialize_dict(dict, tw, level); } // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBeMadeStatic.Global // ReSharper disable once InconsistentNaming protected Func GetCustomConverter(Type obj_type) { Func converter; if(ClassToDictRegistry.TryGetValue(obj_type, out converter)) return converter; // exact match // check if there's a custom converter registered for an interface or abstract base class // that this object implements or inherits from. foreach(var x in ClassToDictRegistry) { if(x.Key.IsAssignableFrom(obj_type)) { return x.Value; } } return null; } } } Serpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/Serpent.snk000066400000000000000000000011241425606146200252110ustar00rootroot00000000000000$RSA2ˤsW0#yC۵ `/\k1G+wxk 2 6$ 2>y7]gV.ԕo@ȣܶ@sGE59S9?O,f*ۅR'i=|VF߇u uȺrV~nvN_0y\a{Z}NLӋM)ܗ M"5ߡH4w4,6kjSerpent-serpent-1.41/dotnet/Serpent/Razorvine.Serpent/Version.cs000066400000000000000000000005501425606146200250320ustar00rootroot00000000000000/*** Serpent, a Python literal expression serializer/deserializer (a.k.a. Python's ast.literal_eval in .NET) Copyright: Irmen de Jong (irmen@razorvine.net) Software license: "MIT software license". See http://opensource.org/licenses/MIT ***/ namespace Razorvine.Serpent { public static class LibraryVersion { public const string Version = "1.40"; } } Serpent-serpent-1.41/dotnet/Serpent/Serpent.sln000066400000000000000000000023651425606146200216240ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Razorvine.Serpent", "Razorvine.Serpent\Razorvine.Serpent.csproj", "{2FD56EEF-23E7-4CE8-94FA-92B0FC6E5CA4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{10CAEE38-7E25-420D-AC29-98960E8785D2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2FD56EEF-23E7-4CE8-94FA-92B0FC6E5CA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2FD56EEF-23E7-4CE8-94FA-92B0FC6E5CA4}.Debug|Any CPU.Build.0 = Debug|Any CPU {2FD56EEF-23E7-4CE8-94FA-92B0FC6E5CA4}.Release|Any CPU.ActiveCfg = Release|Any CPU {2FD56EEF-23E7-4CE8-94FA-92B0FC6E5CA4}.Release|Any CPU.Build.0 = Release|Any CPU {10CAEE38-7E25-420D-AC29-98960E8785D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {10CAEE38-7E25-420D-AC29-98960E8785D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {10CAEE38-7E25-420D-AC29-98960E8785D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {10CAEE38-7E25-420D-AC29-98960E8785D2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal Serpent-serpent-1.41/dotnet/Serpent/Tests/000077500000000000000000000000001425606146200205625ustar00rootroot00000000000000Serpent-serpent-1.41/dotnet/Serpent/Tests/CycleTest.cs000066400000000000000000000064461425606146200230220ustar00rootroot00000000000000using System; using System.Collections; using System.Collections.Generic; using Xunit; // ReSharper disable CheckNamespace // ReSharper disable InconsistentNaming namespace Razorvine.Serpent.Test { public class CycleTest { [Fact] public void testTupleOk() { var ser = new Serializer(); var t = new[] {1, 2, 3}; var d = new object[] {t, t, t}; var data = ser.Serialize(d); var parser = new Parser(); parser.Parse(data); } [Fact] public void testListOk() { var ser = new Serializer(); var t = new List {1, 2, 3}; var d = new List {t, t, t}; var data = ser.Serialize(d); var parser = new Parser(); parser.Parse(data); } [Fact] public void testDictOk() { var ser = new Serializer(); var t = new Hashtable {["a"] = 1}; var d = new Hashtable { ["x"] = t, ["y"] = t, ["z"] = t }; var data = ser.Serialize(d); var parser = new Parser(); parser.Parse(data); } [Fact] public void testListCycle() { var ser = new Serializer(); var d = new List {1, 2}; d.Add(d); Assert.Throws(() => ser.Serialize(d)); } [Fact] public void testDictCycle() { var ser = new Serializer(); var d = new Hashtable { ["x"] = 1, ["y"] = 2 }; d["z"] = d; Assert.Throws(() => ser.Serialize(d)); } [Fact] public void testClassCycle() { var ser = new Serializer(); var d = new SerializeTestClass { x = 42, i = 99, s = "hello" }; d.obj = d; // make cycle try { ser.Serialize(d); throw new Exception("should not reach this"); } catch (ArgumentException x) { Assert.Contains("nesting too deep", x.Message); } } [Fact] public void testMaxLevel() { var ser = new Serializer(); Assert.Equal(500, ser.MaximumLevel); object[] array = { "level1", new object[] { "level2", new object[] { "level3", new object[] { "level 4" } } } }; ser.MaximumLevel = 4; ser.Serialize(array); // should work ser.MaximumLevel = 3; try { ser.Serialize(array); Assert.True(false, "should fail"); } catch (ArgumentException x) { Assert.Contains("too deep", x.Message); } } } }Serpent-serpent-1.41/dotnet/Serpent/Tests/Example.cs000066400000000000000000000071211425606146200225050ustar00rootroot00000000000000using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; using Xunit; // ReSharper disable CheckNamespace namespace Razorvine.Serpent.Test { /// /// Example usage. /// public class Example { [Fact(Skip = "this is an example")] public void ExampleUsage() { Console.WriteLine("using serpent library version {0}", LibraryVersion.Version); var data = new Dictionary { {"tuple", new[] {1, 2, 3}}, {"date", DateTime.Now}, {"set", new HashSet {"a", "b", "c"}}, { "class", new SampleClass { Name = "Sally", Age = 26 } } }; // serialize data structure to bytes var serpent = new Serializer(true); var ser = serpent.Serialize(data); // print it on the screen, but normally you'd store byte bytes in a file or transfer them across a network connection Console.WriteLine("Serialized:"); Console.WriteLine(Encoding.UTF8.GetString(ser)); // parse the serialized bytes back into an abstract syntax tree of the datastructure var parser = new Parser(); var ast = parser.Parse(ser); Console.WriteLine("\nParsed AST:"); Console.WriteLine(ast.Root.ToString()); // print debug representation var dv = new DebugVisitor(); ast.Accept(dv); Console.WriteLine("DEBUG string representation:"); Console.WriteLine(dv.ToString()); // turn the Ast into regular .net objects var dict = (IDictionary) ast.GetData(); // You can get the data out of the Ast manually as well, by using the supplied visitor: // var visitor = new ObjectifyVisitor(); // ast.Accept(visitor); // var dict = (IDictionary) visitor.GetObject(); // print the results Console.WriteLine("PARSED results:"); Console.Write("tuple items: "); var tuple = (object[]) dict["tuple"]; Console.WriteLine(string.Join(", ", tuple.Select(e => e.ToString()).ToArray())); Console.WriteLine("date: {0}", dict["date"]); Console.Write("set items: "); var set = (HashSet) dict["set"]; Console.WriteLine(string.Join(", ", set.Select(e => e.ToString()).ToArray())); Console.WriteLine("class attributes:"); var clazz = (IDictionary) dict["class"]; // custom classes are serialized as dicts Console.WriteLine(" type: {0}", clazz["__class__"]); Console.WriteLine(" name: {0}", clazz["name"]); Console.WriteLine(" age: {0}", clazz["age"]); Console.WriteLine(""); // parse and print the example file ser = File.ReadAllBytes("testserpent.utf8.bin"); ast = parser.Parse(ser); dv = new DebugVisitor(); ast.Accept(dv); Console.WriteLine("DEBUG string representation of the test file:"); Console.WriteLine(dv.ToString()); } [Serializable] [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] private class SampleClass { public int Age { get; set; } public string Name { get; set; } } } }Serpent-serpent-1.41/dotnet/Serpent/Tests/ParserTest.cs000066400000000000000000001013231425606146200232050ustar00rootroot00000000000000using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using Xunit; // ReSharper disable CheckNamespace // ReSharper disable SpecifyACultureInStringConversionExplicitly // ReSharper disable RedundantAssignment // ReSharper disable PossibleNullReferenceException // ReSharper disable InconsistentNaming // ReSharper disable MemberCanBeMadeStatic.Local namespace Razorvine.Serpent.Test { public class ParserTest { [Fact] public void TestBasic() { var p = new Parser(); Assert.Null(p.Parse((string) null).Root); Assert.Null(p.Parse("").Root); Assert.NotNull(p.Parse("# comment\n42\n").Root); } [Fact] public void TestComments() { var p = new Parser(); var ast = p.Parse("[ 1, 2 ]"); // no header whatsoever var visitor = new ObjectifyVisitor(); ast.Accept(visitor); var obj = visitor.GetObject(); Assert.Equal(new[] {1, 2}, obj); ast = p.Parse(@"# serpent utf-8 python2.7 [ 1, 2, # some comments here 3, 4] # more here # and here. "); visitor = new ObjectifyVisitor(); ast.Accept(visitor); obj = visitor.GetObject(); Assert.Equal(new[] {1, 2, 3, 4}, obj); } [Fact] public void TestPrimitives() { var p = new Parser(); Assert.Equal(new Ast.IntegerNode(42), p.Parse("42").Root); Assert.Equal(new Ast.IntegerNode(-42), p.Parse("-42").Root); Assert.Equal(new Ast.DoubleNode(42.331), p.Parse("42.331").Root); Assert.Equal(new Ast.DoubleNode(-42.331), p.Parse("-42.331").Root); Assert.Equal(new Ast.DoubleNode(-1.2e19), p.Parse("-1.2e+19").Root); Assert.Equal(new Ast.DoubleNode(-1.2e19), p.Parse("-1.2e19").Root); Assert.Equal(new Ast.DoubleNode(0.0004), p.Parse("4e-4").Root); Assert.Equal(new Ast.DoubleNode(40000), p.Parse("4e4").Root); Assert.Equal(new Ast.BooleanNode(true), p.Parse("True").Root); Assert.Equal(new Ast.BooleanNode(false), p.Parse("False").Root); Assert.Equal(Ast.NoneNode.Instance, p.Parse("None").Root); // long ints Assert.Equal(new Ast.DecimalNode(123456789123456789123456789M), p.Parse("123456789123456789123456789").Root); Assert.NotEqual(new Ast.LongNode(52), p.Parse("52").Root); Assert.Equal(new Ast.LongNode(123456789123456789L), p.Parse("123456789123456789").Root); Assert.Throws(() => p.Parse("123456789123456789123456789123456789")); // overflow } [Fact] public void TestWeirdFloats() { var p = new Parser(); var d = (Ast.DoubleNode) p.Parse("1e30000").Root; Assert.True(double.IsPositiveInfinity(d.Value)); d = (Ast.DoubleNode) p.Parse("-1e30000").Root; Assert.True(double.IsNegativeInfinity(d.Value)); var tuple = (Ast.TupleNode) p.Parse("(1e30000,-1e30000,{'__class__':'float','value':'nan'})").Root; Assert.Equal(3, tuple.Elements.Count); d = (Ast.DoubleNode) tuple.Elements[0]; Assert.True(double.IsPositiveInfinity(d.Value)); d = (Ast.DoubleNode) tuple.Elements[1]; Assert.True(double.IsNegativeInfinity(d.Value)); d = (Ast.DoubleNode) tuple.Elements[2]; Assert.True(double.IsNaN(d.Value)); var c = (Ast.ComplexNumberNode) p.Parse("(1e30000-1e30000j)").Root; Assert.True(double.IsPositiveInfinity(c.Real)); Assert.True(double.IsNegativeInfinity(c.Imaginary)); } [Fact] public void TestFloatPrecision() { var p = new Parser(); var serpent = new Serializer(); var ser = serpent.Serialize(1.2345678987654321); var dv = (Ast.DoubleNode) p.Parse(ser).Root; Assert.Equal(1.2345678987654321.ToString(), dv.Value.ToString()); ser = serpent.Serialize(5555.12345678987656); dv = (Ast.DoubleNode) p.Parse(ser).Root; Assert.Equal(5555.12345678987656.ToString(), dv.Value.ToString()); ser = serpent.Serialize(98765432123456.12345678987656); dv = (Ast.DoubleNode) p.Parse(ser).Root; Assert.Equal(98765432123456.12345678987656.ToString(), dv.Value.ToString()); ser = serpent.Serialize(98765432123456.12345678987656e+44); dv = (Ast.DoubleNode) p.Parse(ser).Root; Assert.Equal(98765432123456.12345678987656e+44.ToString(), dv.Value.ToString()); ser = serpent.Serialize(-98765432123456.12345678987656e-44); dv = (Ast.DoubleNode) p.Parse(ser).Root; Assert.Equal((-98765432123456.12345678987656e-44).ToString(), dv.Value.ToString()); } [Fact] public void TestEquality() { Ast.INode n1 = new Ast.IntegerNode(42); Ast.INode n2 = new Ast.IntegerNode(42); Assert.Equal(n1, n2); n2 = new Ast.IntegerNode(43); Assert.NotEqual(n1, n2); n1 = new Ast.StringNode("foo"); n2 = new Ast.StringNode("foo"); Assert.Equal(n1, n2); n2 = new Ast.StringNode("bar"); Assert.NotEqual(n1, n2); n1 = new Ast.ComplexNumberNode { Real = 1.1, Imaginary = 2.2 }; n2 = new Ast.ComplexNumberNode { Real = 1.1, Imaginary = 2.2 }; Assert.Equal(n1, n2); n2 = new Ast.ComplexNumberNode { Real = 1.1, Imaginary = 3.3 }; Assert.NotEqual(n1, n2); n1 = new Ast.KeyValueNode { Key = new Ast.IntegerNode(42), Value = new Ast.IntegerNode(42) }; n2 = new Ast.KeyValueNode { Key = new Ast.IntegerNode(42), Value = new Ast.IntegerNode(42) }; Assert.Equal(n1, n2); n1 = new Ast.KeyValueNode { Key = new Ast.IntegerNode(43), Value = new Ast.IntegerNode(43) }; Assert.NotEqual(n1, n2); n1 = Ast.NoneNode.Instance; n2 = Ast.NoneNode.Instance; Assert.Equal(n1, n2); n2 = new Ast.IntegerNode(42); Assert.NotEqual(n1, n2); n1 = new Ast.DictNode { Elements = new List { new Ast.KeyValueNode { Key = new Ast.IntegerNode(42), Value = new Ast.IntegerNode(42) } } }; n2 = new Ast.DictNode { Elements = new List { new Ast.KeyValueNode { Key = new Ast.IntegerNode(42), Value = new Ast.IntegerNode(42) } } }; Assert.Equal(n1, n2); n2 = new Ast.DictNode { Elements = new List { new Ast.KeyValueNode { Key = new Ast.IntegerNode(42), Value = new Ast.IntegerNode(43) } } }; Assert.NotEqual(n1, n2); n1 = new Ast.ListNode { Elements = new List { new Ast.IntegerNode(42) } }; n2 = new Ast.ListNode { Elements = new List { new Ast.IntegerNode(42) } }; Assert.Equal(n1, n2); n2 = new Ast.ListNode { Elements = new List { new Ast.IntegerNode(43) } }; Assert.NotEqual(n1, n2); n1 = new Ast.SetNode { Elements = new List { new Ast.IntegerNode(42) } }; n2 = new Ast.SetNode { Elements = new List { new Ast.IntegerNode(42) } }; Assert.Equal(n1, n2); n2 = new Ast.SetNode { Elements = new List { new Ast.IntegerNode(43) } }; Assert.NotEqual(n1, n2); n1 = new Ast.TupleNode { Elements = new List { new Ast.IntegerNode(42) } }; n2 = new Ast.TupleNode { Elements = new List { new Ast.IntegerNode(42) } }; Assert.Equal(n1, n2); n2 = new Ast.TupleNode { Elements = new List { new Ast.IntegerNode(43) } }; Assert.NotEqual(n1, n2); } [Fact] public void TestDictEquality() { var dict1 = new Ast.DictNode(); var kv = new Ast.KeyValueNode { Key = new Ast.StringNode("key1"), Value = new Ast.IntegerNode(42) }; dict1.Elements.Add(kv); kv = new Ast.KeyValueNode { Key = new Ast.StringNode("key2"), Value = new Ast.IntegerNode(43) }; dict1.Elements.Add(kv); kv = new Ast.KeyValueNode { Key = new Ast.StringNode("key3"), Value = new Ast.IntegerNode(44) }; dict1.Elements.Add(kv); var dict2 = new Ast.DictNode(); kv = new Ast.KeyValueNode { Key = new Ast.StringNode("key2"), Value = new Ast.IntegerNode(43) }; dict2.Elements.Add(kv); kv = new Ast.KeyValueNode { Key = new Ast.StringNode("key3"), Value = new Ast.IntegerNode(44) }; dict2.Elements.Add(kv); kv = new Ast.KeyValueNode { Key = new Ast.StringNode("key1"), Value = new Ast.IntegerNode(42) }; dict2.Elements.Add(kv); Assert.Equal(dict1, dict2); kv = new Ast.KeyValueNode { Key = new Ast.StringNode("key4"), Value = new Ast.IntegerNode(45) }; dict2.Elements.Add(kv); Assert.NotEqual(dict1, dict2); } [Fact] public void TestSetEquality() { var set1 = new Ast.SetNode(); set1.Elements.Add(new Ast.IntegerNode(1)); set1.Elements.Add(new Ast.IntegerNode(2)); set1.Elements.Add(new Ast.IntegerNode(3)); var set2 = new Ast.SetNode(); set2.Elements.Add(new Ast.IntegerNode(2)); set2.Elements.Add(new Ast.IntegerNode(3)); set2.Elements.Add(new Ast.IntegerNode(1)); Assert.Equal(set1, set2); set2.Elements.Add(new Ast.IntegerNode(0)); Assert.NotEqual(set1, set2); } [Fact] public void TestPrintSingle() { var p = new Parser(); // primitives Assert.Equal("42", p.Parse("42").Root.ToString()); Assert.Equal("-42.331", p.Parse("-42.331").Root.ToString()); Assert.Equal("-42.0", p.Parse("-42.0").Root.ToString()); Assert.Equal("-2E+20", p.Parse("-2E20").Root.ToString()); Assert.Equal("2.0", p.Parse("2.0").Root.ToString()); Assert.Equal("1.2E+19", p.Parse("1.2e19").Root.ToString()); Assert.Equal("True", p.Parse("True").Root.ToString()); Assert.Equal("'hello'", p.Parse("'hello'").Root.ToString()); Assert.Equal("'\\n'", p.Parse("'\n'").Root.ToString()); Assert.Equal("'\\''", p.Parse("'\\''").Root.ToString()); Assert.Equal("'\"'", p.Parse("'\\\"'").Root.ToString()); Assert.Equal("'\"'", p.Parse("'\"'").Root.ToString()); Assert.Equal("'\\\\'", p.Parse("'\\\\'").Root.ToString()); Assert.Equal("None", p.Parse("None").Root.ToString()); const string ustr = "'\u20ac\u2603'"; Assert.Equal(ustr, p.Parse(ustr).Root.ToString()); // complex Assert.Equal("(0+2j)", p.Parse("2j").Root.ToString()); Assert.Equal("(-1.1-2.2j)", p.Parse("(-1.1-2.2j)").Root.ToString()); Assert.Equal("(1.1+2.2j)", p.Parse("(1.1+2.2j)").Root.ToString()); // long int Assert.Equal("123456789123456789123456789", p.Parse("123456789123456789123456789").Root.ToString()); } [Fact] public void TestPrintSeq() { var p = new Parser(); //tuple Assert.Equal("()", p.Parse("()").Root.ToString()); Assert.Equal("(42,)", p.Parse("(42,)").Root.ToString()); Assert.Equal("(42,43)", p.Parse("(42,43)").Root.ToString()); // list Assert.Equal("[]", p.Parse("[]").Root.ToString()); Assert.Equal("[42]", p.Parse("[42]").Root.ToString()); Assert.Equal("[42,43]", p.Parse("[42,43]").Root.ToString()); // set Assert.Equal("{42}", p.Parse("{42}").Root.ToString()); Assert.Equal("{42,43}", p.Parse("{42,43,43,43}").Root.ToString()); // dict Assert.Equal("{}", p.Parse("{}").Root.ToString()); Assert.Equal("{'a':42}", p.Parse("{'a': 42}").Root.ToString()); Assert.Equal("{'a':42,'b':43}", p.Parse("{'a': 42, 'b': 43}").Root.ToString()); Assert.Equal("{'a':42,'b':45}", p.Parse("{'a': 42, 'b': 43, 'b': 44, 'b': 45}").Root.ToString()); } [Fact] public void TestInvalidPrimitives() { var p = new Parser(); Assert.Throws(() => p.Parse("1+2")); Assert.Throws(() => p.Parse("1-2")); Assert.Throws(() => p.Parse("1.1+2.2")); Assert.Throws(() => p.Parse("1.1-2.2")); Assert.Throws(() => p.Parse("True+2")); Assert.Throws(() => p.Parse("False-2")); Assert.Throws(() => p.Parse("3j+2")); Assert.Throws(() => p.Parse("3j-2")); Assert.Throws(() => p.Parse("None+2")); Assert.Throws(() => p.Parse("None-2")); } [Fact] public void TestComplex() { var p = new Parser(); var cplx = new Ast.ComplexNumberNode { Real = 4.2, Imaginary = 3.2 }; var cplx2 = new Ast.ComplexNumberNode { Real = 4.2, Imaginary = 99 }; Assert.NotEqual(cplx, cplx2); cplx2.Imaginary = 3.2; Assert.Equal(cplx, cplx2); Assert.Equal(cplx, p.Parse("(4.2+3.2j)").Root); cplx.Real = 0; Assert.Equal(cplx, p.Parse("(0+3.2j)").Root); Assert.Equal(cplx, p.Parse("3.2j").Root); Assert.Equal(cplx, p.Parse("+3.2j").Root); cplx.Imaginary = -3.2; Assert.Equal(cplx, p.Parse("-3.2j").Root); cplx.Real = -9.9; Assert.Equal(cplx, p.Parse("(-9.9-3.2j)").Root); cplx.Real = 2; cplx.Imaginary = 3; Assert.Equal(cplx, p.Parse("(2+3j)").Root); cplx.Imaginary = -3; Assert.Equal(cplx, p.Parse("(2-3j)").Root); cplx.Real = 0; Assert.Equal(cplx, p.Parse("-3j").Root); cplx.Real = -3.2e32; cplx.Imaginary = -9.9e44; Assert.Equal(cplx, p.Parse("(-3.2e32 -9.9e44j)").Root); Assert.Equal(cplx, p.Parse("(-3.2e+32 -9.9e+44j)").Root); Assert.Equal(cplx, p.Parse("(-3.2e32-9.9e44j)").Root); Assert.Equal(cplx, p.Parse("(-3.2e+32-9.9e+44j)").Root); cplx.Imaginary = 9.9e44; Assert.Equal(cplx, p.Parse("(-3.2e32+9.9e44j)").Root); Assert.Equal(cplx, p.Parse("(-3.2e+32+9.9e+44j)").Root); cplx.Real = -3.2e-32; cplx.Imaginary = -9.9e-44; Assert.Equal(cplx, p.Parse("(-3.2e-32-9.9e-44j)").Root); } [Fact] public void TestComplexPrecision() { var p = new Parser(); var cv = (Ast.ComplexNumberNode) p.Parse("(98765432123456.12345678987656+665544332211.9998877665544j)") .Root; Assert.Equal(98765432123456.12345678987656, cv.Real); Assert.Equal(665544332211.9998877665544, cv.Imaginary); cv = (Ast.ComplexNumberNode) p.Parse("(98765432123456.12345678987656-665544332211.9998877665544j)").Root; Assert.Equal(98765432123456.12345678987656, cv.Real); Assert.Equal(-665544332211.9998877665544, cv.Imaginary); cv = (Ast.ComplexNumberNode) p.Parse("(98765432123456.12345678987656e+33+665544332211.9998877665544e+44j)") .Root; Assert.Equal(98765432123456.12345678987656e+33, cv.Real); Assert.Equal(665544332211.9998877665544e+44, cv.Imaginary); cv = (Ast.ComplexNumberNode) p.Parse("(-98765432123456.12345678987656e+33-665544332211.9998877665544e+44j)") .Root; Assert.Equal(-98765432123456.12345678987656e+33, cv.Real); Assert.Equal(-665544332211.9998877665544e+44, cv.Imaginary); } [Fact] public void TestPrimitivesStuffAtEnd() { var p = new Parser(); Assert.Equal(new Ast.IntegerNode(42), p.ParseSingle(new SeekableStringReader("42@"))); Assert.Equal(new Ast.DoubleNode(42.331), p.ParseSingle(new SeekableStringReader("42.331@"))); Assert.Equal(new Ast.BooleanNode(true), p.ParseSingle(new SeekableStringReader("True@"))); Assert.Equal(Ast.NoneNode.Instance, p.ParseSingle(new SeekableStringReader("None@"))); var cplx = new Ast.ComplexNumberNode { Real = 4, Imaginary = 3 }; Assert.Equal(cplx, p.ParseSingle(new SeekableStringReader("(4+3j)@"))); cplx.Real = 0; Assert.Equal(cplx, p.ParseSingle(new SeekableStringReader("3j@"))); } [Fact] public void TestStrings() { var p = new Parser(); Assert.Equal(new Ast.StringNode("hello"), p.Parse("'hello'").Root); Assert.Equal(new Ast.StringNode("hello"), p.Parse("\"hello\"").Root); Assert.Equal(new Ast.StringNode("\\"), p.Parse("'\\\\'").Root); Assert.Equal(new Ast.StringNode("\\"), p.Parse("\"\\\\\"").Root); Assert.Equal(new Ast.StringNode("'"), p.Parse("\"'\"").Root); Assert.Equal(new Ast.StringNode("\""), p.Parse("'\"'").Root); Assert.Equal(new Ast.StringNode("tab\tnewline\n."), p.Parse("'tab\\tnewline\\n.'").Root); } [Fact] public void TestUnicode() { var p = new Parser(); const string str = "'\u20ac\u2603'"; Assert.Equal(0x20ac, str[1]); Assert.Equal(0x2603, str[2]); var bytes = Encoding.UTF8.GetBytes(str); const string value = "\u20ac\u2603"; Assert.Equal(new Ast.StringNode(value), p.Parse(str).Root); Assert.Equal(new Ast.StringNode(value), p.Parse(bytes).Root); } [Fact] public void TestLongUnicodeRoundtrip() { var chars64k = new char[65536]; for (var i = 0; i <= 65535; ++i) chars64k[i] = (char) i; var str64k = new string(chars64k); var ser = new Serializer(); var data = ser.Serialize(str64k); Assert.True(data.Length > chars64k.Length); var p = new Parser(); var result = (string) p.Parse(data).GetData(); Assert.Equal(str64k, result); } [Fact] public void TestWhitespace() { var p = new Parser(); Assert.Equal(new Ast.IntegerNode(42), p.Parse(" 42 ").Root); Assert.Equal(new Ast.IntegerNode(42), p.Parse(" 42 ").Root); Assert.Equal(new Ast.IntegerNode(42), p.Parse("\t42\r\n").Root); Assert.Equal(new Ast.IntegerNode(42), p.Parse(" \t 42 \r \n ").Root); Assert.Equal(new Ast.StringNode(" string value "), p.Parse(" ' string value ' ").Root); Assert.Throws(() => p.Parse(" ( 42 , ( 'x', 'y' ) ")); // missing tuple close ) var ast = p.Parse(" ( 42 , ( 'x', 'y' ) ) "); var tuple = (Ast.TupleNode) ast.Root; Assert.Equal(new Ast.IntegerNode(42), tuple.Elements[0]); tuple = (Ast.TupleNode) tuple.Elements[1]; Assert.Equal(new Ast.StringNode("x"), tuple.Elements[0]); Assert.Equal(new Ast.StringNode("y"), tuple.Elements[1]); p.Parse(" ( 52 , ) "); p.Parse(" [ 52 ] "); p.Parse(" { 'a' : 42 } "); p.Parse(" { 52 } "); } [Fact] public void TestTuple() { var p = new Parser(); var tuple = new Ast.TupleNode(); var tuple2 = new Ast.TupleNode(); Assert.Equal(tuple, tuple2); tuple.Elements.Add(new Ast.IntegerNode(42)); tuple2.Elements.Add(new Ast.IntegerNode(99)); Assert.NotEqual(tuple, tuple2); tuple2.Elements.Clear(); tuple2.Elements.Add(new Ast.IntegerNode(42)); Assert.Equal(tuple, tuple2); tuple2.Elements.Add(new Ast.IntegerNode(43)); tuple2.Elements.Add(new Ast.IntegerNode(44)); Assert.NotEqual(tuple, tuple2); Assert.Equal(new Ast.TupleNode(), p.Parse("()").Root); Assert.Equal(tuple, p.Parse("(42,)").Root); Assert.Equal(tuple2, p.Parse("( 42,43, 44 )").Root); Assert.Throws(() => p.Parse("(42,43]")); Assert.Throws(() => p.Parse("()@")); Assert.Throws(() => p.Parse("(42,43)@")); } [Fact] public void TestList() { var p = new Parser(); var list = new Ast.ListNode(); var list2 = new Ast.ListNode(); Assert.Equal(list, list2); list.Elements.Add(new Ast.IntegerNode(42)); list2.Elements.Add(new Ast.IntegerNode(99)); Assert.NotEqual(list, list2); list2.Elements.Clear(); list2.Elements.Add(new Ast.IntegerNode(42)); Assert.Equal(list, list2); list2.Elements.Add(new Ast.IntegerNode(43)); list2.Elements.Add(new Ast.IntegerNode(44)); Assert.NotEqual(list, list2); Assert.Equal(new Ast.ListNode(), p.Parse("[]").Root); Assert.Equal(list, p.Parse("[42]").Root); Assert.Equal(list2, p.Parse("[ 42,43, 44 ]").Root); Assert.Throws(() => p.Parse("[42,43}")); Assert.Throws(() => p.Parse("[]@")); Assert.Throws(() => p.Parse("[42,43]@")); } [Fact] public void TestSet() { var p = new Parser(); var set1 = new Ast.SetNode(); var set2 = new Ast.SetNode(); Assert.Equal(set1, set2); set1.Elements.Add(new Ast.IntegerNode(42)); set2.Elements.Add(new Ast.IntegerNode(99)); Assert.NotEqual(set1, set2); set2.Elements.Clear(); set2.Elements.Add(new Ast.IntegerNode(42)); Assert.Equal(set1, set2); set2.Elements.Add(new Ast.IntegerNode(43)); set2.Elements.Add(new Ast.IntegerNode(44)); Assert.NotEqual(set1, set2); Assert.Equal(set1, p.Parse("{42}").Root); Assert.Equal(set2, p.Parse("{ 42,43, 44 }").Root); Assert.Throws(() => p.Parse("{42,43]")); Assert.Throws(() => p.Parse("{42,43}@")); set1 = p.Parse("{'first','second','third','fourth','fifth','second', 'first', 'third', 'third' }") .Root as Ast.SetNode; Assert.Equal("'first'", set1.Elements[0].ToString()); Assert.Equal("'second'", set1.Elements[1].ToString()); Assert.Equal("'third'", set1.Elements[2].ToString()); Assert.Equal("'fourth'", set1.Elements[3].ToString()); Assert.Equal("'fifth'", set1.Elements[4].ToString()); Assert.Equal(5, set1.Elements.Count); } [Fact] public void TestDict() { var p = new Parser(); var dict1 = new Ast.DictNode(); var dict2 = new Ast.DictNode(); Assert.Equal(dict1, dict2); var kv1 = new Ast.KeyValueNode {Key = new Ast.StringNode("key"), Value = new Ast.IntegerNode(42)}; var kv2 = new Ast.KeyValueNode {Key = new Ast.StringNode("key"), Value = new Ast.IntegerNode(99)}; Assert.NotEqual(kv1, kv2); kv2.Value = new Ast.IntegerNode(42); Assert.Equal(kv1, kv2); dict1.Elements.Add(new Ast.KeyValueNode {Key = new Ast.StringNode("key1"), Value = new Ast.IntegerNode(42)}); dict2.Elements.Add(new Ast.KeyValueNode {Key = new Ast.StringNode("key1"), Value = new Ast.IntegerNode(99)}); Assert.NotEqual(dict1, dict2); dict2.Elements.Clear(); dict2.Elements.Add(new Ast.KeyValueNode {Key = new Ast.StringNode("key1"), Value = new Ast.IntegerNode(42)}); Assert.Equal(dict1, dict2); dict2.Elements.Add(new Ast.KeyValueNode {Key = new Ast.StringNode("key2"), Value = new Ast.IntegerNode(43)}); dict2.Elements.Add(new Ast.KeyValueNode {Key = new Ast.StringNode("key3"), Value = new Ast.IntegerNode(44)}); Assert.NotEqual(dict1, dict2); Assert.Equal(new Ast.DictNode(), p.Parse("{}").Root); Assert.Equal(dict1, p.Parse("{'key1': 42}").Root); Assert.Equal(dict2, p.Parse("{'key1': 42, 'key2': 43, 'key3':44}").Root); Assert.Throws(() => p.Parse("{'key': 42]")); Assert.Throws(() => p.Parse("{}@")); Assert.Throws(() => p.Parse("{'key': 42}@")); dict1 = p.Parse("{'a': 1, 'b': 2, 'c': 3, 'c': 4, 'c': 5, 'c': 6}").Root as Ast.DictNode; Assert.Equal("'a':1", dict1.Elements[0].ToString()); Assert.Equal("'b':2", dict1.Elements[1].ToString()); Assert.Equal("'c':6", dict1.Elements[2].ToString()); Assert.Equal(3, dict1.Elements.Count); } [Fact] public void TestFile() { var p = new Parser(); var ser = File.ReadAllBytes("testserpent.utf8.bin"); var ast = p.Parse(ser); var expr = ast.ToString(); var ast2 = p.Parse(expr); var expr2 = ast2.ToString(); Assert.Equal(expr, expr2); var sb = new StringBuilder(); Walk(ast.Root, sb); var walk1 = sb.ToString(); sb = new StringBuilder(); Walk(ast2.Root, sb); var walk2 = sb.ToString(); Assert.Equal(walk1, walk2); Assert.Equal(ast.Root, ast2.Root); ast = p.Parse(expr2); Assert.Equal(ast.Root, ast2.Root); } [Fact] public void TestAstEquals() { var p = new Parser(); var ser = File.ReadAllBytes("testserpent.utf8.bin"); var ast = p.Parse(ser); var ast2 = p.Parse(ser); Assert.Equal(ast.Root, ast2.Root); } private void Walk(Ast.INode node, StringBuilder sb) { var seq = node as Ast.SequenceNode; if (seq != null) { sb.AppendLine(string.Format("{0} (seq)", node.GetType())); foreach (var child in seq.Elements) Walk(child, sb); } else { sb.AppendLine(string.Format("{0} = {1}", node.GetType(), node.ToString())); } } [Fact] public void TestTrailingCommas() { var p = new Parser(); var result = p.Parse("[1,2,3, ]").Root; result = p.Parse("[1,2,3 , ]").Root; result = p.Parse("[1,2,3,]").Root; Assert.Equal("[1,2,3]", result.ToString()); result = p.Parse("(1,2,3, )").Root; result = p.Parse("(1,2,3 , )").Root; result = p.Parse("(1,2,3,)").Root; Assert.Equal("(1,2,3)", result.ToString()); // for dict and set the asserts are a bit more complex // we cannot simply convert to string because the order of elts is undefined. result = p.Parse("{'a':1, 'b':2, 'c':3, }").Root; result = p.Parse("{'a':1, 'b':2, 'c':3 , }").Root; result = p.Parse("{'a':1, 'b':2, 'c':3,}").Root; var dict = (Ast.DictNode) result; var items = dict.ElementsAsSet(); Assert.Contains(new Ast.KeyValueNode(new Ast.StringNode("a"), new Ast.IntegerNode(1)), items); Assert.Contains(new Ast.KeyValueNode(new Ast.StringNode("b"), new Ast.IntegerNode(2)), items); Assert.Contains(new Ast.KeyValueNode(new Ast.StringNode("c"), new Ast.IntegerNode(3)), items); result = p.Parse("{1,2,3, }").Root; result = p.Parse("{1,2,3 , }").Root; result = p.Parse("{1,2,3,}").Root; var set = (Ast.SetNode) result; items = set.ElementsAsSet(); Assert.Contains(new Ast.IntegerNode(1), items); Assert.Contains(new Ast.IntegerNode(2), items); Assert.Contains(new Ast.IntegerNode(3), items); Assert.DoesNotContain(new Ast.IntegerNode(4), items); } } public class VisitorTest { [Fact] public void TestObjectify() { var p = new Parser(); var ser = File.ReadAllBytes("testserpent.utf8.bin"); var ast = p.Parse(ser); var visitor = new ObjectifyVisitor(); ast.Accept(visitor); var thing = visitor.GetObject(); var dict = (IDictionary) thing; Assert.Equal(11, dict.Count); var list = dict["numbers"] as IList; Assert.Equal(4, list.Count); Assert.Equal(999.1234, list[1]); Assert.Equal(new ComplexNumber(-3, 8), list[3]); var euro = dict["unicode"] as string; Assert.Equal("\u20ac", euro); var exc = (IDictionary) dict["exc"]; var args = (object[]) exc["args"]; Assert.Equal("fault", args[0]); Assert.Equal("ZeroDivisionError", exc["__class__"]); } private object ZerodivisionFromDict(IDictionary dict) { var classname = (string) dict["__class__"]; if (classname != "ZeroDivisionError") return null; var args = (object[]) dict["args"]; return new DivideByZeroException((string) args[0]); } [Fact] public void TestObjectifyDictToClass() { var p = new Parser(); var ser = File.ReadAllBytes("testserpent.utf8.bin"); var ast = p.Parse(ser); var visitor = new ObjectifyVisitor(ZerodivisionFromDict); ast.Accept(visitor); var thing = visitor.GetObject(); var dict = (IDictionary) thing; Assert.Equal(11, dict.Count); var ex = (DivideByZeroException) dict["exc"]; Assert.Equal("fault", ex.Message); thing = ast.GetData(ZerodivisionFromDict); dict = (IDictionary) thing; Assert.Equal(11, dict.Count); ex = (DivideByZeroException) dict["exc"]; Assert.Equal("fault", ex.Message); } } }Serpent-serpent-1.41/dotnet/Serpent/Tests/SerializeTest.cs000066400000000000000000000560471425606146200237140ustar00rootroot00000000000000using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; using Xunit; // ReSharper disable CheckNamespace // ReSharper disable InconsistentNaming // ReSharper disable MemberCanBeMadeStatic.Local namespace Razorvine.Serpent.Test { public class SerializeTest { private byte[] strip_header(byte[] data) { int start=Array.IndexOf(data, (byte)10); // the newline after the header if(start<0) throw new ArgumentException("need header in string"); start++; var result = new byte[data.Length-start]; Array.Copy(data, start, result, 0, data.Length-start); return result; } private byte[] B(string s) { return Encoding.UTF8.GetBytes(s); } private string S(byte[] b) { return Encoding.UTF8.GetString(b); } [Fact] public void TestHeader() { Serializer ser = new Serializer(); var data = ser.Serialize(null); Assert.Equal(35, data[0]); string strdata = S(data); Assert.Equal("# serpent utf-8 python3.2", strdata.Split('\n')[0]); data = B("# header\nfirst-line"); data = strip_header(data); Assert.Equal(B("first-line"), data); } [Fact] public void TestStuff() { Serializer ser=new Serializer(); var result = ser.Serialize("blerp"); result=strip_header(result); Assert.Equal(B("'blerp'"), result); result = ser.Serialize(new Guid("f1f8d00e-49a5-4662-ac1d-d5f0426ed293")); result=strip_header(result); Assert.Equal(B("'f1f8d00e-49a5-4662-ac1d-d5f0426ed293'"), result); result = ser.Serialize(123456789.987654321987654321987654321987654321m); result=strip_header(result); Assert.Equal(B("'123456789.98765432198765432199'"), result); } [Fact] public void TestNull() { Serializer ser = new Serializer(); var data = ser.Serialize(null); data=strip_header(data); Assert.Equal(B("None"),data); } [Fact] public void TestStrings() { Serializer serpent = new Serializer(); var ser = serpent.Serialize("hello"); var data = strip_header(ser); Assert.Equal(B("'hello'"), data); ser = serpent.Serialize("quotes'\""); data = strip_header(ser); Assert.Equal(B("'quotes\\'\"'"), data); ser = serpent.Serialize("quotes2'"); data = strip_header(ser); Assert.Equal(B("\"quotes2'\""), data); } [Fact] public void TestUnicodeEscapes() { Serializer serpent=new Serializer(); // regular escaped chars first var ser = serpent.Serialize("\b\r\n\f\t \\"); var data = strip_header(ser); // '\\x08\\r\\n\\x0c\\t \\\\' Assert.Equal(new byte[] {39, 92, 120, 48, 56, 92, 114, 92, 110, 92, 120, 48, 99, 92, 116, 32, 92, 92, 39}, data); // simple cases (chars < 0x80) ser = serpent.Serialize("\u0000\u0001\u001f\u007f"); data = strip_header(ser); // '\\x00\\x01\\x1f\\x7f' Assert.Equal(new byte[] {39, 92, 120, 48, 48, 92, 120, 48, 49, 92, 120, 49, 102, 92, 120, 55, 102, 39 }, data); // chars 0x80 .. 0xff ser = serpent.Serialize("\u0080\u0081\u00ff"); data = strip_header(ser); // '\\x80\\x81\xc3\xbf' (has some utf-8 encoded chars in it) Assert.Equal(new byte[] {39, 92, 120, 56, 48, 92, 120, 56, 49, 195, 191, 39}, data); // chars above 0xff ser = serpent.Serialize("\u0100\u20ac\u8899"); data = strip_header(ser); // '\xc4\x80\xe2\x82\xac\xe8\xa2\x99' (has some utf-8 encoded chars in it) Assert.Equal(new byte[] {39, 196, 128, 226, 130, 172, 232, 162, 153, 39}, data); // // some random high chars that are all printable in python and not escaped // ser = serpent.Serialize("\u0377\u082d\u10c5\u135d\uac00"); // data = strip_header(ser); // Console.WriteLine(S(data)); // XXX // // '\xcd\xb7\xe0\xa0\xad\xe1\x83\x85\xe1\x8d\x9d\xea\xb0\x80' (only a bunch of utf-8 encoded chars) // Assert.Equal(new byte[] {39, 205, 183, 224, 160, 173, 225, 131, 133, 225, 141, 157, 234, 176, 128, 39}, data); // some random high chars that are all non-printable in python and that are escaped ser = serpent.Serialize("\u0378\u082e\u10c6\u135c\uabff"); data = strip_header(ser); // '\\u0378\\u082e\\u10c6\\u135c\\uabff' Assert.Equal(new byte[] {39, 92, 117, 48, 51, 55, 56, 92, 117, 48, 56, 50, 101, 92, 117, 49, 48, 99, 54, 92, 117, 49, 51, 53, 99, 92, 117, 97, 98, 102, 102, 39}, data); } [Fact] public void TestNumbers() { Serializer serpent = new Serializer(); var ser = serpent.Serialize(12345); var data = strip_header(ser); Assert.Equal(B("12345"), data); ser = serpent.Serialize((uint)12345); data = strip_header(ser); Assert.Equal(B("12345"), data); ser = serpent.Serialize(-1234567891234567891L); data = strip_header(ser); Assert.Equal(B("-1234567891234567891"), data); ser = serpent.Serialize(12345678912345678912L); data = strip_header(ser); Assert.Equal(B("12345678912345678912"), data); ser = serpent.Serialize(99.1234); data = strip_header(ser); Assert.Equal(B("99.1234"), data); ser = serpent.Serialize(1234.9999999999m); data = strip_header(ser); Assert.Equal(B("'1234.9999999999'"), data); ser = serpent.Serialize(123456789.987654321987654321987654321987654321m); data=strip_header(ser); Assert.Equal(B("'123456789.98765432198765432199'"), data); ComplexNumber cplx = new ComplexNumber(2.2, 3.3); ser = serpent.Serialize(cplx); data = strip_header(ser); Assert.Equal(B("(2.2+3.3j)"), data); cplx = new ComplexNumber(0, 3); ser = serpent.Serialize(cplx); data = strip_header(ser); Assert.Equal(B("(0+3j)"), data); cplx = new ComplexNumber(-2, -3); ser = serpent.Serialize(cplx); data = strip_header(ser); Assert.Equal(B("(-2-3j)"), data); } [Fact] public void TestDoubleNanInf() { Serializer serpent = new Serializer(); var doubles = new object[] {double.PositiveInfinity, double.NegativeInfinity, double.NaN, float.PositiveInfinity, float.NegativeInfinity, float.NaN, new ComplexNumber(double.PositiveInfinity, 3.4)}; var ser = serpent.Serialize(doubles); var data = strip_header(ser); Assert.Equal("(1e30000,-1e30000,{'__class__':'float','value':'nan'},1e30000,-1e30000,{'__class__':'float','value':'nan'},(1e30000+3.4j))", S(data)); } [Fact] public void TestBool() { Serializer serpent = new Serializer(); var ser = serpent.Serialize(true); var data = strip_header(ser); Assert.Equal(B("True"),data); ser = serpent.Serialize(false); data = strip_header(ser); Assert.Equal(B("False"),data); } [Fact] public void TestList() { Serializer serpent = new Serializer(); IList list = new List(); // test empty list var ser = strip_header(serpent.Serialize(list)); Assert.Equal("[]", S(ser)); serpent.Indent=true; ser = strip_header(serpent.Serialize(list)); Assert.Equal("[]", S(ser)); serpent.Indent=false; // test nonempty list list.Add(42); list.Add("Sally"); list.Add(16.5); ser = strip_header(serpent.Serialize(list)); Assert.Equal("[42,'Sally',16.5]", S(ser)); serpent.Indent=true; ser = strip_header(serpent.Serialize(list)); Assert.Equal("[\n 42,\n 'Sally',\n 16.5\n]", S(ser)); } [Fact] public void TestSet() { // test with set literals Serializer serpent = new Serializer(); var set = new HashSet(); // test empty set var ser = strip_header(serpent.Serialize(set)); Assert.Equal("()", S(ser)); // empty set is serialized as a tuple. serpent.Indent=true; ser = strip_header(serpent.Serialize(set)); Assert.Equal("()", S(ser)); // empty set is serialized as a tuple. serpent.Indent=false; // test nonempty set set.Add(42); set.Add("Sally"); set.Add(16.5); ser = strip_header(serpent.Serialize(set)); Assert.Equal("{42,'Sally',16.5}", S(ser)); serpent.Indent=true; ser = strip_header(serpent.Serialize(set)); Assert.Equal("{\n 42,\n 'Sally',\n 16.5\n}", S(ser)); } [Fact] public void TestDictionary() { Serializer serpent = new Serializer(); Parser p = new Parser(); // test empty dict IDictionary ht = new Hashtable(); var ser = serpent.Serialize(ht); Assert.Equal(B("{}"), strip_header(ser)); string parsed = p.Parse(ser).Root.ToString(); Assert.Equal("{}", parsed); // empty dict with indentation serpent.Indent=true; ser = serpent.Serialize(ht); Assert.Equal(B("{}"), strip_header(ser)); parsed = p.Parse(ser).Root.ToString(); Assert.Equal("{}", parsed); // test dict with values serpent.Indent=false; ht = new Hashtable { {42, "fortytwo"}, {"sixteen-and-half", 16.5}, {"name", "Sally"}, {"status", false} }; ser = serpent.Serialize(ht); Assert.Equal((int)'}', ser[ser.Length-1]); Assert.NotEqual((int)',', ser[ser.Length-2]); parsed = p.Parse(ser).Root.ToString(); Assert.Equal(69, parsed.Length); // test indentation serpent.Indent=true; ser = serpent.Serialize(ht); Assert.Equal((int)'}', ser[ser.Length-1]); Assert.Equal((int)'\n', ser[ser.Length-2]); Assert.NotEqual((int)',', ser[ser.Length-3]); string ser_str = S(strip_header(ser)); Assert.Contains("'name': 'Sally'", ser_str); Assert.Contains("'status': False", ser_str); Assert.Contains("42: 'fortytwo'", ser_str); Assert.Contains("'sixteen-and-half': 16.5", ser_str); parsed = p.Parse(ser).Root.ToString(); Assert.Equal(69, parsed.Length); serpent.Indent=false; // generic Dictionary test IDictionary mydict = new Dictionary { { 1, "one" }, { 2, "two" } }; ser = serpent.Serialize(mydict); ser_str = S(strip_header(ser)); Assert.True(ser_str=="{2:'two',1:'one'}" || ser_str=="{1:'one',2:'two'}"); } [Fact] public void TestBytesDefault() { Serializer serpent = new Serializer(true); byte[] bytes = { 97, 98, 99, 100, 101, 102 }; // abcdef var ser = serpent.Serialize(bytes); Assert.Equal("{\n 'data': 'YWJjZGVm',\n 'encoding': 'base64'\n}", S(strip_header(ser))); Parser p = new Parser(); string parsed = p.Parse(ser).Root.ToString(); Assert.Equal(39, parsed.Length); var hashtable = new Hashtable { {"data", "YWJjZGVm"}, {"encoding", "base64"} }; var bytes2 = Parser.ToBytes(hashtable); Assert.Equal(bytes, bytes2); var dict = new Dictionary { {"data", "YWJjZGVm"}, {"encoding", "base64"} }; bytes2 = Parser.ToBytes(dict); Assert.Equal(bytes, bytes2); var dict2 = new Dictionary { {"data", "YWJjZGVm"}, {"encoding", "base64"} }; bytes2 = Parser.ToBytes(dict2); Assert.Equal(bytes, bytes2); dict["encoding"] = "base99"; Assert.Throws(()=>Parser.ToBytes(dict)); dict.Clear(); Assert.Throws(()=>Parser.ToBytes(dict)); dict.Clear(); dict["data"] = "YWJjZGVm"; Assert.Throws(()=>Parser.ToBytes(dict)); dict.Clear(); dict["encoding"] = "base64"; Assert.Throws(()=>Parser.ToBytes(dict)); Assert.Throws(()=>Parser.ToBytes(12345)); Assert.Throws(()=>Parser.ToBytes(null)); } [Fact] public void TestBytesRepr() { Serializer serpent = new Serializer(indent: true, bytesRepr:true); byte[] bytes = { 97, 98, 99, 100, 101, 102, 0, 255, (byte)'\'', (byte)'\"' }; // abcdef\x00\xff'" var ser = serpent.Serialize(bytes); Assert.Equal("b'abcdef\\x00\\xff\\'\"'", S(strip_header(ser))); Parser p = new Parser(); Ast.BytesNode parsed = (Ast.BytesNode) p.Parse(ser).Root; Assert.Equal(bytes, parsed.Value); } [Fact] public void TestCollection() { ICollection intlist = new LinkedList(); intlist.Add(42); intlist.Add(43); Serializer serpent = new Serializer(); var ser = serpent.Serialize(intlist); ser = strip_header(ser); Assert.Equal("[42,43]", S(ser)); ser=strip_header(serpent.Serialize(new [] {42})); Assert.Equal("(42,)", S(ser)); ser=strip_header(serpent.Serialize(new [] {42, 43})); Assert.Equal("(42,43)", S(ser)); serpent.Indent=true; ser = strip_header(serpent.Serialize(intlist)); Assert.Equal("[\n 42,\n 43\n]", S(ser)); ser=strip_header(serpent.Serialize(new [] {42})); Assert.Equal("(\n 42,\n)", S(ser)); ser=strip_header(serpent.Serialize(new [] {42, 43})); Assert.Equal("(\n 42,\n 43\n)", S(ser)); } [Fact] public void TestIndentation() { var dict = new Dictionary(); var list = new List { 1, 2, new [] {"a", "b"} }; dict.Add("first", list); dict.Add("second", new Dictionary { {1, false} }); dict.Add("third", new HashSet { 3, 4} ); Serializer serpent = new Serializer {Indent = true}; var ser = strip_header(serpent.Serialize(dict)); string txt=@"{ 'first': [ 1, 2, ( 'a', 'b' ) ], 'second': { 1: False }, 'third': { 3, 4 } }"; // bit of trickery to deal with Windows/Unix line ending differences txt = txt.Replace("\n","\r\n"); txt = txt.Replace("\r\r\n", "\r\n"); string ser_txt = S(ser); ser_txt = ser_txt.Replace("\n", "\r\n"); ser_txt = ser_txt.Replace("\r\r\n", "\r\n"); Assert.Equal(txt, ser_txt); } [Fact] public void TestSorting() { Serializer serpent=new Serializer(); object data = new List { 3, 2, 1}; var ser = strip_header(serpent.Serialize(data)); Assert.Equal("[3,2,1]", S(ser)); data = new [] { 3,2,1 }; ser = strip_header(serpent.Serialize(data)); Assert.Equal("(3,2,1)", S(ser)); data = new HashSet { 42, "hi" }; serpent.Indent=true; ser = strip_header(serpent.Serialize(data)); Assert.True(S(ser)=="{\n 42,\n 'hi'\n}" || S(ser)=="{\n 'hi',\n 42\n}"); data = new Dictionary { {5, "five"}, {3, "three"}, {1, "one"}, {4, "four"}, {2, "two"} }; serpent.Indent=true; ser = strip_header(serpent.Serialize(data)); Assert.Equal("{\n 1: 'one',\n 2: 'two',\n 3: 'three',\n 4: 'four',\n 5: 'five'\n}", S(ser)); data = new HashSet { "x", "y", "z", "c", "b", "a" }; serpent.Indent=true; ser = strip_header(serpent.Serialize(data)); Assert.Equal("{\n 'a',\n 'b',\n 'c',\n 'x',\n 'y',\n 'z'\n}", S(ser)); } [Fact] public void TestClass() { Serializer.RegisterClass(typeof(SerializeTestClass), null); Serializer serpent = new Serializer(true); var obj = new SerializeTestClass { i = 99, s = "hi", x = 42 }; var ser = strip_header(serpent.Serialize(obj)); Assert.Equal("{\n '__class__': 'SerializeTestClass',\n 'i': 99,\n 'obj': None,\n 's': 'hi'\n}", S(ser)); } [Fact] public void TestClass2() { Serializer.RegisterClass(typeof(SerializeTestClass), null); Serializer serpent = new Serializer(true, namespaceInClassName: true); object obj = new SerializeTestClass { i = 99, s = "hi", x = 42 }; var ser = strip_header(serpent.Serialize(obj)); Assert.Equal("{\n '__class__': 'Razorvine.Serpent.Test.SerializeTestClass',\n 'i': 99,\n 'obj': None,\n 's': 'hi'\n}", S(ser)); } private IDictionary testclassConverter(object obj) { SerializeTestClass o = (SerializeTestClass) obj; IDictionary result = new Hashtable(); result["__class@__"] = o.GetType().Name+"@"; result["i@"] = o.i; result["s@"] = o.s; result["x@"] = o.x; return result; } [Fact] public void TestCustomClassDict() { Serializer.RegisterClass(typeof(SerializeTestClass), testclassConverter); Serializer serpent = new Serializer(true); var obj = new SerializeTestClass { i = 99, s = "hi", x = 42 }; var ser = strip_header(serpent.Serialize(obj)); Assert.Equal("{\n '__class@__': 'SerializeTestClass@',\n 'i@': 99,\n 's@': 'hi',\n 'x@': 42\n}", S(ser)); } [Fact] public void TestStruct() { Serializer serpent = new Serializer(true); var obj2 = new SerializeTestStruct { i = 99, s = "hi", x = 42 }; var ser = strip_header(serpent.Serialize(obj2)); Assert.Equal("{\n '__class__': 'SerializeTestStruct',\n 'i': 99,\n 's': 'hi'\n}", S(ser)); } [Fact] public void TestStruct2() { Serializer serpent = new Serializer(true, namespaceInClassName: true); var obj2 = new SerializeTestStruct { i = 99, s = "hi", x = 42 }; var ser = strip_header(serpent.Serialize(obj2)); Assert.Equal("{\n '__class__': 'Razorvine.Serpent.Test.SerializeTestStruct',\n 'i': 99,\n 's': 'hi'\n}", S(ser)); } [Fact] public void TestAnonymousClass() { Serializer serpent = new Serializer(true); object obj = new { Name="Harry", Age=33, Country="NL" }; var ser = strip_header(serpent.Serialize(obj)); Assert.Equal("{\n 'Age': 33,\n 'Country': 'NL',\n 'Name': 'Harry'\n}", S(ser)); } [Fact] public void TestDateTime() { Serializer serpent = new Serializer(); DateTime date = new DateTime(2013, 1, 20, 23, 59, 45, 999, DateTimeKind.Local); var ser = strip_header(serpent.Serialize(date)); Assert.Equal("'2013-01-20T23:59:45.999'", S(ser)); date = new DateTime(2013, 1, 20, 23, 59, 45, 999, DateTimeKind.Utc); ser = strip_header(serpent.Serialize(date)); Assert.Equal("'2013-01-20T23:59:45.999'", S(ser)); date = new DateTime(2013, 1, 20, 23, 59, 45, 999, DateTimeKind.Unspecified); ser = strip_header(serpent.Serialize(date)); Assert.Equal("'2013-01-20T23:59:45.999'", S(ser)); date = new DateTime(2013, 1, 20, 23, 59, 45); ser = strip_header(serpent.Serialize(date)); Assert.Equal("'2013-01-20T23:59:45'", S(ser)); TimeSpan timespan = new TimeSpan(1, 10, 20, 30, 999); ser = strip_header(serpent.Serialize(timespan)); Assert.Equal("123630.999", S(ser)); } [Fact] public void TestDateTimeOffset() { Serializer serpent = new Serializer(); DateTimeOffset date = new DateTimeOffset(2013, 1, 20, 23, 59, 45, 999, TimeSpan.FromHours(+2)); var ser = strip_header(serpent.Serialize(date)); Assert.Equal("'2013-01-20T23:59:45.999+02:00'", S(ser)); date = new DateTimeOffset(2013, 5, 10, 13, 59, 45, TimeSpan.FromHours(+2)); ser = strip_header(serpent.Serialize(date)); Assert.Equal("'2013-05-10T13:59:45+02:00'", S(ser)); } [Fact] public void TestException() { Exception x = new ApplicationException("errormessage"); Serializer serpent = new Serializer(true); var ser = strip_header(serpent.Serialize(x)); Assert.Equal("{\n '__class__': 'ApplicationException',\n '__exception__': True,\n 'args': (\n 'errormessage',\n ),\n 'attributes': {}\n}", S(ser)); x.Data["custom_attribute"]=999; ser = strip_header(serpent.Serialize(x)); Assert.Equal("{\n '__class__': 'ApplicationException',\n '__exception__': True,\n 'args': (\n 'errormessage',\n ),\n 'attributes': {\n 'custom_attribute': 999\n }\n}", S(ser)); } [Fact] public void TestExceptionWithNamespace() { Exception x = new ApplicationException("errormessage"); Serializer serpent = new Serializer(true, namespaceInClassName: true); var ser = strip_header(serpent.Serialize(x)); Assert.Equal("{\n '__class__': 'System.ApplicationException',\n '__exception__': True,\n 'args': (\n 'errormessage',\n ),\n 'attributes': {}\n}", S(ser)); } private enum FooType { Jarjar } [Fact] public void TestEnum() { const FooType e = FooType.Jarjar; Serializer serpent = new Serializer(); var ser = strip_header(serpent.Serialize(e)); Assert.Equal("'Jarjar'", S(ser)); } private interface IBaseInterface {} private interface ISubInterface : IBaseInterface {} private class BaseClassWithInterface : IBaseInterface {} private class SubClassWithInterface : BaseClassWithInterface, ISubInterface {} private abstract class AbstractBaseClass {} private class ConcreteSubClass : AbstractBaseClass {} private IDictionary AnyClassSerializer(object arg) { IDictionary result = new Hashtable(); result["(SUB)CLASS"] = arg.GetType().Name; return result; } [Fact] public void testAbstractBaseClassHierarchyPickler() { ConcreteSubClass c = new ConcreteSubClass(); Serializer serpent = new Serializer(); serpent.Serialize(c); Serializer.RegisterClass(typeof(AbstractBaseClass), AnyClassSerializer); var data = serpent.Serialize(c); Assert.Equal("{'(SUB)CLASS':'ConcreteSubClass'}", S(strip_header(data))); } [Fact] public void TestInterfaceHierarchyPickler() { BaseClassWithInterface b = new BaseClassWithInterface(); SubClassWithInterface sub = new SubClassWithInterface(); Serializer serpent = new Serializer(); serpent.Serialize(b); serpent.Serialize(sub); Serializer.RegisterClass(typeof(IBaseInterface), AnyClassSerializer); var data = serpent.Serialize(b); Assert.Equal("{'(SUB)CLASS':'BaseClassWithInterface'}", S(strip_header(data))); data = serpent.Serialize(sub); Assert.Equal("{'(SUB)CLASS':'SubClassWithInterface'}", S(strip_header(data))); } } [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] public class SerializeTestClass { public int x; public string s {get; set;} public int i {get; set;} public object obj {get; set;} protected bool Equals(SerializeTestClass other) { return x == other.x && string.Equals(s, other.s) && i == other.i && Equals(obj, other.obj); } public override int GetHashCode() { unchecked { int hashCode = x; hashCode = (hashCode * 397) ^ (s != null ? s.GetHashCode() : 0); hashCode = (hashCode * 397) ^ i; hashCode = (hashCode * 397) ^ (obj != null ? obj.GetHashCode() : 0); return hashCode; } } } [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] public struct SerializeTestStruct { // ReSharper disable once NotAccessedField.Global public int x; public string s {get; set;} public int i {get; set;} public bool Equals(SerializeTestStruct other) { return x == other.x && string.Equals(s, other.s) && i == other.i; } public override int GetHashCode() { unchecked { int hashCode = x; hashCode = (hashCode * 397) ^ (s != null ? s.GetHashCode() : 0); hashCode = (hashCode * 397) ^ i; return hashCode; } } } } Serpent-serpent-1.41/dotnet/Serpent/Tests/SlowPerformanceTest.cs000066400000000000000000000030531425606146200250600ustar00rootroot00000000000000using System; using Xunit; // ReSharper disable CheckNamespace namespace Razorvine.Serpent.Test { public class SlowPerformanceTest { // tests some performance regressions when they occur [Fact(Skip="number parse performance in long lists has been resolved")] public void TestManyFloats() { const int amount = 200000; var array = new double[amount]; for(int i=0; i(()=>s2.Peek()); } } [Fact] public void TestRead() { SeekableStringReader s = new SeekableStringReader("hello"); Assert.Equal('h', s.Read()); Assert.Equal('e', s.Read()); Assert.Equal("l", s.Read(1)); Assert.Equal("lo", s.Read(2)); Assert.Throws(()=>s.Read()); } [Fact] public void TestRanges() { SeekableStringReader s = new SeekableStringReader("hello"); Assert.Throws(()=>s.Read(-1)); Assert.Equal("hello", s.Read(999)); Assert.Throws(()=>s.Read(1)); s.Rewind(int.MaxValue); Assert.True(s.HasMore()); Assert.Equal("hello", s.Peek(999)); Assert.True(s.HasMore()); } [Fact] public void TestReadUntil() { SeekableStringReader s = new SeekableStringReader("hello there"); s.Read(); Assert.Equal("ello", s.ReadUntil(' ')); Assert.Equal('t', s.Peek()); Assert.Throws(()=>s.ReadUntil('x')); Assert.Equal("there", s.Rest()); Assert.Throws(()=>s.Rest()); s.Rewind(int.MaxValue); Assert.Equal("hell", s.ReadUntil('x', 'y', 'z', ' ', 'o')); Assert.Throws(()=>s.ReadUntil('x', 'y', '@')); } [Fact] public void TestReadWhile() { SeekableStringReader s = new SeekableStringReader("123.456 foo"); Assert.Equal("123.456", s.ReadWhile("0123456789.")); Assert.Equal("", s.ReadWhile("@")); Assert.Equal(" ", s.ReadWhile(" ")); Assert.Equal("foo", s.Rest()); } [Fact] public void TestBookmark() { SeekableStringReader s = new SeekableStringReader("hello"); s.Read(2); int bookmark = s.Bookmark(); Assert.Equal("ll", s.Read(2)); s.FlipBack(bookmark); Assert.Equal("ll", s.Read(2)); Assert.Equal("o", s.Read(999)); } [Fact] public void TestNesting() { SeekableStringReader outer = new SeekableStringReader("hello!"); outer.Read(1); SeekableStringReader inner1 = new SeekableStringReader(outer); SeekableStringReader inner2 = new SeekableStringReader(outer); Assert.Equal("ell", inner1.Read(3)); Assert.Equal("el", inner2.Read(2)); Assert.Equal("o", inner1.Read(1)); Assert.Equal("l", inner2.Read(1)); Assert.Equal("e", outer.Read(1)); Assert.Equal("o", inner2.Read(1)); Assert.Equal("l", outer.Read(1)); outer.Sync(inner2); Assert.Equal("!", outer.Read(1)); } [Fact] public void TestContext() { SeekableStringReader s = new SeekableStringReader("abcdefghijklmnopqrstuvwxyz"); s.Read(10); string left, right; s.Context(-1, 5, out left, out right); Assert.Equal("fghij", left); Assert.Equal("klmno", right); s.Context(-1, 12, out left, out right); Assert.Equal("abcdefghij", left); Assert.Equal("klmnopqrstuv", right); s.Read(13); s.Context(-1, 6, out left, out right); Assert.Equal("rstuvw", left); Assert.Equal("xyz", right); s.Context(5,4, out left, out right); Assert.Equal("bcde", left); Assert.Equal("fghi", right); } } }Serpent-serpent-1.41/dotnet/Serpent/Tests/Tests.csproj000066400000000000000000000016021425606146200231050ustar00rootroot00000000000000 net6.0 false 6 UnitTests UnitTests Library PreserveNewest Serpent-serpent-1.41/dotnet/Serpent/Tests/testserpent.utf8.bin000066400000000000000000000022361425606146200245240ustar00rootroot00000000000000# serpent utf-8 python3.3 { # serpent supports comments in the serialized data 'bytes': { 'data': 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', 'encoding': 'base64' }, 'dates': [ # test some dates. '2013-01-26T03:32:39.007001', '23:59:45.999888', 500.0 ], 'dict': { 0: '0000', 1: '1111', 2: '2222', 3: '3333', 4: '4444', 5: '5555', 6: '6666', 7: '7777', 8: '8888', 9: '9999' }, 'exc': { '__class__': 'ZeroDivisionError', '__exception__': True, # this is an exception 'args': ( 'fault', ), 'attributes': {} }, 'list': [ 1, 2, 3, 4, 5, 6, 7, 8 ], 'numbers': [ 12345678901234567890123456, 999.1234, '1.99999999999999999991', (-3+8j) ], 'set': { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 'str': 'hello', 'tuple': ( 1, 2, 3, 4, 5, 6, 7, 8 ), 'unicode': '€', # the feared unicode! 'uuid': '57e12d82-511f-456b-ab65-f765144c6a90' }Serpent-serpent-1.41/dotnet/nuget-pack.sh000077500000000000000000000006271425606146200204420ustar00rootroot00000000000000#!/bin/sh set -e echo "Building and testing..." dotnet test -c Release Serpent/Tests echo "\n\nCreating nuget release package..." dotnet pack -c Release -o $(pwd)/dist Serpent echo "\n\nPackage available in dist/ directory:" ls -l dist echo "\nIf this is allright, publish to nuget.org with:" echo "dotnet nuget push dist/Razorvine.Serpent.xxxxx.nupkg -s https://www.nuget.org -k api_key_from_nuget_org" Serpent-serpent-1.41/dotnet/test.sh000077500000000000000000000000711425606146200173540ustar00rootroot00000000000000#!/bin/sh echo "Running tests" dotnet test Serpent/Tests Serpent-serpent-1.41/java/000077500000000000000000000000001425606146200154645ustar00rootroot00000000000000Serpent-serpent-1.41/java/.gitignore000066400000000000000000000001051425606146200174500ustar00rootroot00000000000000# intellij workspace info .idea/workspace.xml # java/maven target/ Serpent-serpent-1.41/java/.idea/000077500000000000000000000000001425606146200164445ustar00rootroot00000000000000Serpent-serpent-1.41/java/.idea/.name000066400000000000000000000000071425606146200173620ustar00rootroot00000000000000SerpentSerpent-serpent-1.41/java/.idea/Serpent.iml000066400000000000000000000020071425606146200205660ustar00rootroot00000000000000 Serpent-serpent-1.41/java/.idea/compiler.xml000066400000000000000000000015031425606146200207770ustar00rootroot00000000000000 Serpent-serpent-1.41/java/.idea/encodings.xml000066400000000000000000000003621425606146200211400ustar00rootroot00000000000000 Serpent-serpent-1.41/java/.idea/libraries/000077500000000000000000000000001425606146200204205ustar00rootroot00000000000000Serpent-serpent-1.41/java/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml000066400000000000000000000010101425606146200304340ustar00rootroot00000000000000 Serpent-serpent-1.41/java/.idea/misc.xml000066400000000000000000000006141425606146200201220ustar00rootroot00000000000000 Serpent-serpent-1.41/java/.idea/modules.xml000066400000000000000000000004121425606146200206330ustar00rootroot00000000000000 Serpent-serpent-1.41/java/.idea/vcs.xml000066400000000000000000000002671425606146200177660ustar00rootroot00000000000000 Serpent-serpent-1.41/java/mvn-release-readme.txt000066400000000000000000000006171425606146200217020ustar00rootroot00000000000000Making a release to Sonatype Nexus/maven central: $ mvn release:clean release:prepare release:perform Requires version number in the pom.xml to be "x.y-SNAPSHOT". Finalise and publish the release via https://oss.sonatype.org/#stagingRepositories See also: http://java.dzone.com/articles/deploy-maven-central http://central.sonatype.org/pages/apache-maven.html#performing-a-release-deployment Serpent-serpent-1.41/java/pom.xml000066400000000000000000000056611425606146200170110ustar00rootroot00000000000000 4.0.0 org.sonatype.oss oss-parent 9 net.razorvine serpent 1.41-SNAPSHOT jar serpent https://github.com/irmen/Serpent UTF-8 org.apache.maven.plugins maven-compiler-plugin 3.8.0 -Xlint:all 1.8 1.8 org.apache.maven.plugins maven-gpg-plugin 1.5 org.apache.maven.plugins maven-release-plugin 2.5.2 org.apache.maven.plugins maven-javadoc-plugin none junit junit 4.13.1 test Serpent serializes an object tree into a Python ast.literal_eval() compatible literal expression. It is safe to send serpent data to other machines over the network for instance (because only 'safe' literals are encoded). There is also a deserializer or parse provided that turns such a literal expression back into the appropriate Java object tree. It is an alternative to JSON to provide easy data integration between Java and Python. Serpent is more expressive as JSON (it supports more data types). https://github.com/irmen/Serpent scm:git:https://github.com/irmen/Serpent.git scm:git:https://github.com/irmen/Serpent.git HEAD Github https://github.com/irmen/Serpent/issues irmen Irmen de Jong irmen@razorvine.net https://github.com/irmen MIT License http://www.opensource.org/licenses/mit-license.php Serpent-serpent-1.41/java/serpent.iml000066400000000000000000000016571425606146200176600ustar00rootroot00000000000000 Serpent-serpent-1.41/java/src/000077500000000000000000000000001425606146200162535ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/main/000077500000000000000000000000001425606146200171775ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/main/java/000077500000000000000000000000001425606146200201205ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/main/java/net/000077500000000000000000000000001425606146200207065ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/main/java/net/razorvine/000077500000000000000000000000001425606146200227255ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/000077500000000000000000000000001425606146200244055ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ComplexNumber.java000066400000000000000000000040111425606146200300240ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent; import java.io.Serializable; /** * A complex number. */ public class ComplexNumber implements Serializable { private static final long serialVersionUID = 5396759273405612137L; public double real; public double imaginary; public ComplexNumber(double r, double i) { real=r; imaginary=i; } @Override public String toString() { StringBuilder sb=new StringBuilder(); sb.append(real); if(imaginary>0) sb.append('+'); return sb.append(imaginary).append('i').toString(); } public double Magnitude() { return Math.sqrt(real * real + imaginary * imaginary); } public void add(ComplexNumber other) { real += other.real; imaginary += other.imaginary; } public void subtract(ComplexNumber other) { real -= other.real; imaginary -= other.imaginary; } public void multiply(ComplexNumber other) { double new_real = real * other.real - imaginary * other.imaginary; double new_imaginary = real * other.imaginary + imaginary * other.real; real = new_real; imaginary = new_imaginary; } public void divide(ComplexNumber other) { double new_real = (real * other.real + imaginary * other.imaginary) / (other.real * other.real + other.imaginary * other.imaginary); double new_imaginary = (imaginary * other.real - real * other.imaginary) / (other.real * other.real + other.imaginary * other.imaginary); real = new_real; imaginary = new_imaginary; } @Override public boolean equals(Object obj) { if(!(obj instanceof ComplexNumber)) { return false; } ComplexNumber other = (ComplexNumber) obj; return real==other.real && imaginary==other.imaginary; } @Override public int hashCode() { Double r = Double.valueOf(real); Double i = Double.valueOf(imaginary); return r.hashCode() ^ i.hashCode(); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/DebugVisitor.java000066400000000000000000000053561425606146200276670ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent; import net.razorvine.serpent.ast.*; import java.util.Arrays; /** * Ast nodevisitor that prints out the Ast as a string for debugging purposes */ public class DebugVisitor implements INodeVisitor { private StringBuilder result = new StringBuilder(); private int indentlevel=0; public DebugVisitor() { } /** * Get the debug string representation result. */ @Override public String toString() { return result.toString(); } protected void indent() { for(int i=0; i convert(Object obj); } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/IDictToInstance.java000066400000000000000000000011621425606146200302340ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent; import java.io.IOException; import java.util.Map; /** * Customization interface for turning dicts back into specific objects. */ public interface IDictToInstance { /** * Convert the given dictionary to a specific object. * Can return null to use the default behavior. */ Object convert(Map dict) throws IOException; } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/LibraryVersion.java000066400000000000000000000005561425606146200302300ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent; public final class LibraryVersion { public static final String VERSION = "1.40"; } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ObjectifyVisitor.java000066400000000000000000000060341425606146200305510ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import net.razorvine.serpent.ast.*; /** * Ast nodevisitor that turns the AST into actual Java objects (array, int, IDictionary, string, etc...) */ public class ObjectifyVisitor implements INodeVisitor { Stack generated = new Stack(); protected IDictToInstance dictConverter = null; public ObjectifyVisitor() { } public ObjectifyVisitor(IDictToInstance dictConverter) { this.dictConverter = dictConverter; } /** * get the resulting object tree. */ public Object getObject() { return generated.pop(); } public void visit(ComplexNumberNode complex) { generated.push(new ComplexNumber(complex.real, complex.imaginary)); } public void visit(DictNode dict) { Map obj = new HashMap(dict.elements.size()); for(INode e: dict.elements) { KeyValueNode kv = (KeyValueNode)e; kv.key.accept(this); Object key = generated.pop(); kv.value.accept(this); Object value = generated.pop(); obj.put(key, value); } if(dictConverter==null || !obj.containsKey("__class__")) { generated.push(obj); } else { Object result; try { result = dictConverter.convert(obj); } catch (IOException e) { throw new RuntimeException("problem converting dict to class", e); } if(result==null) generated.push(obj); else generated.push(result); } } public void visit(ListNode list) { List obj = new ArrayList(list.elements.size()); for(INode node: list.elements) { node.accept(this); obj.add(generated.pop()); } generated.push(obj); } public void visit(NoneNode none) { generated.push(null); } public void visit(IntegerNode value) { generated.push(value.value); } public void visit(LongNode value) { generated.push(value.value); } public void visit(DoubleNode value) { generated.push(value.value); } public void visit(BooleanNode value) { generated.push(value.value); } public void visit(StringNode value) { generated.push(value.value); } public void visit(BytesNode value) { generated.push(value.value); } public void visit(BigIntNode value) { generated.push(value.value); } public void visit(SetNode setnode) { Set obj = new HashSet(); for(INode node: setnode.elements) { node.accept(this); obj.add(generated.pop()); } generated.push(obj); } public void visit(TupleNode tuple) { Object[] array = new Object[tuple.elements.size()]; int index=0; for(INode node: tuple.elements) { node.accept(this); array[index++] = generated.pop(); } generated.push(array); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ParseException.java000066400000000000000000000013221425606146200301770ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent; /** * Exception that is used when a serpent parsing error occurs. */ public class ParseException extends RuntimeException { private static final long serialVersionUID = -667969719712045595L; public ParseException() { } public ParseException(String message) { super(message); } public ParseException(Throwable cause) { super(cause); } public ParseException(String message, Throwable cause) { super(message, cause); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/Parser.java000066400000000000000000000420761425606146200265150ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import net.razorvine.serpent.ast.*; /** * Parse a Python literal into an Ast (abstract syntax tree). */ public class Parser { /** * Parse from a byte array (containing utf-8 encoded string with the Python literal expression in it) */ public Ast parse(byte[] serialized) throws ParseException { try { return parse(new String(serialized, "utf-8")); } catch (UnsupportedEncodingException e) { throw new ParseException(e.toString()); } } /** * Parse from a string with the Python literal expression */ public Ast parse(String expression) { Ast ast=new Ast(); if(expression==null || expression.length()==0) return ast; SeekableStringReader sr = new SeekableStringReader(expression); if(sr.peek()=='#') sr.readUntil('\n'); // skip comment line try { ast.root = parseExpr(sr); sr.skipWhitespace(); if(sr.hasMore()) throw new ParseException("garbage at end of expression"); return ast; } catch (ParseException x) { String faultLocation = extractFaultLocation(sr); throw new ParseException(x.getMessage() + " (at position "+sr.bookmark()+"; '"+faultLocation+"')", x); } } String extractFaultLocation(SeekableStringReader sr) { SeekableStringReader.StringContext ctx = sr.context(-1, 20); return String.format("...%s>>><<<%s...", ctx.left, ctx.right); } INode parseExpr(SeekableStringReader sr) { // expr = [ ] single | compound [ ] . sr.skipWhitespace(); if(!sr.hasMore()) throw new ParseException("unexpected end of line, missing expression or close/open character"); char c = sr.peek(); INode node; if(c=='{' || c=='[' || c=='(') node = parseCompound(sr); else node = parseSingle(sr); sr.skipWhitespace(); return node; } INode parseCompound(SeekableStringReader sr) { // compound = tuple | dict | list | set . sr.skipWhitespace(); switch(sr.peek()) { case '[': return parseList(sr); case '{': { int bm = sr.bookmark(); try { return parseSet(sr); } catch(ParseException x) { sr.flipBack(bm); return parseDict(sr); } } case '(': // tricky case here, it can be a tuple but also a complex number: // if the last character before the closing parenthesis is a 'j', it is a complex number { int bm = sr.bookmark(); String betweenparens = sr.readUntil(")\n").trim(); sr.flipBack(bm); return betweenparens.endsWith("j") ? parseComplex(sr) : parseTuple(sr); } default: throw new ParseException("invalid sequencetype char"); } } TupleNode parseTuple(SeekableStringReader sr) { //tuple = tuple_empty | tuple_one | tuple_more //tuple_empty = '()' . //tuple_one = '(' expr ',' ')' . //tuple_more = '(' expr_list trailing_comma ')' . // trailing_comma = '' | ',' . sr.read(); // ( sr.skipWhitespace(); TupleNode tuple = new TupleNode(); if(sr.peek() == ')') { sr.read(); return tuple; // empty tuple } INode firstelement = parseExpr(sr); if(sr.peek() == ',') { sr.read(); sr.skipWhitespace(); if(sr.read() == ')') { // tuple with just a single element tuple.elements.add(firstelement); return tuple; } sr.rewind(1); // undo the thing that wasn't a ) } tuple.elements = parseExprList(sr); tuple.elements.add(0, firstelement); // handle trailing comma if present sr.skipWhitespace(); if(!sr.hasMore()) throw new ParseException("missing ')'"); if(sr.peek() == ',') sr.read(); if(!sr.hasMore()) throw new ParseException("missing ')'"); char closechar = sr.read(); if(closechar==',') closechar = sr.read(); if(closechar!=')') throw new ParseException("expected ')'"); return tuple; } List parseExprList(SeekableStringReader sr) { //expr_list = expr { ',' expr } . List exprList = new ArrayList(); exprList.add(parseExpr(sr)); while(sr.hasMore() && sr.peek() == ',') { sr.read(); try { exprList.add(parseExpr(sr)); } catch (ParseException x) { sr.rewind(1); break; } } return exprList; } List parseKeyValueList(SeekableStringReader sr) { //keyvalue_list = keyvalue { ',' keyvalue } . List kvs = new ArrayList(); kvs.add(parseKeyValue(sr)); while(sr.hasMore() && sr.peek()==',') { sr.read(); try { kvs.add(parseKeyValue(sr)); } catch (ParseException x) { sr.rewind(1); break; } } return kvs; } KeyValueNode parseKeyValue(SeekableStringReader sr) { //keyvalue = expr ':' expr . INode key = parseExpr(sr); if(sr.hasMore() && sr.peek()==':') { sr.read(); // : INode value = parseExpr(sr); KeyValueNode kv = new KeyValueNode(); kv.key = key; kv.value = value; return kv; } throw new ParseException("expected ':'"); } SetNode parseSet(SeekableStringReader sr) { // set = '{' expr_list trailing_comma '}' . // trailing_comma = '' | ',' . sr.read(); // { sr.skipWhitespace(); SetNode setnode = new SetNode(); List elts = parseExprList(sr); // handle trailing comma if present sr.skipWhitespace(); if(!sr.hasMore()) throw new ParseException("missing '}'"); if(sr.peek() == ',') sr.read(); if(!sr.hasMore()) throw new ParseException("missing '}'"); char closechar = sr.read(); if(closechar!='}') throw new ParseException("expected '}'"); // make sure it has set semantics (remove duplicate elements) Set h = new HashSet(elts); setnode.elements = new ArrayList(h); return setnode; } ListNode parseList(SeekableStringReader sr) { // list = list_empty | list_nonempty . // list_empty = '[]' . // list_nonempty = '[' expr_list trailing_comma ']' . // trailing_comma = '' | ',' . sr.read(); // [ sr.skipWhitespace(); ListNode list = new ListNode(); if(sr.peek() == ']') { sr.read(); return list; // empty list } list.elements = parseExprList(sr); // handle trailing comma if present sr.skipWhitespace(); if(!sr.hasMore()) throw new ParseException("missing ']'"); if(sr.peek() == ',') sr.read(); if(!sr.hasMore()) throw new ParseException("missing ']'"); char closechar = sr.read(); if(closechar!=']') throw new ParseException("expected ']'"); return list; } INode parseDict(SeekableStringReader sr) { //dict = '{' keyvalue_list trailing_comma '}' . //keyvalue_list = keyvalue { ',' keyvalue } . //keyvalue = expr ':' expr . // trailing_comma = '' | ',' . sr.read(); // { sr.skipWhitespace(); DictNode dict = new DictNode(); if(sr.peek() == '}') { sr.read(); return dict; // empty dict } List elts = parseKeyValueList(sr); // handle trailing comma if present sr.skipWhitespace(); if(!sr.hasMore()) throw new ParseException("missing '}'"); if(sr.peek() == ',') sr.read(); if(!sr.hasMore()) throw new ParseException("missing '}'"); char closechar = sr.read(); if(closechar!='}') throw new ParseException("expected '}'"); // make sure it has dict semantics (remove duplicate keys) Map fixedDict = new HashMap(elts.size()); for(INode e: elts) { KeyValueNode kv = (KeyValueNode)e; fixedDict.put(kv.key, kv.value); } for(Map.Entry e: fixedDict.entrySet()) { KeyValueNode kvnode = new KeyValueNode(); kvnode.key = e.getKey(); kvnode.value = e.getValue(); dict.elements.add(kvnode); } // SPECIAL CASE: {'__class__':'float','value':'nan'} ---> Double.NaN if(dict.elements.size()==2) { if(dict.elements.contains(new KeyValueNode(new StringNode("__class__"), new StringNode("float")))) { if(dict.elements.contains(new KeyValueNode(new StringNode("value"), new StringNode("nan")))) { return new DoubleNode(Double.NaN); } } } return dict; } public INode parseSingle(SeekableStringReader sr) { // single = int | float | complex | string | bool | none . sr.skipWhitespace(); switch(sr.peek()) { case 'N': return parseNone(sr); case 'T': case 'F': return parseBool(sr); case '\'': case '"': return parseString(sr); case 'b': return parseBytes(sr); } // int or float or complex. int bookmark = sr.bookmark(); try { return parseComplex(sr); } catch (ParseException x1) { sr.flipBack(bookmark); try { return parseFloat(sr); } catch (ParseException x2) { sr.flipBack(bookmark); return parseInt(sr); } } } final String FloatCharacters = "-+.eE0123456789"; final String IntCharacters = "-0123456789"; INode parseInt(SeekableStringReader sr) { // int = ['-'] digitnonzero {digit} . String numberstr = sr.readWhile(IntCharacters); if(numberstr.length()==0) throw new ParseException("invalid int character"); try { try { return new IntegerNode(Integer.parseInt(numberstr)); } catch (NumberFormatException x1) { // try long try { return new LongNode(Long.parseLong(numberstr)); } catch (NumberFormatException x2) { // try bigint, but it can still overflow because it's not arbitrary precision try { return new BigIntNode(new BigInteger(numberstr)); } catch (NumberFormatException x3) { throw new ParseException("number too large or invalid"); } } } } catch (NumberFormatException x) { throw new ParseException("invalid integer format", x); } } PrimitiveNode parseFloat(SeekableStringReader sr) { String numberstr = sr.readWhile(FloatCharacters); if(numberstr.length()==0) throw new ParseException("invalid float character"); // little bit of a hack: // if the number doesn't contain a decimal point and no 'e'/'E', it is an integer instead. // in that case, we need to reject it as a float. if(numberstr.indexOf('.')<0 && numberstr.indexOf('e')<0 && numberstr.indexOf('E')<0) throw new ParseException("number is not a float (might be an integer though)"); try { return new DoubleNode(Double.parseDouble(numberstr)); } catch (NumberFormatException x) { throw new ParseException("invalid float format", x); } } ComplexNumberNode parseComplex(SeekableStringReader sr) { //complex = complextuple | imaginary . //imaginary = ['+' | '-' ] ( float | int ) 'j' . //complextuple = '(' ( float | int ) imaginary ')' . if(sr.peek()=='(') { // complextuple sr.read(); // ( String numberstr; if(sr.peek()=='-' || sr.peek()=='+') { // starts with a sign, read that first otherwise the readuntil will return immediately numberstr = sr.read(1) + sr.readUntil("+-"); } else { numberstr = sr.readUntil("+-"); } sr.rewind(1); // rewind the +/- // because we're a bit more cautious here with reading chars than in the float parser, // it can be that the parser now stopped directly after the 'e' in a number like "3.14e+20". // ("3.14e20" is fine) So, check if the last char is 'e' and if so, continue reading 0..9. if(numberstr.endsWith("e")||numberstr.endsWith("E")) { // if the next symbol is + or -, accept it, then read the exponent integer if(sr.peek()=='-' || sr.peek()=='+') numberstr+=sr.read(1); numberstr += sr.readWhile("0123456789"); } sr.skipWhitespace(); double real; try { real = Double.parseDouble(numberstr); } catch (NumberFormatException x) { throw new ParseException("invalid float format", x); } double imaginarypart = parseImaginaryPart(sr); if(sr.read()!=')') throw new ParseException("expected ) to end a complex number"); ComplexNumberNode c = new ComplexNumberNode(); c.real = real; c.imaginary = imaginarypart; return c; } else { // imaginary double imag = parseImaginaryPart(sr); ComplexNumberNode c = new ComplexNumberNode(); c.real = 0; c.imaginary = imag; return c; } } double parseImaginaryPart(SeekableStringReader sr) { //imaginary = ['+' | '-' ] ( float | int ) 'j' . if(!sr.hasMore()) throw new ParseException("unexpected end of input string"); char sign_or_digit = sr.peek(); if(sign_or_digit=='+') sr.read(); // skip the '+' // now an int or float follows. double double_value; int bookmark = sr.bookmark(); try { PrimitiveNode float_part = parseFloat(sr); double_value = float_part.value; } catch (ParseException x1) { sr.flipBack(bookmark); INode integer_part = parseInt(sr); if(integer_part instanceof IntegerNode) { double_value = ((IntegerNode)integer_part).value; } else if(integer_part instanceof LongNode) { double_value = ((LongNode)integer_part).value; } else if(integer_part instanceof BigIntNode) { double_value = ((BigIntNode)integer_part).value.doubleValue(); } else { throw new ParseException("not an integer for the imaginary part"); } } // now a 'j' must follow! sr.skipWhitespace(); try { char j_char = sr.read(); if(j_char!='j') throw new ParseException("not an imaginary part"); } catch (IndexOutOfBoundsException x) { throw new ParseException("not an imaginary part"); } return double_value; } PrimitiveNode parseString(SeekableStringReader sr) { char quotechar = sr.read(); // ' or " StringBuilder sb = new StringBuilder(10); while(sr.hasMore()) { char c = sr.read(); if(c=='\\') { // backslash unescape c = sr.read(); switch(c) { case '\\': sb.append('\\'); break; case '\'': sb.append('\''); break; case '"': sb.append('"'); break; case 'b': sb.append('\b'); break; case 'f': sb.append('\f'); break; case 'n': sb.append('\n'); break; case 'r': sb.append('\r'); break; case 't': sb.append('\t'); break; case 'x': // "\x00" sb.append((char)Integer.parseInt(sr.read(2), 16)); break; case 'u': // "\u0000" sb.append((char)Integer.parseInt(sr.read(4), 16)); break; default: sb.append(c); break; } } else if(c==quotechar) { // end of string return new StringNode(sb.toString()); } else { sb.append(c); } } throw new ParseException("unclosed string"); } PrimitiveNode> parseBytes(SeekableStringReader sr) { sr.read(); // skip the 'b' char quotechar = sr.read(); // ' or " List bytes = new ArrayList(); while(sr.hasMore()) { char c = sr.read(); if(c=='\\') { // backslash unescape c = sr.read(); switch(c) { case '\\': bytes.add((byte) '\\'); break; case '\'': bytes.add((byte)'\''); break; case '"': bytes.add((byte)'"'); break; case 'b': bytes.add((byte)'\b'); break; case 'f': bytes.add((byte)'\f'); break; case 'n': bytes.add((byte)'\n'); break; case 'r': bytes.add((byte)'\r'); break; case 't': bytes.add((byte)'\t'); break; case 'x': // "\x00" bytes.add((byte) Integer.parseInt(sr.read(2), 16)); break; default: bytes.add((byte)c); break; } } else if(c==quotechar) { // end of bytes return new BytesNode(bytes); } else { bytes.add((byte)c); } } throw new ParseException("unclosed bytes"); } PrimitiveNode parseBool(SeekableStringReader sr) { // True,False String b = sr.readUntil('e'); if(b.equals("Tru")) return new BooleanNode(true); if(b.equals("Fals")) return new BooleanNode(false); throw new ParseException("expected bool, True or False"); } NoneNode parseNone(SeekableStringReader sr) { // None String n = sr.readUntil('e'); if(n.equals("Non")) return NoneNode.Instance; throw new ParseException("expected None"); } /** * Utility function to convert obj back to actual bytes if it is a serpent-encoded bytes dictionary * (a IDictionary with base-64 encoded 'data' in it and 'encoding'='base64'). * If obj is already a byte array, return obj unmodified. * If it is something else, throw an IllegalArgumentException */ public static byte[] toBytes(Object obj) { if(obj instanceof Map) { @SuppressWarnings("unchecked") Map dict = (Map)obj; String data = dict.get("data"); String encoding = dict.get("encoding"); if(data==null || encoding==null || !encoding.equals("base64")) { throw new IllegalArgumentException("argument is neither bytearray nor serpent base64 encoded bytes dict"); } return Base64.getDecoder().decode(data); } if(obj instanceof byte[]) { return (byte[]) obj; } throw new IllegalArgumentException("argument is neither bytearray nor serpent base64 encoded bytes dict"); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/SeekableStringReader.java000066400000000000000000000113231425606146200312750ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent; /** * A special string reader that is suitable for the parser to read through * the expression string. You can rewind it, set bookmarks to flip back to, etc. */ public class SeekableStringReader { private String str; private int cursor = 0; private int bookmark = -1; public SeekableStringReader(String str) { if(str==null) throw new IllegalArgumentException("str may not be null"); this.str = str; } /** * Make a nested reader with its own cursor and bookmark. * The cursor starts at the same position as the parent. */ public SeekableStringReader(SeekableStringReader parent) { str = parent.str; cursor = parent.cursor; } /** * Is tehre more to read? */ public boolean hasMore() { return cursor0) throw new ParseException("no more data"); String result = str.substring(cursor, cursor+safecount); cursor += safecount; return result; } /** * Read everything until one the sentinel, which must exist in the string. * Sentinel char is read but not returned in the result. */ public String readUntil(char sentinel) { int i = str.indexOf(sentinel, cursor); if(i>=0) { int from = cursor; cursor = i+1; return str.substring(from, i); } throw new ParseException("terminator not found"); } /** * Read everything until one of the sentinel(s), which must exist in the string. * Sentinel char is read but not returned in the result. */ public String readUntil(String sentinels) { int index=Integer.MAX_VALUE; for(char s: sentinels.toCharArray()) { int i = str.indexOf(s, cursor); if(i>=0) index = Math.min(i, index); } if(index>=0 && index=0) ++cursor; else break; } return str.substring(start, cursor); } /** * Read away any whitespace. * If a comment follows ('# bla bla') read away that as well */ public void skipWhitespace() { while(hasMore()) { char c=read(); if(c=='#') { readUntil('\n'); return; } if(!Character.isWhitespace(c)) { rewind(1); return; } } } /** * Returns the rest of the data until the end. */ public String rest() { if(cursor>=str.length()) throw new ParseException("no more data"); String result=str.substring(cursor); cursor = str.length(); return result; } /** * Rewind a number of characters. */ public void rewind(int count) { cursor = Math.max(0, cursor-count); } /** * Return a bookmark to rewind to later. */ public int bookmark() { return cursor; } /** * Flip back to previously set bookmark. */ public void flipBack(int bookmark) { cursor = bookmark; } /** * Sync the position and bookmark with the current position in another reader. */ public void sync(SeekableStringReader inner) { bookmark = inner.bookmark; cursor = inner.cursor; } /** * Extract a piece of context around the current cursor (if you set cursor to -1) * or around a given position in the string (if you set cursor greater or equal to 0). */ public class StringContext { public String left; public String right; } public StringContext context(int crsr, int width) { if(crsr<0) crsr=this.cursor; int leftStrt = Math.max(0, crsr-width); int leftLen = crsr-leftStrt; int rightLen = Math.min(width, str.length()-crsr); StringContext result = new StringContext(); result.left = str.substring(leftStrt, leftStrt+leftLen); result.right = str.substring(crsr, crsr+rightLen); return result; } public void close() { this.str = null; } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/Serializer.java000066400000000000000000000470601425606146200273700ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent; import java.io.IOException; import java.io.Serializable; import java.io.StringWriter; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.Map.Entry; /** * Serialize an object tree to a byte stream. * It is not thread-safe: make sure you're not making changes to the object tree that is being serialized. */ public class Serializer { /** * The maximum nesting level of the object graphs that you want to serialize. * This limit has been set to avoid troublesome stack overflow errors. * (If it is reached, an IllegalArgumentException is thrown instead with a clear message) */ public int maximumLevel = 500; // to avoid stack overflow errors /** * Indent the resulting serpent serialization text? */ public boolean indent = false; /** * Include package name in class name, for classes that are serialized to dicts? */ public boolean packageInClassName = false; /** * Use bytes literal representation instead of base-64 encoding? */ public boolean bytesRepr = false; private static Map, IClassSerializer> classToDictRegistry = new HashMap, IClassSerializer>(); /** * Create a Serpent serializer with default options. */ public Serializer() { } /** * Create a Serpent serializer with custom options. * @param indent should the output be indented to make it more readable? * @param packageInClassName should the package name be included with the class name for classes that are serialized to dict? */ public Serializer(boolean indent, boolean packageInClassName) { this.indent = indent; this.packageInClassName = packageInClassName; } /** * Create a Serpent serializer with custom options. * @param indent should the output be indented to make it more readable? * @param packageInClassName should the package name be included with the class name for classes that are serialized to dict? * @param bytesRepr use bytes literal representation instead of base-64 encoding for bytes types? */ public Serializer(boolean indent, boolean packageInClassName, boolean bytesRepr) { this.indent = indent; this.packageInClassName = packageInClassName; this.bytesRepr = bytesRepr; } /** * Register a custom class serializer, if you want to tweak the serialization of classes that Serpent doesn't know about yet. */ public static void registerClass(Class clazz, IClassSerializer converter) { classToDictRegistry.put(clazz, converter); } /** * Serialize an object graph to a serpent serialized form. */ public byte[] serialize(Object obj) { StringWriter sw = new StringWriter(); sw.write("# serpent utf-8 python3.2\n"); serialize(obj, sw, 0); sw.flush(); final String ser = sw.toString(); try { sw.close(); return ser.getBytes("utf-8"); } catch (IOException x) { throw new IllegalArgumentException("error creating output bytes: "+x); } } protected void serialize(Object obj, StringWriter sw, int level) { if(level>maximumLevel) throw new IllegalArgumentException("Object graph nesting too deep. Increase serializer.maximumLevel if you think you need more."); if(obj!=null && obj.getClass().getName().startsWith("org.python.")) obj = convertJythonObject(obj); // null -> None // hashtables/dictionaries -> dict // hashset -> set // array -> tuple // byte arrays --> base64 or bytes literal // any other collection --> list // date//uuid/exception -> custom mapping // random class --> public javabean properties to dictionary // primitive types --> simple mapping Class type = obj==null? null : obj.getClass(); Class componentType = type==null? null : type.getComponentType(); // primitive array? if(componentType!=null) { // byte array? encode as base-64 or bytes literal if(componentType==Byte.TYPE) { serialize_bytes((byte[])obj, sw, level); return; } else { serialize_primitive_array(obj, sw, level); } return; } if(obj==null) { sw.write("None"); } else if(obj instanceof String) { serialize_string((String)obj, sw, level); } else if(type.isPrimitive() || isBoxed(type)) { serialize_primitive(obj, sw, level); } else if(obj instanceof Enum) { serialize_string(obj.toString(), sw, level); } else if(obj instanceof BigDecimal) { serialize_bigdecimal((BigDecimal)obj, sw, level); } else if(obj instanceof Number) { serialize_primitive(obj, sw, level); } else if(obj instanceof Date) { serialize_date((Date)obj, sw, level); } else if(obj instanceof Calendar) { serialize_calendar((Calendar)obj, sw, level); } else if(obj instanceof UUID) { serialize_uuid((UUID)obj, sw, level); } else if(obj instanceof Set) { serialize_set((Set)obj, sw, level); } else if(obj instanceof Map) { serialize_dict((Map)obj, sw, level); } else if(obj instanceof Collection) { serialize_collection((Collection)obj, sw, level); } else if(obj instanceof ComplexNumber) { serialize_complex((ComplexNumber)obj, sw, level); } else if(obj instanceof Exception) { serialize_exception((Exception)obj, sw, level); } else if(obj instanceof Serializable) { serialize_class(obj, sw, level); } else { throw new IllegalArgumentException("cannot serialize object of type "+type); } } /** * When used from Jython directly, it sometimes passes some Jython specific * classes to the serializer (such as org.python.core.PyComplex for a complex number). * Due to the way these are constructed, Serpent is greatly confused, and will often * end up in an endless loop eventually crashing with too deep nesting. * For the know cases, we convert them here to appropriate representations. */ protected Object convertJythonObject(Object obj) { final Class clazz = obj.getClass(); final String classname = clazz.getName(); try { // use reflection because I don't want to have a compiler dependency on Jython. switch (classname) { case "org.python.core.PyTuple": return clazz.getMethod("toArray").invoke(obj); case "org.python.core.PyComplex": Object pyImag = clazz.getMethod("getImag").invoke(obj); Object pyReal = clazz.getMethod("getReal").invoke(obj); Double imag = (Double) pyImag.getClass().getMethod("getValue").invoke(pyImag); Double real = (Double) pyReal.getClass().getMethod("getValue").invoke(pyReal); return new ComplexNumber(real, imag); case "org.python.core.PyByteArray": Object pyStr = clazz.getMethod("__str__").invoke(obj); return pyStr.getClass().getMethod("toBytes").invoke(pyStr); case "org.python.core.PyMemoryView": Object pyBytes = clazz.getMethod("tobytes").invoke(obj); return pyBytes.getClass().getMethod("toBytes").invoke(pyBytes); } } catch (ReflectiveOperationException e) { throw new IllegalArgumentException("cannot serialize Jython object of type "+clazz, e); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("cannot serialize Jython object of type "+clazz, e); } catch (SecurityException e) { throw new IllegalArgumentException("cannot serialize Jython object of type "+clazz, e); } // instead of an endless nesting loop, report a proper exception throw new IllegalArgumentException("cannot serialize Jython object of type "+obj.getClass()); } protected void serialize_collection(Collection collection, StringWriter sw, int level) { // output a list sw.write("["); serialize_sequence_elements(collection, false, sw, level+1); if(this.indent && collection.size()>0) { for(int i=0; i elts, boolean trailingComma, StringWriter sw, int level) { if(elts.size()==0) return; int count=0; if(this.indent) { sw.write("\n"); String innerindent = ""; for(int i=0; i set, StringWriter sw, int level) { if(set.size()>0) { sw.write("{"); Collection output = set; if(this.indent) { // try to sort the set Set outputset = set; try { outputset = new TreeSet(set); } catch (ClassCastException x) { // ignore unsortable elements } output = outputset; } serialize_sequence_elements(output, false, sw, level+1); if(this.indent) { for(int i=0; i items = new ArrayList(length); for(int i=0; i items, StringWriter sw, int level) { sw.write("("); serialize_sequence_elements(items, items.size()==1, sw, level+1); if(this.indent && items.size()>0) { for(int i=0; i dict = new HashMap(); dict.put("data", str); dict.put("encoding", "base64"); serialize_dict(dict, sw, level); } } private void handleQuotes(StringWriter sw, StringBuilder b, boolean containsSingleQuote, boolean containsQuote, boolean isBytes) { if(!containsSingleQuote) { b.insert(0, '\''); b.append('\''); if(isBytes) sw.write('b'); sw.write(b.toString()); } else if (!containsQuote) { b.insert(0, '"'); b.append('"'); if(isBytes) sw.write('b'); sw.write(b.toString()); } else { String str2 = b.toString(); str2 = str2.replace("'", "\\'"); if(isBytes) sw.write('b'); sw.write("'"); sw.write(str2); sw.write("'"); } } protected void serialize_dict(Map dict, StringWriter sw,int level) { if(dict.size()==0) { sw.write("{}"); return; } int counter=0; if(this.indent) { String innerindent = " "; for(int i=0; i outputdict = dict; try { outputdict = new TreeMap(dict); } catch (ClassCastException x) { // ignore unsortable keys } for(Map.Entry e: outputdict.entrySet()) { sw.write(innerindent); serialize(e.getKey(), sw, level+1); sw.write(": "); serialize(e.getValue(), sw, level+1); counter++; if(counter e: dict.entrySet()) { serialize(e.getKey(), sw, level+1); sw.write(":"); serialize(e.getValue(), sw, level+1); counter++; if(counter0) { // we have millis df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"+tzformat); } else { // no millis df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"+tzformat); } df.setTimeZone(cal.getTimeZone()); serialize_string(df.format(cal.getTime()), sw, level); } protected void serialize_date(Date date, StringWriter sw, int level) { DateFormat df; if((date.getTime() % 1000) != 0) { // we have millis df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); } else { // no millis df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); } df.setTimeZone(TimeZone.getTimeZone("UTC")); serialize_string(df.format(date), sw, level); } protected void serialize_complex(ComplexNumber cplx, StringWriter sw, int level) { sw.write("("); serialize_primitive(cplx.real, sw, level); if(cplx.imaginary>=0) sw.write("+"); serialize_primitive(cplx.imaginary, sw, level); sw.write("j)"); } protected void serialize_uuid(UUID obj, StringWriter sw, int level) { serialize_string(obj.toString(), sw, level); } protected void serialize_bigdecimal(BigDecimal decimal, StringWriter sw, int level) { serialize_string(decimal.toEngineeringString(), sw, level); } private static final HashSet> boxedTypes; static { boxedTypes = new HashSet>(); boxedTypes.add(Boolean.class); boxedTypes.add(Character.class); boxedTypes.add(Byte.class); boxedTypes.add(Short.class); boxedTypes.add(Integer.class); boxedTypes.add(Long.class); boxedTypes.add(Float.class); boxedTypes.add(Double.class); } protected boolean isBoxed(Class type) { return boxedTypes.contains(type); } protected void serialize_class(Object obj, StringWriter sw, int level) { Map map; IClassSerializer converter=getCustomConverter(obj.getClass()); if(null!=converter) { map = converter.convert(obj); } else { map=new HashMap(); try { // note: don't use the java.bean api, because that is not available on Android. for(Method m: obj.getClass().getMethods()) { int modifiers = m.getModifiers(); if((modifiers & Modifier.PUBLIC)!=0 && (modifiers & Modifier.STATIC)==0) { String methodname = m.getName(); int prefixlen = 0; if(methodname.equals("getClass")) continue; if(methodname.startsWith("get")) prefixlen=3; else if(methodname.startsWith("is")) prefixlen=2; else continue; Object value = m.invoke(obj); String name = methodname.substring(prefixlen); if(name.length()==1) { name = name.toLowerCase(); } else { if(!Character.isUpperCase(name.charAt(1))) { name = Character.toLowerCase(name.charAt(0)) + name.substring(1); } } map.put(name, value); } } if(this.packageInClassName) map.put("__class__", obj.getClass().getName()); else map.put("__class__", obj.getClass().getSimpleName()); } catch (IllegalAccessException e) { throw new IllegalArgumentException("couldn't introspect javabean: "+e); } catch (InvocationTargetException e) { throw new IllegalArgumentException("couldn't introspect javabean: "+e); } } serialize_dict(map, sw, level); } protected IClassSerializer getCustomConverter(Class type) { IClassSerializer converter = classToDictRegistry.get(type.getClass()); if(converter!=null) { return converter; // exact match } // check if there's a custom pickler registered for an interface or abstract base class // that this object implements or inherits from. for(Entry, IClassSerializer> x: classToDictRegistry.entrySet()) { if(x.getKey().isAssignableFrom(type)) { return x.getValue(); } } return null; } protected void serialize_primitive(Object obj, StringWriter sw, int level) { if(obj instanceof Boolean || obj.getClass()==Boolean.TYPE) { sw.write(obj.equals(Boolean.TRUE)? "True": "False"); } else if (obj instanceof Float || obj.getClass()==Float.TYPE) { Float f = (Float)obj; serialize_primitive(f.doubleValue(), sw, level); } else if (obj instanceof Double || obj.getClass()==Double.TYPE) { Double d = (Double) obj; if(d.isInfinite()) { // output a literal expression that overflows the float and results in +/-INF if(d>0.0) { sw.write("1e30000"); } else { sw.write("-1e30000"); } } else if(d.isNaN()) { // there's no literal expression for a float NaN... sw.write("{'__class__':'float','value':'nan'}"); } else { sw.write(d.toString()); } } else { sw.write(obj.toString()); } } // the repr translation table for characters 0x00-0xff private final static String[] repr_255; private final static String[] bytesrepr_255; static { repr_255=new String[256]; bytesrepr_255=new String[256]; for(int c=0; c<32; ++c) { repr_255[c] = String.format("\\x%02x",c); bytesrepr_255[c] = String.format("\\x%02x",c); } for(char c=0x20; c<0x7f; ++c) { repr_255[c] = String.valueOf(c); bytesrepr_255[c] = String.valueOf(c); } for(int c=0x7f; c<=0xa0; ++c) { repr_255[c] = String.format("\\x%02x", c); } for(char c=0xa1; c<=0xff; ++c) { repr_255[c] = String.valueOf(c); } for(int c=0x7f; c<=0xff; ++c) { bytesrepr_255[c] = String.format("\\x%02x", c); } // odd ones out: repr_255['\t'] = "\\t"; repr_255['\n'] = "\\n"; repr_255['\r'] = "\\r"; repr_255['\\'] = "\\\\"; repr_255[0xad] = "\\xad"; bytesrepr_255['\t'] = "\\t"; bytesrepr_255['\n'] = "\\n"; bytesrepr_255['\r'] = "\\r"; bytesrepr_255['\\'] = "\\\\"; } protected void serialize_string(String str, StringWriter sw, int level) { // create a 'repr' string representation following the same escaping rules as python 3.x repr() does. StringBuilder b=new StringBuilder(str.length()*2); boolean containsSingleQuote=false; boolean containsQuote=false; for(int i=0; i dict; IClassSerializer converter=classToDictRegistry.get(ex.getClass()); if(null!=converter) { dict = converter.convert(ex); } else { dict = new HashMap(); if(this.packageInClassName) dict.put("__class__", ex.getClass().getName()); else dict.put("__class__", ex.getClass().getSimpleName()); dict.put("__exception__", true); dict.put("args", new String[]{ex.getMessage()}); dict.put("attributes", java.util.Collections.EMPTY_MAP); } serialize_dict(dict, sw, level); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/000077500000000000000000000000001425606146200251745ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/Ast.java000066400000000000000000000024441425606146200265720ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent.ast; import net.razorvine.serpent.IDictToInstance; import net.razorvine.serpent.ObjectifyVisitor; /** * Abstract syntax tree for the literal expression. This is what the parser returns. */ public class Ast { public INode root; @Override public String toString() { return "# serpent utf-8 .net\n" + root.toString(); } /** * get the actual data as Java objects. */ public Object getData() { ObjectifyVisitor v = new ObjectifyVisitor(); this.accept(v); return v.getObject(); } /** * get the actual data as Java objects. * @param dictConverter object to convert dicts to actual instances for a class, * instead of leaving them as dictionaries. Requires the __class__ key to be present * in the dict node. If it returns null, the normal processing is done. */ public Object getData(IDictToInstance dictConverter) { ObjectifyVisitor v = new ObjectifyVisitor(dictConverter); this.accept(v); return v.getObject(); } public void accept(INodeVisitor visitor) { root.accept(visitor); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/BigIntNode.java000066400000000000000000000004171425606146200300230ustar00rootroot00000000000000package net.razorvine.serpent.ast; import java.math.BigInteger; public class BigIntNode extends PrimitiveNode { public BigIntNode(BigInteger value) { super(value); } @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/BooleanNode.java000066400000000000000000000003551425606146200302270ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class BooleanNode extends PrimitiveNode { public BooleanNode(boolean value) { super(value); } @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/BytesNode.java000066400000000000000000000006501425606146200277340ustar00rootroot00000000000000package net.razorvine.serpent.ast; import java.util.List; public class BytesNode extends PrimitiveNode> { public BytesNode(List value) { super(value); } @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } public byte[] toByteArray() { byte[] bytes = new byte[value.size()]; for(int i=0; i=0) return String.format("(%s+%sj)", strReal, strImag); return String.format("(%s%sj)", strReal, strImag); } @Override public boolean equals(Object obj) { if(!(obj instanceof ComplexNumberNode)) return false; ComplexNumberNode other = (ComplexNumberNode) obj; return real==other.real && imaginary==other.imaginary; } @Override public int hashCode() { return new Double(real).hashCode() ^ new Double(imaginary).hashCode(); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/DictNode.java000066400000000000000000000004431425606146200275310ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class DictNode extends UnorderedSequenceNode { @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } @Override public char getOpenChar() { return '{'; } @Override public char getCloseChar() { return '}'; } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/DoubleNode.java000066400000000000000000000003501425606146200300550ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class DoubleNode extends PrimitiveNode { public DoubleNode(double value) { super(value); } @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/INode.java000066400000000000000000000002241425606146200270330ustar00rootroot00000000000000package net.razorvine.serpent.ast; public interface INode { String toString(); boolean equals(Object obj); void accept(INodeVisitor visitor); } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/INodeVisitor.java000066400000000000000000000007261425606146200304220ustar00rootroot00000000000000package net.razorvine.serpent.ast; public interface INodeVisitor { void visit(ComplexNumberNode complex); void visit(DictNode dict); void visit(ListNode list); void visit(NoneNode none); void visit(IntegerNode value); void visit(LongNode value); void visit(DoubleNode value); void visit(BooleanNode value); void visit(StringNode value); void visit(BytesNode value); void visit(SetNode setnode); void visit(TupleNode tuple); void visit(BigIntNode value); } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/IntegerNode.java000066400000000000000000000003501425606146200302400ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class IntegerNode extends PrimitiveNode { public IntegerNode(int value) { super(value); } @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/KeyValueNode.java000066400000000000000000000013661425606146200304000ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class KeyValueNode implements INode { public INode key; public INode value; public KeyValueNode() {} public KeyValueNode(INode key, INode value) { this.key = key; this.value = value; } @Override public String toString() { return String.format("%s:%s", key, value); } public void accept(INodeVisitor visitor) { throw new NoSuchMethodError("don't visit a keyvaluenode"); } @Override public boolean equals(Object obj) { if(!(obj instanceof KeyValueNode)) return false; KeyValueNode other = (KeyValueNode) obj; return key.equals(other.key) && value.equals(other.value); } @Override public int hashCode() { return key.hashCode() ^ (1000000007 * value.hashCode()); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/ListNode.java000066400000000000000000000004321425606146200275570ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class ListNode extends SequenceNode { @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } @Override public char getOpenChar() { return '['; } @Override public char getCloseChar() { return ']'; } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/LongNode.java000066400000000000000000000003411425606146200275420ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class LongNode extends PrimitiveNode { public LongNode(long value) { super(value); } @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/NoneNode.java000066400000000000000000000004271425606146200275470ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class NoneNode implements INode { public static NoneNode Instance = new NoneNode(); private NoneNode() { } public String toString() { return "None"; } public void accept(INodeVisitor visitor) { visitor.visit(this); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/PrimitiveNode.java000066400000000000000000000031721425606146200306200ustar00rootroot00000000000000package net.razorvine.serpent.ast; public abstract class PrimitiveNode implements INode, Comparable { public T value; public PrimitiveNode(T value) { this.value=value; } @Override public int hashCode() { return value!=null? value.hashCode() : 0; } @Override public boolean equals(Object obj) { return (obj instanceof PrimitiveNode) && value.equals(((PrimitiveNode)obj).value); } public int compareTo(T other) { return 0; } public boolean equals(PrimitiveNode other) { return this.value.equals(other.value); } @Override public String toString() { if(value instanceof String) { StringBuilder sb=new StringBuilder(); sb.append("'"); String strValue = (String)value; for(char c: strValue.toCharArray()) { switch(c) { case '\\': sb.append("\\\\"); break; case '\'': sb.append("\\'"); break; case '\b': sb.append("\\b"); break; case '\f': sb.append("\\f"); break; case '\n': sb.append("\\n"); break; case '\r': sb.append("\\r"); break; case '\t': sb.append("\\t"); break; default: sb.append(c); break; } } sb.append("'"); return sb.toString(); } else if(value instanceof Boolean) { return value.equals(Boolean.TRUE)? "True": "False"; } else if(value instanceof Float || value instanceof Double) { String d = value.toString(); if(d.indexOf('.')<=0 && d.indexOf('e')<=0 && d.indexOf('E')<=0) d+=".0"; return d; } else return value.toString(); } public abstract void accept(INodeVisitor visitor); } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/SequenceNode.java000066400000000000000000000020131425606146200304110ustar00rootroot00000000000000package net.razorvine.serpent.ast; import java.util.ArrayList; import java.util.List; public abstract class SequenceNode implements INode { public List elements = new ArrayList(); public abstract char getOpenChar(); public abstract char getCloseChar(); @Override public int hashCode() { int hashCode = 0; for(INode elt: elements) hashCode += 1000000007 * elt.hashCode(); return hashCode; } @Override public boolean equals(Object obj) { if(!(obj instanceof SequenceNode)) return false; SequenceNode other = (SequenceNode)obj; return elements.equals(other.elements); } @Override public String toString() { StringBuilder sb=new StringBuilder(); sb.append(getOpenChar()); if(elements != null) { for(INode elt: elements) { sb.append(elt.toString()); sb.append(','); } } if(elements.size()>0) sb.deleteCharAt(sb.length()-1); // remove last comma sb.append(getCloseChar()); return sb.toString(); } public abstract void accept(INodeVisitor visitor); } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/SetNode.java000066400000000000000000000004421425606146200274000ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class SetNode extends UnorderedSequenceNode { @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } @Override public char getOpenChar() { return '{'; } @Override public char getCloseChar() { return '}'; } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/StringNode.java000066400000000000000000000003501425606146200301110ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class StringNode extends PrimitiveNode { public StringNode(String value) { super(value); } @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/TupleNode.java000066400000000000000000000012121425606146200277320ustar00rootroot00000000000000package net.razorvine.serpent.ast; public class TupleNode extends SequenceNode { @Override public String toString() { StringBuilder sb=new StringBuilder(); sb.append(getOpenChar()); if(elements != null) { for(INode elt: elements) { sb.append(elt.toString()); sb.append(","); } } if(elements.size()>1) sb.deleteCharAt(sb.length()-1); // remove last comma sb.append(getCloseChar()); return sb.toString(); } @Override public void accept(INodeVisitor visitor) { visitor.visit(this); } @Override public char getOpenChar() { return '('; } @Override public char getCloseChar() { return ')'; } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/ast/UnorderedSequenceNode.java000066400000000000000000000007331425606146200322700ustar00rootroot00000000000000package net.razorvine.serpent.ast; import java.util.HashSet; import java.util.Set; abstract class UnorderedSequenceNode extends SequenceNode { @Override public boolean equals(Object obj) { if(!(obj instanceof UnorderedSequenceNode)) return false; Set set1 = elementsAsSet(); Set set2 = ((UnorderedSequenceNode)obj).elementsAsSet(); return set1.equals(set2); } public Set elementsAsSet() { return new HashSet(elements); } } Serpent-serpent-1.41/java/src/main/java/net/razorvine/serpent/package-info.java000066400000000000000000000004531425606146200275760ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) * @version 1.40 */ package net.razorvine.serpent; Serpent-serpent-1.41/java/src/test/000077500000000000000000000000001425606146200172325ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/test/java/000077500000000000000000000000001425606146200201535ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/test/java/SerpentExample.java000066400000000000000000000075001425606146200237540ustar00rootroot00000000000000 import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.Serializable; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import net.razorvine.serpent.DebugVisitor; import net.razorvine.serpent.LibraryVersion; import net.razorvine.serpent.Parser; import net.razorvine.serpent.Serializer; import net.razorvine.serpent.ast.Ast; public class SerpentExample { enum Foo { Foobar, Jarjar } public static void main(String[] args) { SerpentExample t=new SerpentExample(); try { t.run(); } catch (IOException e) { e.printStackTrace(); } } @SuppressWarnings("unchecked") public void run() throws IOException { // some example use of Serpent System.out.println("Using serpent library version "+LibraryVersion.VERSION); Map data = new HashMap(); data.put("tuple", new int[] { 1,2,3 }); data.put("date", new java.util.Date()); Set set = new HashSet(); set.add("c"); set.add("b"); set.add("a"); data.put("set", set); data.put("class", new SampleClass("Sally", 26)); // serialize data structure to bytes Serializer serpent = new Serializer(true, false); byte[] ser = serpent.serialize(data); // print it on the screen, but normally you'd store byte bytes in a file or transfer them across a network connection System.out.println("Serialized:"); System.out.println(new String(ser, "utf-8")); // parse the serialized bytes back into an abstract syntax tree of the datastructure Parser parser = new Parser(); Ast ast = parser.parse(ser); System.out.println("\nParsed AST:"); System.out.println(ast.root.toString()); // print debug representation DebugVisitor dv = new DebugVisitor(); ast.accept(dv); System.out.println("DEBUG string representation:"); System.out.println(dv.toString()); // turn the Ast into regular Java objects Map dict = (Map)ast.getData(); // You can get the data out of the Ast manually as well, by using the supplied visitor: // ObjectifyVisitor visitor = new ObjectifyVisitor(); // ast.accept(visitor); // Map dict = (Map) visitor.getObject(); // print the results System.out.println("PARSED results:"); System.out.print("tuple items: "); Object[] tuple = (Object[]) dict.get("tuple"); for(Object o: tuple) System.out.print(" "+o.toString()+","); System.out.println(""); System.out.println("date: "+dict.get("date")); System.out.print("set items: "); Set set2 = (Set) dict.get("set"); for(Object o: set2) System.out.print(" "+o.toString()+","); System.out.println(""); System.out.println("class attributes:"); Map clazz = (Map) dict.get("class"); // custom classes are serialized as dicts System.out.println(" type: "+clazz.get("__class__")); System.out.println(" name: "+clazz.get("name")); System.out.println(" age: "+clazz.get("age")); System.out.println(""); // parse and print the example file File testdatafile = new File("src/test/java/testserpent.utf8.bin"); ser = new byte[(int) testdatafile.length()]; FileInputStream fis=new FileInputStream(testdatafile); DataInputStream dis = new DataInputStream(fis); dis.readFully(ser); dis.close(); fis.close(); ast = parser.parse(ser); dv = new DebugVisitor(); ast.accept(dv); System.out.println("DEBUG string representation of the test file:"); System.out.println(dv.toString()); } public class SampleClass implements Serializable { private static final long serialVersionUID = -782424804184940436L; int a; String n; public SampleClass(String name, int age) { a=age; n=name; } public int getAge() { return a; } public String getName() { return n; } } } Serpent-serpent-1.41/java/src/test/java/net/000077500000000000000000000000001425606146200207415ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/test/java/net/razorvine/000077500000000000000000000000001425606146200227605ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/test/java/net/razorvine/serpent/000077500000000000000000000000001425606146200244405ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/test/java/net/razorvine/serpent/test/000077500000000000000000000000001425606146200254175ustar00rootroot00000000000000Serpent-serpent-1.41/java/src/test/java/net/razorvine/serpent/test/CycleTest.java000066400000000000000000000053141425606146200301640ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent.test; import java.util.HashMap; import java.util.LinkedList; import net.razorvine.serpent.Parser; import net.razorvine.serpent.Serializer; import org.junit.Test; import static org.junit.Assert.*; public class CycleTest { @Test public void testTupleOk() { Serializer ser = new Serializer(); int[] t = new int[] {1,2,3}; Object[] d = new Object[] {t,t,t}; byte[] data = ser.serialize(d); Parser parser = new Parser(); parser.parse(data); } @Test public void testListOk() { Serializer ser = new Serializer(); LinkedList t = new LinkedList(); t.add(1); t.add(2); t.add(3); LinkedList d = new LinkedList(); d.add(t); d.add(t); d.add(t); byte[] data = ser.serialize(d); Parser parser = new Parser(); parser.parse(data); } @Test public void testDictOk() { Serializer ser = new Serializer(); HashMap t = new HashMap(); t.put("a", 1); HashMap d = new HashMap(); d.put("x", t); d.put("y", t); d.put("z", t); byte[] data = ser.serialize(d); Parser parser = new Parser(); parser.parse(data); } @Test(expected=IllegalArgumentException.class) public void testListCycle() { Serializer ser = new Serializer(); LinkedList d = new LinkedList(); d.add(1); d.add(2); d.add(d); ser.serialize(d); } @Test(expected=IllegalArgumentException.class) public void testDictCycle() { Serializer ser = new Serializer(); HashMap d = new HashMap(); d.put("x", 1); d.put("y", 2); d.put("z", d); ser.serialize(d); } @Test(expected=IllegalArgumentException.class) public void testClassCycle() { Serializer ser = new Serializer(); SerializationHelperClass thing = new SerializationHelperClass(); thing.i = 42; thing.s = "hello"; thing.x = 99; thing.obj = thing; ser.serialize(thing); } @Test public void testMaxLevel() { Serializer ser = new Serializer(); assertEquals(500, ser.maximumLevel); Object[] array = new Object[] { "level1", new Object[] { "level2", new Object[] { "level3", new Object[] { "level 4" } } } }; ser.maximumLevel = 4; ser.serialize(array); // should work ser.maximumLevel = 3; try { ser.serialize(array); fail("should fail"); } catch(IllegalArgumentException x) { assertTrue(x.getMessage().contains("too deep")); } } } Serpent-serpent-1.41/java/src/test/java/net/razorvine/serpent/test/ParserTest.java000066400000000000000000000704501425606146200303640ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent.test; import static org.junit.Assert.*; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Set; import net.razorvine.serpent.ObjectifyVisitor; import net.razorvine.serpent.ParseException; import net.razorvine.serpent.Parser; import net.razorvine.serpent.SeekableStringReader; import net.razorvine.serpent.Serializer; import net.razorvine.serpent.ast.*; import org.junit.Ignore; import org.junit.Test; public class ParserTest { @Test public void TestBasic() { Parser p = new Parser(); assertNull(p.parse((String)null).root); assertNull(p.parse("").root); assertNotNull(p.parse("# comment\n42\n").root); } @Test public void TestComments() { Parser p = new Parser(); Ast ast = p.parse("[ 1, 2 ]"); // no header whatsoever ObjectifyVisitor visitor = new ObjectifyVisitor(); ast.accept(visitor); Object obj = visitor.getObject(); List expected = new ArrayList(); expected.add(1); expected.add(2); assertEquals(expected, obj); expected = new LinkedList(); expected.add(1); expected.add(2); assertEquals(expected, obj); ast = p.parse("# serpent utf-8 python2.7\n"+ "[ 1, 2,\n"+ " # some comments here\n"+ " 3, 4] # more here\n"+ "# and here.\n"); visitor = new ObjectifyVisitor(); ast.accept(visitor); obj = visitor.getObject(); expected = new LinkedList(); expected.add(1); expected.add(2); expected.add(3); expected.add(4); assertEquals(expected, obj); } @Test public void TestPrimitives() { Parser p = new Parser(); assertEquals(new IntegerNode(42), p.parse("42").root); assertEquals(new IntegerNode(-42), p.parse("-42").root); assertEquals(new DoubleNode(42.331), p.parse("42.331").root); assertEquals(new DoubleNode(-42.331), p.parse("-42.331").root); assertEquals(new DoubleNode(-1.2e19), p.parse("-1.2e+19").root); assertEquals(new DoubleNode(-1.2e-19), p.parse("-1.2e-19").root); assertEquals(new DoubleNode(0.0004), p.parse("4e-4").root); assertEquals(new DoubleNode(40000), p.parse("4e4").root); assertEquals(new BooleanNode(true), p.parse("True").root); assertEquals(new BooleanNode(false), p.parse("False").root); assertEquals(NoneNode.Instance, p.parse("None").root); // long ints assertEquals(new BigIntNode(new BigInteger("123456789123456789123456789")), p.parse("123456789123456789123456789").root); assertFalse(new LongNode(52).equals(p.parse("52").root)); assertEquals(new LongNode(123456789123456789L), p.parse("123456789123456789").root); assertEquals(BigIntNode.class, p.parse("12345678912345678912345678912345678978571892375798273578927389758378467693485903859038593453475897349587348957893457983475983475893475893785732957398475").root.getClass()); } @Test public void TestWeirdFloats() { Parser p = new Parser(); DoubleNode d = (DoubleNode) p.parse("1e30000").root; assertTrue(Double.isInfinite(d.value)); assertTrue(d.value > 0.0); d = (DoubleNode) p.parse("-1e30000").root; assertTrue(Double.isInfinite(d.value)); assertTrue(d.value < 0.0); TupleNode tuple = (TupleNode) p.parse("(1e30000,-1e30000,{'__class__':'float','value':'nan'})").root; assertEquals(3, tuple.elements.size()); d = (DoubleNode) tuple.elements.get(0); assertTrue(Double.isInfinite(d.value)); d = (DoubleNode) tuple.elements.get(1); assertTrue(Double.isInfinite(d.value)); assertTrue(d.value < 0.0); d = (DoubleNode) tuple.elements.get(2); assertTrue(Double.isNaN(d.value)); ComplexNumberNode c = (ComplexNumberNode) p.parse("(1e30000-1e30000j)").root; assertTrue(Double.isInfinite(c.real)); assertTrue(c.real>0.0); assertTrue(Double.isInfinite(c.imaginary)); assertTrue(c.imaginary<0.0); } @Test public void TestFloatPrecision() { Parser p = new Parser(); Serializer serpent = new Serializer(); byte[] ser = serpent.serialize(1.2345678987654321); DoubleNode dv = (DoubleNode) p.parse(ser).root; assertEquals(Double.valueOf(1.2345678987654321), dv.value); ser = serpent.serialize(5555.12345678987656); dv = (DoubleNode) p.parse(ser).root; assertEquals(Double.valueOf(5555.12345678987656), dv.value); ser = serpent.serialize(98765432123456.12345678987656); dv = (DoubleNode) p.parse(ser).root; assertEquals(Double.valueOf(98765432123456.12345678987656), dv.value); ser = serpent.serialize(98765432123456.12345678987656e+44); dv = (DoubleNode) p.parse(ser).root; assertEquals(Double.valueOf(98765432123456.12345678987656e+44), dv.value); ser = serpent.serialize(-98765432123456.12345678987656e-44); dv = (DoubleNode) p.parse(ser).root; assertEquals(Double.valueOf(-98765432123456.12345678987656e-44), dv.value); } @Test public void TestEquality() { INode n1, n2; n1 = new IntegerNode(42); n2 = new IntegerNode(42); assertEquals(n1, n2); n2 = new IntegerNode(43); assertFalse(n1.equals(n2)); n1 = new StringNode("foo"); n2 = new StringNode("foo"); assertEquals(n1, n2); n2 = new StringNode("bar"); assertFalse(n1.equals(n2)); ComplexNumberNode cn1 = new ComplexNumberNode(); cn1.real=1.1; cn1.imaginary=2.2; ComplexNumberNode cn2 = new ComplexNumberNode(); cn2.real=1.1; cn2.imaginary=2.2; assertEquals(cn1, cn2); cn2 = new ComplexNumberNode(); cn2.real=1.1; cn2.imaginary=3.3; assertFalse(cn1.equals(cn2)); KeyValueNode kvn1=new KeyValueNode(); kvn1.key = new IntegerNode(42); kvn1.value = new IntegerNode(42); KeyValueNode kvn2=new KeyValueNode(); kvn2.key=new IntegerNode(42); kvn2.value=new IntegerNode(42); assertEquals(kvn1, kvn2); kvn1=new KeyValueNode(); kvn1.key=new IntegerNode(43); kvn1.value=new IntegerNode(43); assertFalse(kvn1.equals(kvn2)); n1=NoneNode.Instance; n2=NoneNode.Instance; assertEquals(n1, n2); n2=new IntegerNode(42); assertFalse(n1.equals(n2)); DictNode dn1 = new DictNode(); kvn1 = new KeyValueNode(); kvn1.key = new IntegerNode(42); kvn1.value = new IntegerNode(42); dn1.elements.add(kvn1); DictNode dn2=new DictNode(); kvn1 = new KeyValueNode(); kvn1.key =new IntegerNode(42); kvn1.value = new IntegerNode(42); dn2.elements.add(kvn1); assertEquals(dn1, dn2); dn2=new DictNode(); kvn1 = new KeyValueNode(); kvn1.key = new IntegerNode(42); kvn1.value = new IntegerNode(43); dn2.elements.add(kvn1); assertFalse(dn1.equals(dn2)); ListNode ln1=new ListNode(); ln1.elements.add(new IntegerNode(42)); ListNode ln2=new ListNode(); ln2.elements.add(new IntegerNode(42)); assertEquals(ln1,ln2); ln2=new ListNode(); ln2.elements.add(new IntegerNode(43)); assertFalse(ln1.equals(ln2)); SetNode sn1=new SetNode(); sn1.elements.add(new IntegerNode(42)); SetNode sn2=new SetNode(); sn2.elements.add(new IntegerNode(42)); assertEquals(sn1,sn2); sn2=new SetNode(); sn2.elements.add(new IntegerNode(43)); assertFalse(sn1.equals(sn2)); TupleNode tn1=new TupleNode(); tn1.elements.add(new IntegerNode(42)); TupleNode tn2=new TupleNode(); tn2.elements.add(new IntegerNode(42)); assertEquals(tn1,tn2); tn2=new TupleNode(); tn2.elements.add(new IntegerNode(43)); assertFalse(tn1.equals(tn2)); } @Test public void TestPrintSingle() { Parser p = new Parser(); // primitives assertEquals("42", p.parse("42").root.toString()); assertEquals("-42.331", p.parse("-42.331").root.toString()); assertEquals("-42.0", p.parse("-42.0").root.toString()); assertEquals("-2.0E20", p.parse("-2E20").root.toString()); assertEquals("2.0", p.parse("2.0").root.toString()); assertEquals("1.2E19", p.parse("1.2e19").root.toString()); assertEquals("True", p.parse("True").root.toString()); assertEquals("'hello'", p.parse("'hello'").root.toString()); assertEquals("'\\n'", p.parse("'\n'").root.toString()); assertEquals("'\\''", p.parse("'\\''").root.toString()); assertEquals("'\"'", p.parse("'\\\"'").root.toString()); assertEquals("'\"'", p.parse("'\"'").root.toString()); assertEquals("'\\\\'", p.parse("'\\\\'").root.toString()); assertEquals("None", p.parse("None").root.toString()); String ustr = "'\u20ac\u2603'"; assertEquals(ustr, p.parse(ustr).root.toString()); // complex assertEquals("(0.0+2.0j)", p.parse("2j").root.toString()); assertEquals("(-1.1-2.2j)", p.parse("(-1.1-2.2j)").root.toString()); assertEquals("(1.1+2.2j)", p.parse("(1.1+2.2j)").root.toString()); // long int assertEquals("123456789123456789123456789", p.parse("123456789123456789123456789").root.toString()); } @Test public void TestPrintSeq() { Parser p=new Parser(); //tuple assertEquals("()", p.parse("()").root.toString()); assertEquals("(42,)", p.parse("(42,)").root.toString()); assertEquals("(42,43)", p.parse("(42,43)").root.toString()); // list assertEquals("[]", p.parse("[]").root.toString()); assertEquals("[42]", p.parse("[42]").root.toString()); assertEquals("[42,43]", p.parse("[42,43]").root.toString()); // set assertEquals("{42}", p.parse("{42}").root.toString()); assertEquals("{42,43}", p.parse("{42,43,43,43}").root.toString()); // dict assertEquals("{}", p.parse("{}").root.toString()); assertEquals("{'a':42}", p.parse("{'a': 42}").root.toString()); String result = p.parse("{'a': 42, 'b': 43}").root.toString(); assertTrue(result.equals("{'a':42,'b':43}") || result.equals("{'b':43,'a':42}")); result = p.parse("{'a': 42, 'b': 43, 'b': 44, 'b': 45}").root.toString(); assertTrue(result.equals("{'a':42,'b':45}") || result.equals("{'b':45,'a':42}")); } @Test(expected=ParseException.class) public void TestInvalidPrimitive1() { new Parser().parse("1+2"); } @Test(expected=ParseException.class) public void TestInvalidPrimitive2() { new Parser().parse("1-2"); } @Test(expected=ParseException.class) public void TestInvalidPrimitive3() { new Parser().parse("1.1+2.2"); } @Test(expected=ParseException.class) public void TestInvalidPrimitive4() { new Parser().parse("1.1-2.2"); } @Test(expected=ParseException.class) public void TestInvalidPrimitive5() { new Parser().parse("True+2"); } @Test(expected=ParseException.class) public void TestInvalidPrimitive6() { new Parser().parse("False-2"); } @Test(expected=ParseException.class) public void TestInvalidPrimitive7() { new Parser().parse("3j+2"); } @Test(expected=ParseException.class) public void TestInvalidPrimitive8() { new Parser().parse("3j-2"); } @Test(expected=ParseException.class) public void TestInvalidPrimitive9() { new Parser().parse("None+2"); } @Test(expected=ParseException.class) public void TestInvalidPrimitive10() { new Parser().parse("None-2"); } @Test(expected=ParseException.class) public void TestInvalidTuple1() { new Parser().parse("(42,43]"); } @Test(expected=ParseException.class) public void TestInvalidTuple2() { new Parser().parse("()@"); } @Test(expected=ParseException.class) public void TestInvalidTuple3() { new Parser().parse("(42,43)@"); } @Test(expected=ParseException.class) public void TestInvalidList2() { new Parser().parse("[42,43}"); } @Test(expected=ParseException.class) public void TestInvalidList3() { new Parser().parse("[]@"); } @Test(expected=ParseException.class) public void TestInvalidList4() { new Parser().parse("[42,43]@"); } @Test(expected=ParseException.class) public void TestInvalidSet2() { new Parser().parse("{42,43]"); } @Test(expected=ParseException.class) public void TestInvalidSet3() { new Parser().parse("{42,43}@"); } @Test(expected=ParseException.class) public void TestInvalidDict2() { new Parser().parse("{'key': 42]"); } @Test(expected=ParseException.class) public void TestInvalidDict3() { new Parser().parse("{}@"); } @Test(expected=ParseException.class) public void TestInvalidDict4() { new Parser().parse("{'key': 42}@"); } @Test public void TestComplex() { Parser p = new Parser(); ComplexNumberNode cplx = new ComplexNumberNode(); cplx.real = 4.2; cplx.imaginary = 3.2; ComplexNumberNode cplx2 = new ComplexNumberNode(); cplx2.real = 4.2; cplx2.imaginary = 99; assertFalse(cplx.equals(cplx2)); cplx2.imaginary = 3.2; assertEquals(cplx, cplx2); assertEquals(cplx, p.parse("(4.2+3.2j)").root); cplx.real = 0; assertEquals(cplx, p.parse("(0+3.2j)").root); assertEquals(cplx, p.parse("3.2j").root); assertEquals(cplx, p.parse("+3.2j").root); cplx.imaginary = -3.2; assertEquals(cplx, p.parse("-3.2j").root); cplx.real = -9.9; assertEquals(cplx, p.parse("(-9.9-3.2j)").root); cplx.real = 2; cplx.imaginary = 3; assertEquals(cplx, p.parse("(2+3j)").root); cplx.imaginary = -3; assertEquals(cplx, p.parse("(2-3j)").root); cplx.real = 0; assertEquals(cplx, p.parse("-3j").root); cplx.real = -3.2e32; cplx.imaginary = -9.9e44; assertEquals(cplx, p.parse("(-3.2e32-9.9e44j)").root); assertEquals(cplx, p.parse("(-3.2e+32-9.9e+44j)").root); assertEquals(cplx, p.parse("(-3.2e32 -9.9e44j)").root); assertEquals(cplx, p.parse("(-3.2e+32 -9.9e+44j)").root); cplx.imaginary = 9.9e44; assertEquals(cplx, p.parse("(-3.2e32+9.9e44j)").root); assertEquals(cplx, p.parse("(-3.2e+32+9.9e+44j)").root); cplx.real = -3.2e-32; cplx.imaginary = -9.9e-44; assertEquals(cplx, p.parse("(-3.2e-32-9.9e-44j)").root); } @Test public void TestComplexPrecision() { Parser p = new Parser(); ComplexNumberNode cv = (ComplexNumberNode)p.parse("(98765432123456.12345678987656+665544332211.9998877665544j)").root; assertEquals(Double.valueOf(98765432123456.12345678987656), cv.real, 0); assertEquals(Double.valueOf(665544332211.9998877665544), cv.imaginary, 0); cv = (ComplexNumberNode)p.parse("(98765432123456.12345678987656-665544332211.9998877665544j)").root; assertEquals(Double.valueOf(98765432123456.12345678987656), cv.real, 0); assertEquals(Double.valueOf(-665544332211.9998877665544), cv.imaginary, 0); cv = (ComplexNumberNode)p.parse("(98765432123456.12345678987656e+33+665544332211.9998877665544e+44j)").root; assertEquals(Double.valueOf(98765432123456.12345678987656e+33), cv.real, 0); assertEquals(Double.valueOf(665544332211.9998877665544e+44), cv.imaginary, 0); cv = (ComplexNumberNode)p.parse("(-98765432123456.12345678987656e+33-665544332211.9998877665544e+44j)").root; assertEquals(Double.valueOf(-98765432123456.12345678987656e+33), cv.real, 0); assertEquals(Double.valueOf(-665544332211.9998877665544e+44), cv.imaginary, 0); } @Test public void TestPrimitivesStuffAtEnd() { Parser p = new Parser(); assertEquals(new IntegerNode(42), p.parseSingle(new SeekableStringReader("42@"))); assertEquals(new DoubleNode(42.331), p.parseSingle(new SeekableStringReader("42.331@"))); assertEquals(new BooleanNode(true), p.parseSingle(new SeekableStringReader("True@"))); assertEquals(NoneNode.Instance, p.parseSingle(new SeekableStringReader("None@"))); ComplexNumberNode cplx = new ComplexNumberNode(); cplx.real=4; cplx.imaginary=3; assertEquals(cplx, p.parseSingle(new SeekableStringReader("(4+3j)@"))); cplx.real=0; assertEquals(cplx, p.parseSingle(new SeekableStringReader("3j@"))); } @Test public void TestStrings() { Parser p = new Parser(); assertEquals(new StringNode("hello"), p.parse("'hello'").root); assertEquals(new StringNode("hello"), p.parse("\"hello\"").root); assertEquals(new StringNode("\\"), p.parse("'\\\\'").root); assertEquals(new StringNode("\\"), p.parse("\"\\\\\"").root); assertEquals(new StringNode("'"), p.parse("\"'\"").root); assertEquals(new StringNode("\""), p.parse("'\"'").root); assertEquals(new StringNode("tab\tnewline\n."), p.parse("'tab\\tnewline\\n.'").root); } @Test public void TestUnicode() throws UnsupportedEncodingException { Parser p = new Parser(); String str = "'\u20ac\u2603'"; assertEquals(0x20ac, str.charAt(1)); assertEquals(0x2603, str.charAt(2)); byte[] bytes = str.getBytes("utf-8"); String value = "\u20ac\u2603"; assertEquals(new StringNode(value), p.parse(str).root); assertEquals(new StringNode(value), p.parse(bytes).root); } @Test public void testLongUnicodeRoundtrip() { char[] chars64k = new char[65536]; for(int i=0; i<=65535; ++i) chars64k[i]=(char)i; String str64k= new String(chars64k); Serializer ser=new Serializer(); byte[] data = ser.serialize(str64k); assertTrue(data.length > chars64k.length); Parser p=new Parser(); String result = (String)p.parse(data).getData(); assertEquals(str64k, result); } @Test public void TestWhitespace() { Parser p = new Parser(); assertEquals(new IntegerNode(42), p.parse(" 42 ").root); assertEquals(new IntegerNode(42), p.parse(" 42 ").root); assertEquals(new IntegerNode(42), p.parse("\t42\r\n").root); assertEquals(new IntegerNode(42), p.parse(" \t 42 \r \n ").root); assertEquals(new StringNode(" string value "), p.parse(" ' string value ' ").root); try { p.parse(" ( 42 , 43 , "); // missing tuple close ) fail("expected parse error"); } catch (ParseException x) { //ok } try { p.parse(" [ 42 , 43 , "); // missing list close ) fail("expected parse error"); } catch (ParseException x) { //ok } try { p.parse(" { 42 , 43 , "); // missing set close ) fail("expected parse error"); } catch (ParseException x) { //ok } try { p.parse(" { 'a' : 4 2 , "); // missing dict close ) fail("expected parse error"); } catch (ParseException x) { //ok } Ast ast = p.parse(" ( 42 , ( 'x', 'y' ) ) "); TupleNode tuple = (TupleNode) ast.root; assertEquals(new IntegerNode(42), tuple.elements.get(0)); tuple = (TupleNode) tuple.elements.get(1); assertEquals(new StringNode("x"), tuple.elements.get(0)); assertEquals(new StringNode("y"), tuple.elements.get(1)); p.parse(" ( 52 , ) "); p.parse(" [ 52 ] "); p.parse(" { 'a' : 42 } "); p.parse(" { 52 } "); } @Test public void TestTuple() { Parser p = new Parser(); TupleNode tuple = new TupleNode(); TupleNode tuple2 = new TupleNode(); assertEquals(tuple, tuple2); tuple.elements.add(new IntegerNode(42)); tuple2.elements.add(new IntegerNode(99)); assertFalse(tuple.equals(tuple2)); tuple2.elements.clear(); tuple2.elements.add(new IntegerNode(42)); assertEquals(tuple, tuple2); tuple2.elements.add(new IntegerNode(43)); tuple2.elements.add(new IntegerNode(44)); assertFalse(tuple.equals(tuple2)); assertEquals(new TupleNode(), p.parse("()").root); assertEquals(tuple, p.parse("(42,)").root); assertEquals(tuple2, p.parse("( 42,43, 44 )").root); } @Test public void TestList() { Parser p = new Parser(); ListNode list = new ListNode(); ListNode list2 = new ListNode(); assertEquals(list, list2); list.elements.add(new IntegerNode(42)); list2.elements.add(new IntegerNode(99)); assertFalse(list.equals(list2)); list2.elements.clear(); list2.elements.add(new IntegerNode(42)); assertEquals(list, list2); list2.elements.add(new IntegerNode(43)); list2.elements.add(new IntegerNode(44)); assertFalse(list.equals(list2)); assertEquals(new ListNode(), p.parse("[]").root); assertEquals(list, p.parse("[42]").root); assertEquals(list2, p.parse("[ 42,43, 44 ]").root); } @Test public void TestSet() { Parser p = new Parser(); SetNode set1 = new SetNode(); SetNode set2 = new SetNode(); assertEquals(set1, set2); set1.elements.add(new IntegerNode(42)); set2.elements.add(new IntegerNode(99)); assertFalse(set1.equals(set2)); set2.elements.clear(); set2.elements.add(new IntegerNode(42)); assertEquals(set1, set2); set2.elements.add(new IntegerNode(43)); set2.elements.add(new IntegerNode(44)); assertFalse(set1.equals(set2)); assertEquals(set1, p.parse("{42}").root); assertEquals(set2, p.parse("{ 42,43, 44 }").root); set1 = (SetNode) p.parse("{'first','second','third','fourth','fifth','second', 'first', 'third', 'third' }").root; assertTrue(set1.elements.contains(new StringNode("first"))); assertTrue(set1.elements.contains(new StringNode("second"))); assertTrue(set1.elements.contains(new StringNode("third"))); assertTrue(set1.elements.contains(new StringNode("fourth"))); assertTrue(set1.elements.contains(new StringNode("fifth"))); assertEquals(5, set1.elements.size()); } @Test public void TestDict() { Parser p = new Parser(); DictNode dict1 = new DictNode(); DictNode dict2 = new DictNode(); assertEquals(dict1, dict2); KeyValueNode kv1 = new KeyValueNode(); kv1.key = new StringNode("key"); kv1.value = new IntegerNode(42); KeyValueNode kv2 = new KeyValueNode(); kv2.key=new StringNode("key"); kv2.value=new IntegerNode(99); assertFalse(kv1.equals(kv2)); kv2.value = new IntegerNode(42); assertEquals(kv1, kv2); kv1=new KeyValueNode(); kv1.key=new StringNode("key1"); kv1.value=new IntegerNode(42); dict1.elements.add(kv1); kv1=new KeyValueNode(); kv1.key=new StringNode("key1"); kv1.value=new IntegerNode(99); dict2.elements.add(kv1); assertFalse(dict1.equals(dict2)); dict2.elements.clear(); kv1=new KeyValueNode(); kv1.key=new StringNode("key1"); kv1.value=new IntegerNode(42); dict2.elements.add(kv1); assertEquals(dict1, dict2); kv1=new KeyValueNode(); kv1.key=new StringNode("key2"); kv1.value=new IntegerNode(43); dict2.elements.add(kv1); kv1=new KeyValueNode(); kv1.key=new StringNode("key3"); kv1.value=new IntegerNode(44); dict2.elements.add(kv1); assertFalse(dict1.equals(dict2)); assertEquals(new DictNode(), p.parse("{}").root); assertEquals(dict1, p.parse("{'key1': 42}").root); assertEquals(dict2, p.parse("{'key1': 42, 'key2': 43, 'key3':44}").root); dict1 = (DictNode) p.parse("{'a': 1, 'b': 2, 'c': 3, 'c': 4, 'c': 5, 'c': 6}").root; kv1 = new KeyValueNode(); kv1.key=new StringNode("c"); kv1.value=new IntegerNode(6); assertTrue(dict1.elements.contains(kv1)); assertEquals(3, dict1.elements.size()); } @Test public void TestKeyValueEquality() { KeyValueNode kv1=new KeyValueNode(); kv1.key=new StringNode("key1"); kv1.value=new IntegerNode(42); KeyValueNode kv2=new KeyValueNode(); kv2.key=new StringNode("key1"); kv2.value=new IntegerNode(42); assertEquals(kv1, kv2); kv2.value=new IntegerNode(43); assertFalse(kv1.equals(kv2)); } @Test public void TestDictEquality() { DictNode dict1 = new DictNode(); KeyValueNode kv=new KeyValueNode(); kv.key=new StringNode("key1"); kv.value=new IntegerNode(42); dict1.elements.add(kv); DictNode dict2 = new DictNode(); kv=new KeyValueNode(); kv.key=new StringNode("key1"); kv.value=new IntegerNode(42); dict2.elements.add(kv); assertEquals(dict1, dict2); kv=new KeyValueNode(); kv.key=new StringNode("key2"); kv.value=new IntegerNode(43); dict1.elements.add(kv); kv=new KeyValueNode(); kv.key=new StringNode("key3"); kv.value=new IntegerNode(44); dict1.elements.add(kv); dict2 = new DictNode(); kv=new KeyValueNode(); kv.key=new StringNode("key2"); kv.value=new IntegerNode(43); dict2.elements.add(kv); kv=new KeyValueNode(); kv.key=new StringNode("key3"); kv.value=new IntegerNode(44); dict2.elements.add(kv); kv=new KeyValueNode(); kv.key=new StringNode("key1"); kv.value=new IntegerNode(42); dict2.elements.add(kv); assertTrue(dict1.equals(dict2)); assertEquals(dict1, dict2); kv=new KeyValueNode(); kv.key=new StringNode("key4"); kv.value=new IntegerNode(45); dict2.elements.add(kv); assertFalse(dict1.equals(dict2)); } @Test public void TestSetEquality() { SetNode set1 = new SetNode(); set1.elements.add(new IntegerNode(1)); set1.elements.add(new IntegerNode(2)); set1.elements.add(new IntegerNode(3)); SetNode set2 = new SetNode(); set2.elements.add(new IntegerNode(2)); set2.elements.add(new IntegerNode(3)); set2.elements.add(new IntegerNode(1)); assertEquals(set1, set2); set2.elements.add(new IntegerNode(0)); assertFalse(set1.equals(set2)); } @Test public void TestFile() throws IOException { Parser p = new Parser(); File testdatafile = new File("src/test/java/testserpent.utf8.bin"); byte[] ser = new byte[(int) testdatafile.length()]; FileInputStream fis=new FileInputStream(testdatafile); DataInputStream dis = new DataInputStream(fis); dis.readFully(ser); dis.close(); fis.close(); Ast ast = p.parse(ser); String expr = ast.toString(); Ast ast2 = p.parse(expr); String expr2 = ast2.toString(); assertEquals(expr.length(), expr2.length()); StringBuilder sb= new StringBuilder(); Walk(ast.root, sb); String walk1 = sb.toString(); sb= new StringBuilder(); Walk(ast2.root, sb); String walk2 = sb.toString(); assertEquals(walk1.length(), walk2.length()); // TODO assertEquals(ast.root, ast2.root); ast = p.parse(expr2); // TODO assertEquals(ast.root, ast2.root); } @Test public void TestTrailingCommas() throws IOException { Parser p = new Parser(); INode result; result = p.parse("[1,2,3, ]").root; result = p.parse("[1,2,3 , ]").root; result = p.parse("[1,2,3,]").root; assertEquals("[1,2,3]", result.toString()); result = p.parse("(1,2,3, )").root; result = p.parse("(1,2,3 , )").root; result = p.parse("(1,2,3,)").root; assertEquals("(1,2,3)", result.toString()); // for dict and set the asserts are a bit more complex // we cannot simply convert to string because the order of elts is undefined. result = p.parse("{'a':1, 'b':2, 'c':3, }").root; result = p.parse("{'a':1, 'b':2, 'c':3 , }").root; result = p.parse("{'a':1, 'b':2, 'c':3,}").root; DictNode dict = (DictNode) result; assertEquals(3, dict.elements.size()); Set elts = dict.elementsAsSet(); assertTrue(elts.contains(new KeyValueNode(new StringNode("a"), new IntegerNode(1)))); assertTrue(elts.contains(new KeyValueNode(new StringNode("b"), new IntegerNode(2)))); assertTrue(elts.contains(new KeyValueNode(new StringNode("c"), new IntegerNode(3)))); result = p.parse("{1,2,3, }").root; result = p.parse("{1,2,3 , }").root; result = p.parse("{1,2,3,}").root; SetNode set = (SetNode) result; assertEquals(3, set.elements.size()); elts = set.elementsAsSet(); assertTrue(elts.contains(new IntegerNode(1))); assertTrue(elts.contains(new IntegerNode(2))); assertTrue(elts.contains(new IntegerNode(3))); } @Test @Ignore("can't get the ast compare to succeed :(") public void TestAstEquals() throws IOException { Parser p = new Parser(); File testdatafile = new File("test/testserpent.utf8.bin"); byte[] ser = new byte[(int) testdatafile.length()]; FileInputStream fis=new FileInputStream(testdatafile); DataInputStream dis = new DataInputStream(fis); dis.readFully(ser); dis.close(); fis.close(); Ast ast = p.parse(ser); Ast ast2 = p.parse(ser); assertEquals(ast.root, ast2.root); // TODO this fails :( } public void Walk(INode node, StringBuilder sb) { if(node instanceof SequenceNode) { sb.append(String.format("%s (seq)\n", node.getClass())); SequenceNode seq = (SequenceNode)node; for(INode child: seq.elements) { Walk(child, sb); } } else sb.append(String.format(" %s\n", node.toString())); } } Serpent-serpent-1.41/java/src/test/java/net/razorvine/serpent/test/SerializationHelperClass.java000066400000000000000000000010111425606146200332160ustar00rootroot00000000000000package net.razorvine.serpent.test; import java.io.Serializable; public class SerializationHelperClass implements Serializable { private static final long serialVersionUID = 5151254868567404093L; public int x; public String s; public int i; public Object obj; public String getTheString() { return s; } public int getTheInteger() { return i; } public boolean isThingy() { return true; } public int getNUMBER() { return 42; } public String getX() { return "X"; } public Object getObject() { return obj; } }Serpent-serpent-1.41/java/src/test/java/net/razorvine/serpent/test/SerializeTest.java000066400000000000000000000571151425606146200310620ustar00rootroot00000000000000/** * Serpent, a Python literal expression serializer/deserializer * (a.k.a. Python's ast.literal_eval in Java) * Software license: "MIT software license". See http://opensource.org/licenses/MIT * @author Irmen de Jong (irmen@razorvine.net) */ package net.razorvine.serpent.test; import static org.junit.Assert.*; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.*; import net.razorvine.serpent.ComplexNumber; import net.razorvine.serpent.IClassSerializer; import net.razorvine.serpent.Parser; import net.razorvine.serpent.Serializer; import net.razorvine.serpent.ast.BytesNode; import org.junit.Test; public class SerializeTest { public byte[] strip_header(byte[] data) { int start; for(start=0; start=data.length) { throw new IllegalArgumentException("need header in string"); } start++; byte[] result = new byte[data.length-start]; System.arraycopy(data, start, result, 0, data.length-start); return result; } public byte[] B(String s) { try { return s.getBytes("utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return null; } } public String S(byte[] b) { try { return new String(b, "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return null; } } @Test public void testHeader() { Serializer ser = new Serializer(); byte[] data = ser.serialize(null); assertEquals(35, data[0]); String strdata = S(data); assertEquals("# serpent utf-8 python3.2", strdata.split("\n")[0]); data = B("# header\nfirst-line"); data = strip_header(data); assertEquals("first-line", S(data)); } @Test public void testException() { Serializer.registerClass(IllegalArgumentException.class, null); Exception x = new IllegalArgumentException("errormessage"); Serializer serpent = new Serializer(true, false); byte[] ser = strip_header(serpent.serialize(x)); assertEquals("{\n '__class__': 'IllegalArgumentException',\n '__exception__': True,\n 'args': (\n 'errormessage',\n ),\n 'attributes': {}\n}", S(ser)); } @Test public void testExceptionPackage() { Serializer.registerClass(IllegalArgumentException.class, null); Exception x = new IllegalArgumentException("errormessage"); Serializer serpent = new Serializer(true, true); byte[] ser = strip_header(serpent.serialize(x)); assertEquals("{\n '__class__': 'java.lang.IllegalArgumentException',\n '__exception__': True,\n 'args': (\n 'errormessage',\n ),\n 'attributes': {}\n}", S(ser)); } @Test public void testStuff() { Serializer ser=new Serializer(); byte[] result = ser.serialize("blerp"); result=strip_header(result); assertEquals("'blerp'", S(result)); result = ser.serialize("\\"); result=strip_header(result); assertEquals("'\\\\'", S(result)); result = ser.serialize(UUID.fromString("f1f8d00e-49a5-4662-ac1d-d5f0426ed293")); result=strip_header(result); assertEquals("'f1f8d00e-49a5-4662-ac1d-d5f0426ed293'", S(result)); result = ser.serialize(new BigDecimal("123456789.987654321987654321987654321987654321")); result=strip_header(result); assertEquals("'123456789.987654321987654321987654321987654321'", S(result)); } @Test public void testNull() { Serializer ser = new Serializer(); byte[] data = ser.serialize(null); data=strip_header(data); assertEquals("None", S(data)); } @Test public void testStrings() { Serializer serpent = new Serializer(); byte[] ser = serpent.serialize("hello"); byte[] data = strip_header(ser); assertEquals("'hello'", S(data)); ser = serpent.serialize("quotes'\""); data = strip_header(ser); assertEquals("'quotes\\'\"'", S(data)); ser = serpent.serialize("quotes2'"); data = strip_header(ser); assertEquals("\"quotes2'\"", S(data)); } @Test public void testUnicodeEscapes() { Serializer serpent=new Serializer(); // regular escaped chars first byte[] ser = serpent.serialize("\b\r\n\f\t \\"); byte[] data = strip_header(ser); // '\\x08\\r\\n\\x0c\\t \\\\' assertArrayEquals(new byte[] {39, 92, 120, 48, 56, 92, 114, 92, 110, 92, 120, 48, 99, 92, 116, 32, 92, 92, 39}, data); // simple cases (chars < 0x80) ser = serpent.serialize("\u0000\u0001\u001f\u007f"); data = strip_header(ser); // '\\x00\\x01\\x1f\\x7f' assertArrayEquals(new byte[] {39, 92, 120, 48, 48, 92, 120, 48, 49, 92, 120, 49, 102, 92, 120, 55, 102, 39 }, data); // chars 0x80 .. 0xff ser = serpent.serialize("\u0080\u0081\u00ff"); data = strip_header(ser); // '\\x80\\x81\xc3\xbf' (has some utf-8 encoded chars in it) assertArrayEquals(new byte[] {39, 92, 120, 56, 48, 92, 120, 56, 49, -61, -65, 39}, data); // chars above 0xff ser = serpent.serialize("\u0100\u20ac\u8899"); data = strip_header(ser); // '\xc4\x80\xe2\x82\xac\xe8\xa2\x99' (has some utf-8 encoded chars in it) assertArrayEquals(new byte[] {39, -60, -128, -30, -126, -84, -24, -94, -103, 39}, data); // some random high chars that are all printable in python and not escaped ser = serpent.serialize("\u0377\u082d\u10c5\u135d\uac00"); data = strip_header(ser); // '\xcd\xb7\xe0\xa0\xad\xe1\x83\x85\xe1\x8d\x9d\xea\xb0\x80' (only a bunch of utf-8 encoded chars) assertArrayEquals(new byte[] {39, -51, -73, -32, -96, -83, -31, -125, -123, -31, -115, -99, -22, -80, -128, 39}, data); // some random high chars that are all non-printable in python and that are escaped ser = serpent.serialize("\u0378\u082e\u10c6\u135c\uabff"); data = strip_header(ser); // '\\u0378\\u082e\\u10c6\\u135c\\uabff' assertArrayEquals(new byte[] {39, 92, 117, 48, 51, 55, 56, 92, 117, 48, 56, 50, 101, 92, 117, 49, 48, 99, 54, 92, 117, 49, 51, 53, 99, 92, 117, 97, 98, 102, 102, 39}, data); } @Test public void testNullByte() { Serializer serpent = new Serializer(); byte[] ser = serpent.serialize("null\u0000byte"); byte[] data = strip_header(ser); assertEquals("'null\\x00byte'", new String(data)); for(byte b: ser) { if(b==0) fail("serialized data may not contain 0-bytes"); } } @Test public void testBool() { Serializer serpent = new Serializer(); byte[] ser = serpent.serialize(true); byte[] data = strip_header(ser); assertEquals("True", S(data)); ser = serpent.serialize(false); data = strip_header(ser); assertEquals("False", S(data)); } @Test public void testBytesDefault() { Serializer serpent = new Serializer(true, false); byte[] bytes = new byte[] { 97, 98, 99, 100, 101, 102 }; // abcdef byte[] ser = serpent.serialize(bytes); assertEquals("{\n 'data': 'YWJjZGVm',\n 'encoding': 'base64'\n}", S(strip_header(ser))); Parser p = new Parser(); String parsed = p.parse(ser).root.toString(); assertEquals(39, parsed.length()); Map dict = new HashMap(); dict.put("data", "YWJjZGVm"); dict.put("encoding", "base64"); byte[] bytes2 = Parser.toBytes(dict); assertArrayEquals(bytes, bytes2); dict.put("encoding", "base99"); try { Parser.toBytes(dict); fail("error expected"); } catch (IllegalArgumentException x) { // } dict.clear(); try { Parser.toBytes(dict); fail("error expected"); } catch (IllegalArgumentException x) { // } dict.clear(); dict.put("data", "YWJjZGVm"); try { Parser.toBytes(dict); fail("error expected"); } catch (IllegalArgumentException x) { // } dict.clear(); dict.put("encoding", "base64"); try { Parser.toBytes(dict); fail("error expected"); } catch (IllegalArgumentException x) { // } try { Parser.toBytes(12345); fail("error expected"); } catch (IllegalArgumentException x) { // } try { Parser.toBytes(null); fail("error expected"); } catch (IllegalArgumentException x) { // } } @Test public void testBytesRepr() { Serializer serpent = new Serializer(true, false, true); byte[] bytes = new byte[] { 97, 98, 99, 100, 101, 102, 0, -1, '\'', '\"' }; // abcdef\x00\xff'" byte[] ser = serpent.serialize(bytes); assertEquals("b'abcdef\\x00\\xff\\'\"'", S(strip_header(ser))); Parser p = new Parser(); BytesNode parsed = (BytesNode) p.parse(ser).root; assertArrayEquals(bytes, parsed.toByteArray()); } @Test public void testDateTime() { Serializer serpent = new Serializer(); Calendar cal = new GregorianCalendar(2013, 0, 20, 23, 59, 45); cal.set(Calendar.MILLISECOND, 999); cal.setTimeZone(TimeZone.getTimeZone("GMT+0")); byte[] ser = strip_header(serpent.serialize(cal)); assertEquals("'2013-01-20T23:59:45.999Z'", S(ser)); Date date = cal.getTime(); ser = strip_header(serpent.serialize(date)); assertEquals("'2013-01-20T23:59:45.999Z'", S(ser)); ser = strip_header(serpent.serialize(date)); assertEquals("'2013-01-20T23:59:45.999Z'", S(ser)); cal.set(Calendar.MILLISECOND, 0); ser = strip_header(serpent.serialize(cal)); assertEquals("'2013-01-20T23:59:45Z'", S(ser)); date = cal.getTime(); ser = strip_header(serpent.serialize(date)); assertEquals("'2013-01-20T23:59:45Z'", S(ser)); } @Test public void testDateTimeWithTimezone() { Serializer serpent = new Serializer(); Calendar cal = new GregorianCalendar(2013, 0, 20, 23, 59, 45); cal.set(Calendar.MILLISECOND, 999); cal.setTimeZone(TimeZone.getTimeZone("Europe/Amsterdam")); byte[] ser = strip_header(serpent.serialize(cal)); assertEquals("'2013-01-20T23:59:45.999+0100'", S(ser)); // normal time cal = new GregorianCalendar(2013, 4, 10, 13, 59, 45); cal.set(Calendar.MILLISECOND, 999); cal.setTimeZone(TimeZone.getTimeZone("Europe/Amsterdam")); ser = strip_header(serpent.serialize(cal)); assertEquals("'2013-05-10T13:59:45.999+0200'", S(ser)); // daylight saving time Date date=cal.getTime(); ser = strip_header(serpent.serialize(date)); assertEquals("'2013-05-10T11:59:45.999Z'", S(ser)); // the date and time in UTC cal.set(Calendar.MILLISECOND, 0); date=cal.getTime(); ser = strip_header(serpent.serialize(date)); assertEquals("'2013-05-10T11:59:45Z'", S(ser)); // the date and time in UTC } @Test public void testNumbers() { Serializer serpent = new Serializer(); byte[] ser = serpent.serialize(12345); byte[] data = strip_header(ser); assertEquals("12345", S(data)); ser = serpent.serialize(1234567891234567891L); data = strip_header(ser); assertEquals("1234567891234567891", S(data)); ser = serpent.serialize(99.1234); data = strip_header(ser); assertEquals("99.1234", S(data)); ser = serpent.serialize(new BigInteger("1234999999999912345678901234567890")); data = strip_header(ser); assertEquals("1234999999999912345678901234567890", S(data)); ser = serpent.serialize(new BigDecimal("123456789.987654321987654321987654321987654321")); data=strip_header(ser); assertEquals("'123456789.987654321987654321987654321987654321'", S(data)); ComplexNumber cplx = new ComplexNumber(2.2, 3.3); ser = serpent.serialize(cplx); data = strip_header(ser); assertEquals("(2.2+3.3j)", S(data)); cplx = new ComplexNumber(0, 3); ser = serpent.serialize(cplx); data = strip_header(ser); assertEquals("(0.0+3.0j)", S(data)); cplx = new ComplexNumber(-2, -3); ser = serpent.serialize(cplx); data = strip_header(ser); assertEquals("(-2.0-3.0j)", S(data)); cplx = new ComplexNumber(-2.5, -3.9); ser = serpent.serialize(cplx); data = strip_header(ser); assertEquals("(-2.5-3.9j)", S(data)); } @Test public void testDoubleNanInf() { Serializer serpent = new Serializer(); Object[] doubles = new Object[] {Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN, new ComplexNumber(Double.POSITIVE_INFINITY, 3.3)}; byte[] ser = serpent.serialize(doubles); byte[] data = strip_header(ser); assertEquals("(1e30000,-1e30000,{'__class__':'float','value':'nan'},1e30000,-1e30000,{'__class__':'float','value':'nan'},(1e30000+3.3j))", S(data)); } @Test public void testList() { Serializer serpent = new Serializer(); List list = new LinkedList(); // test empty list byte[] ser = strip_header(serpent.serialize(list)); assertEquals("[]", S(ser)); serpent.indent=true; ser = strip_header(serpent.serialize(list)); assertEquals("[]", S(ser)); serpent.indent=false; // test nonempty list list.add(42); list.add("Sally"); list.add(16.5); ser = strip_header(serpent.serialize(list)); assertEquals("[42,'Sally',16.5]", S(ser)); serpent.indent=true; ser = strip_header(serpent.serialize(list)); assertEquals("[\n 42,\n 'Sally',\n 16.5\n]", S(ser)); } public class UnserializableClass { } @Test(expected = IllegalArgumentException.class) public void testClassFail() { Serializer serpent = new Serializer(true, false); Object obj = new UnserializableClass(); serpent.serialize(obj); } @Test public void testClassOk() { Serializer.registerClass(SerializationHelperClass.class, null); Serializer serpent = new Serializer(true, false); SerializationHelperClass obj = new SerializationHelperClass(); obj.i=99; obj.s="hi"; obj.x=42; byte[] ser = strip_header(serpent.serialize(obj)); assertEquals("{\n 'NUMBER': 42,\n '__class__': 'SerializationHelperClass',\n 'object': None,\n 'theInteger': 99,\n 'theString': 'hi',\n 'thingy': True,\n 'x': 'X'\n}", S(ser)); } @Test public void testClassPackageOk() { Serializer.registerClass(SerializationHelperClass.class, null); Serializer serpent = new Serializer(true, true); SerializationHelperClass obj = new SerializationHelperClass(); obj.i=99; obj.s="hi"; obj.x=42; byte[] ser = strip_header(serpent.serialize(obj)); assertEquals("{\n 'NUMBER': 42,\n '__class__': 'net.razorvine.serpent.test.SerializationHelperClass',\n 'object': None,\n 'theInteger': 99,\n 'theString': 'hi',\n 'thingy': True,\n 'x': 'X'\n}", S(ser)); } class TestclassConverter implements IClassSerializer { @Override public Map convert(Object obj) { SerializationHelperClass o = (SerializationHelperClass) obj; Map result = new HashMap(); result.put("__class@__", o.getClass().getSimpleName()+"@"); result.put("i@", o.i); result.put("s@", o.s); result.put("x@", o.x); return result; } } class ExceptionConverter implements IClassSerializer { @Override public Map convert(Object obj) { IllegalArgumentException e = (IllegalArgumentException) obj; Map result = new HashMap(); result.put("__class@__", e.getClass().getSimpleName()); result.put("msg@", e.getMessage()); return result; } } @Test public void testCustomClassDict() { Serializer.registerClass(SerializationHelperClass.class, new TestclassConverter()); Serializer serpent = new Serializer(true, false); SerializationHelperClass obj = new SerializationHelperClass(); obj.i=99; obj.s="hi"; obj.x=42; byte[] ser = strip_header(serpent.serialize(obj)); assertEquals("{\n '__class@__': 'SerializationHelperClass@',\n 'i@': 99,\n 's@': 'hi',\n 'x@': 42\n}", S(ser)); } @Test public void testCustomExceptionDict() { Serializer.registerClass(IllegalArgumentException.class, new ExceptionConverter()); Serializer serpent = new Serializer(true, false); Exception x = new IllegalArgumentException("errormessage"); byte[] ser = strip_header(serpent.serialize(x)); assertEquals("{\n '__class@__': 'IllegalArgumentException',\n 'msg@': 'errormessage'\n}", S(ser)); } @Test public void testSet() { Serializer serpent = new Serializer(); Set set = new HashSet(); // test empty set byte[] ser = strip_header(serpent.serialize(set)); assertEquals("()", S(ser)); // empty set is serialized as a tuple. serpent.indent=true; ser = strip_header(serpent.serialize(set)); assertEquals("()", S(ser)); // empty set is serialized as a tuple. serpent.indent=false; // test nonempty set set.add("X"); set.add("Sally"); set.add("Y"); ser = strip_header(serpent.serialize(set)); assertEquals(17, ser.length); assertTrue(S(ser).contains("'Sally'")); assertTrue(S(ser).contains("'X'")); assertTrue(S(ser).contains("'Y'")); serpent.indent=true; ser = strip_header(serpent.serialize(set)); assertEquals("{\n 'Sally',\n 'X',\n 'Y'\n}", S(ser)); } @Test public void testCollection() { Collection intlist = new LinkedList(); intlist.add(42); intlist.add(43); Serializer serpent = new Serializer(); byte[] ser = serpent.serialize(intlist); ser = strip_header(ser); assertEquals("[42,43]", S(ser)); ser=strip_header(serpent.serialize(new int[] {42})); assertEquals("(42,)", S(ser)); ser=strip_header(serpent.serialize(new int[] {42, 43})); assertEquals("(42,43)", S(ser)); serpent.indent=true; ser = strip_header(serpent.serialize(intlist)); assertEquals("[\n 42,\n 43\n]", S(ser)); ser=strip_header(serpent.serialize(new int[] {42})); assertEquals("(\n 42,\n)", S(ser)); ser=strip_header(serpent.serialize(new int[] {42, 43})); assertEquals("(\n 42,\n 43\n)", S(ser)); } @Test public void testDictionary() { Serializer serpent = new Serializer(); Parser p = new Parser(); // test empty dict Hashtable ht = new Hashtable(); byte[] ser = serpent.serialize(ht); assertEquals("{}", S(strip_header(ser))); String parsed = p.parse(ser).root.toString(); assertEquals("{}", parsed); //empty dict with indentation serpent.indent=true; ser = serpent.serialize(ht); assertEquals("{}", S(strip_header(ser))); parsed = p.parse(ser).root.toString(); assertEquals("{}", parsed); // test dict with values serpent.indent=false; ht = new Hashtable(); ht.put(42, "fortytwo"); ht.put("sixteen-and-half", 16.5); ht.put("name", "Sally"); ht.put("status", false); ser = serpent.serialize(ht); assertEquals('}', ser[ser.length-1]); assertTrue(ser[ser.length-2]!=','); parsed = p.parse(ser).root.toString(); assertEquals(69, parsed.length()); // test indentation serpent.indent=true; ser = serpent.serialize(ht); assertEquals('}', ser[ser.length-1]); assertEquals('\n', ser[ser.length-2]); assertTrue(ser[ser.length-3]!=','); String ser_str = S(strip_header(ser)); assertTrue(ser_str.contains("'name': 'Sally'")); assertTrue(ser_str.contains("'status': False")); assertTrue(ser_str.contains("42: 'fortytwo'")); assertTrue(ser_str.contains("'sixteen-and-half': 16.5")); parsed = p.parse(ser).root.toString(); assertEquals(69, parsed.length()); serpent.indent=false; // generic Dictionary test Map mydict = new HashMap(); mydict.put(1, "one"); mydict.put(2, "two"); ser = serpent.serialize(mydict); ser_str = S(strip_header(ser)); assertTrue(ser_str.equals("{2:'two',1:'one'}") || ser_str.equals("{1:'one',2:'two'}")); } @Test public void testIndentation() { Map dict = new HashMap(); List list = new LinkedList(); list.add(1); list.add(2); list.add(new String[] {"a", "b"}); dict.put("first", list); Map subdict = new HashMap(); subdict.put(1, false); dict.put("second", subdict); Set subset = new HashSet(); subset.add(3); subset.add(4); dict.put("third", subset); Serializer serpent = new Serializer(); serpent.indent=true; byte[] ser = strip_header(serpent.serialize(dict)); assertEquals("{\n"+ " 'first': [\n"+ " 1,\n"+ " 2,\n"+ " (\n"+ " 'a',\n"+ " 'b'\n"+ " )\n"+ " ],\n"+ " 'second': {\n"+ " 1: False\n"+ " },\n"+ " 'third': {\n"+ " 3,\n"+ " 4\n"+ " }\n"+ "}", S(ser)); } @Test public void testSorting() { Serializer serpent=new Serializer(); ArrayList data1 = new ArrayList(); data1.add(3); data1.add(2); data1.add(1); byte[] ser = strip_header(serpent.serialize(data1)); assertEquals("[3,2,1]", S(ser)); int[] data2 = new int[] { 3,2,1 }; ser = strip_header(serpent.serialize(data2)); assertEquals("(3,2,1)", S(ser)); Set data3 = new HashSet(); data3.add(42); data3.add("hi"); serpent.indent=true; ser = strip_header(serpent.serialize(data3)); assertTrue(S(ser).equals("{\n 42,\n 'hi'\n}") || S(ser).equals("{\n 'hi',\n 42\n}")); Map data4 = new HashMap(); data4.put(5, "five"); data4.put(3, "three"); data4.put(1, "one"); data4.put(4, "four"); data4.put(2, "two"); serpent.indent=true; ser = strip_header(serpent.serialize(data4)); assertEquals("{\n 1: 'one',\n 2: 'two',\n 3: 'three',\n 4: 'four',\n 5: 'five'\n}", S(ser)); Set data5 = new HashSet(); data5.add("x"); data5.add("y"); data5.add("z"); data5.add("c"); data5.add("b"); data5.add("a"); serpent.indent=true; ser = strip_header(serpent.serialize(data5)); assertEquals("{\n 'a',\n 'b',\n 'c',\n 'x',\n 'y',\n 'z'\n}", S(ser)); } interface IBaseInterface {} interface ISubInterface extends IBaseInterface {} class BaseClassWithInterface implements IBaseInterface, Serializable {} class SubClassWithInterface extends BaseClassWithInterface implements ISubInterface, Serializable {} class BaseClass implements Serializable {} class SubClass extends BaseClass implements Serializable {} abstract class AbstractBaseClass {} class ConcreteSubClass extends AbstractBaseClass implements Serializable {} class AnyClassConverter implements IClassSerializer { @Override public Map convert(Object obj) { Map result = new HashMap(); result.put("(SUB)CLASS", obj.getClass().getSimpleName()); return result; } } @Test public void testAbstractBaseClassHierarchyPickler() { ConcreteSubClass c = new ConcreteSubClass(); Serializer serpent=new Serializer(); byte[] data = serpent.serialize(c); assertEquals("{'__class__':'ConcreteSubClass'}", S(strip_header(data))); // the default serializer Serializer.registerClass(AbstractBaseClass.class, new AnyClassConverter()); data = serpent.serialize(c); assertEquals("{'(SUB)CLASS':'ConcreteSubClass'}", S(strip_header(data))); // custom serializer } @Test public void testInterfaceHierarchyPickler() { BaseClassWithInterface b = new BaseClassWithInterface(); SubClassWithInterface sub = new SubClassWithInterface(); Serializer serpent=new Serializer(); byte[] data = serpent.serialize(b); assertEquals("{'__class__':'BaseClassWithInterface'}", S(strip_header(data))); // the default serializer data = serpent.serialize(sub); assertEquals("{'__class__':'SubClassWithInterface'}", S(strip_header(data))); // the default serializer Serializer.registerClass(IBaseInterface.class, new AnyClassConverter()); data = serpent.serialize(b); assertEquals("{'(SUB)CLASS':'BaseClassWithInterface'}", S(strip_header(data))); // custom serializer data = serpent.serialize(sub); assertEquals("{'(SUB)CLASS':'SubClassWithInterface'}", S(strip_header(data))); // custom serializer } } Serpent-serpent-1.41/java/src/test/java/net/razorvine/serpent/test/SlowPerformanceTest.java000066400000000000000000000033561425606146200322370ustar00rootroot00000000000000package net.razorvine.serpent.test; import java.io.IOException; import net.razorvine.serpent.Parser; import net.razorvine.serpent.Serializer; import org.junit.Ignore; import org.junit.Test; public class SlowPerformanceTest { // tests some performance regressions when they occur @Test @Ignore public void TestManyFloats() throws IOException { // System.out.println("enter to start manyfloats"); // System.in.read(); int amount = 500000; double[] array = new double[amount]; for(int i=0; i dict = (Map) thing; assertEquals(11, dict.size()); List list = (List) dict.get("numbers"); assertEquals(4, list.size()); assertEquals(999.1234, list.get(1)); assertEquals(new ComplexNumber(-3, 8), list.get(3)); String euro = (String) dict.get("unicode"); assertEquals("\u20ac", euro); Map exc = (Map) dict.get("exc"); Object[] args = (Object[]) exc.get("args"); assertEquals("fault", args[0]); assertEquals("ZeroDivisionError", exc.get("__class__")); } class ArithmeticExcFromDict implements IDictToInstance { public Object convert(Map dict) { String classname = (String) dict.get("__class__"); if("ZeroDivisionError".equals(classname)) { Object[] args = (Object[]) dict.get("args"); return new ArithmeticException((String)args[0]); } return null; } } @SuppressWarnings("unchecked") @Test public void testObjectifyDictToClass() throws IOException { Parser p = new Parser(); File testdatafile = new File("src/test/java/testserpent.utf8.bin"); byte[] ser = new byte[(int) testdatafile.length()]; FileInputStream fis=new FileInputStream(testdatafile); DataInputStream dis = new DataInputStream(fis); dis.readFully(ser); dis.close(); fis.close(); Ast ast = p.parse(ser); ObjectifyVisitor visitor = new ObjectifyVisitor(new ArithmeticExcFromDict()); ast.accept(visitor); Object thing = visitor.getObject(); Map dict = (Map) thing; assertEquals(11, dict.size()); ArithmeticException exc = (ArithmeticException) dict.get("exc"); assertEquals("fault", exc.getMessage()); } } Serpent-serpent-1.41/java/src/test/java/testserpent.utf8.bin000066400000000000000000000022361425606146200241150ustar00rootroot00000000000000# serpent utf-8 python3.3 { # serpent supports comments in the serialized data 'bytes': { 'data': 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', 'encoding': 'base64' }, 'dates': [ # test some dates. '2013-01-26T03:32:39.007001', '23:59:45.999888', 500.0 ], 'dict': { 0: '0000', 1: '1111', 2: '2222', 3: '3333', 4: '4444', 5: '5555', 6: '6666', 7: '7777', 8: '8888', 9: '9999' }, 'exc': { '__class__': 'ZeroDivisionError', '__exception__': True, # this is an exception 'args': ( 'fault', ), 'attributes': {} }, 'list': [ 1, 2, 3, 4, 5, 6, 7, 8 ], 'numbers': [ 12345678901234567890123456, 999.1234, '1.99999999999999999991', (-3+8j) ], 'set': { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 'str': 'hello', 'tuple': ( 1, 2, 3, 4, 5, 6, 7, 8 ), 'unicode': '€', # the feared unicode! 'uuid': '57e12d82-511f-456b-ab65-f765144c6a90' }Serpent-serpent-1.41/serpent.py000066400000000000000000000472621425606146200166100ustar00rootroot00000000000000""" ast.literal_eval() compatible object tree serialization. Serpent serializes an object tree into bytes (utf-8 encoded string) that can be decoded and then passed as-is to ast.literal_eval() to rebuild it as the original object tree. As such it is safe to send serpent data to other machines over the network for instance (because only 'safe' literals are encoded). Compatible with recent Python 3 versions Serpent handles several special Python types to make life easier: - bytes, bytearrays, memoryview --> string, base-64 (you'll have to manually un-base64 them though) - uuid.UUID, datetime.{datetime, date, time, timespan} --> appropriate string/number - decimal.Decimal --> string (to not lose precision) - array.array typecode 'u' --> string - array.array other typecode --> list - Exception --> dict with some fields of the exception (message, args) - collections module types --> mostly equivalent primitive types or dict - enums --> the value of the enum - all other types --> dict with __getstate__ or vars() of the object Notes: The serializer is not thread-safe. Make sure you're not making changes to the object tree that is being serialized, and don't use the same serializer in different threads. Because the serialized format is just valid Python source code, it can contain comments. Floats +inf and -inf are handled via a trick, Float 'nan' cannot be handled and is represented by the special value: {'__class__':'float','value':'nan'} We chose not to encode it as just the string 'NaN' because that could cause memory issues when used in multiplications. Copyright by Irmen de Jong (irmen@razorvine.net) Software license: "MIT software license". See http://opensource.org/licenses/MIT """ import ast import base64 import sys import gc import decimal import datetime import uuid import array import math import numbers import codecs import collections import enum from collections.abc import KeysView, ValuesView, ItemsView __version__ = "1.41" __all__ = ["dump", "dumps", "load", "loads", "register_class", "unregister_class", "tobytes"] def dumps(obj, indent=False, module_in_classname=False, bytes_repr=False): """ Serialize object tree to bytes. indent = indent the output over multiple lines (default=false) module_in_classname = include module prefix for class names or only use the class name itself bytes_repr = should the bytes literal value representation be used instead of base-64 encoding for bytes types? """ return Serializer(indent, module_in_classname, bytes_repr).serialize(obj) def dump(obj, file, indent=False, module_in_classname=False, bytes_repr=False): """ Serialize object tree to a file. indent = indent the output over multiple lines (default=false) module_in_classname = include module prefix for class names or only use the class name itself bytes_repr = should the bytes literal value representation be used instead of base-64 encoding for bytes types? """ file.write(dumps(obj, indent=indent, module_in_classname=module_in_classname, bytes_repr=bytes_repr)) def loads(serialized_bytes): """Deserialize bytes back to object tree. Uses ast.literal_eval (safe).""" serialized = codecs.decode(serialized_bytes, "utf-8") if '\x00' in serialized: raise ValueError( "The serpent data contains 0-bytes so it cannot be parsed by ast.literal_eval. Has it been corrupted?") try: gc.disable() return ast.literal_eval(serialized) finally: gc.enable() def load(file): """Deserialize bytes from a file back to object tree. Uses ast.literal_eval (safe).""" data = file.read() return loads(data) def _ser_OrderedDict(obj, serializer, outputstream, indentlevel): obj = { "__class__": "collections.OrderedDict" if serializer.module_in_classname else "OrderedDict", "items": list(obj.items()) } serializer._serialize(obj, outputstream, indentlevel) def _ser_DictView(obj, serializer, outputstream, indentlevel): serializer.ser_builtins_list(obj, outputstream, indentlevel) _special_classes_registry = collections.OrderedDict() # must be insert-order preserving to make sure of proper precedence rules def _reset_special_classes_registry(): _special_classes_registry.clear() _special_classes_registry[KeysView] = _ser_DictView _special_classes_registry[ValuesView] = _ser_DictView _special_classes_registry[ItemsView] = _ser_DictView _special_classes_registry[collections.OrderedDict] = _ser_OrderedDict def _ser_Enum(obj, serializer, outputstream, indentlevel): serializer._serialize(obj.value, outputstream, indentlevel) _special_classes_registry[enum.Enum] = _ser_Enum _reset_special_classes_registry() def unregister_class(clazz): """Unregister the specialcase serializer for the given class.""" if clazz in _special_classes_registry: del _special_classes_registry[clazz] def register_class(clazz, serializer): """ Register a special serializer function for objects of the given class. The function will be called with (object, serpent_serializer, outputstream, indentlevel) arguments. The function must write the serialized data to outputstream. It doesn't return a value. """ _special_classes_registry[clazz] = serializer _repr_types = {str, int, bool, type(None)} _translate_types = { collections.deque: list, collections.UserDict: dict, collections.UserList: list, collections.UserString: str } _bytes_types = (bytes, bytearray, memoryview) def _translate_byte_type(t, data, bytes_repr): if bytes_repr: if t == bytes: return repr(data) elif t == bytearray: return repr(bytes(data)) elif t == memoryview: return repr(bytes(data)) else: raise TypeError("invalid bytes type") else: b64 = base64.b64encode(data) return repr({ "data": b64 if type(b64) is str else b64.decode("ascii"), "encoding": "base64" }) def tobytes(obj): """ Utility function to convert obj back to actual bytes if it is a serpent-encoded bytes dictionary (a dict with base-64 encoded 'data' in it and 'encoding'='base64'). If obj is already bytes or a byte-like type, return obj unmodified. Will raise TypeError if obj is none of the above. All this is not required if you called serpent with 'bytes_repr' set to True, since Serpent 1.40 that can be used to directly encode bytes into the bytes literal value representation. That will be less efficient than the default base-64 encoding though, but it's a bit more convenient. """ if isinstance(obj, _bytes_types): return obj if isinstance(obj, dict) and "data" in obj and obj.get("encoding") == "base64": try: return base64.b64decode(obj["data"]) except TypeError: return base64.b64decode(obj["data"].encode("ascii")) # needed for certain older versions of pypy raise TypeError("argument is neither bytes nor serpent base64 encoded bytes dict") class Serializer(object): """ Serialize an object tree to a byte stream. It is not thread-safe: make sure you're not making changes to the object tree that is being serialized, and don't use the same serializer across different threads. """ dispatch = {} def __init__(self, indent=False, module_in_classname=False, bytes_repr=False): """ Initialize the serializer. indent=indent the output over multiple lines (default=false) module_in_classname = include module prefix for class names or only use the class name itself bytes_repr = should the bytes literal value representation be used instead of base-64 encoding for bytes types? """ self.indent = indent self.module_in_classname = module_in_classname self.serialized_obj_ids = set() self.special_classes_registry_copy = None self.maximum_level = min(sys.getrecursionlimit() // 5, 1000) self.bytes_repr = bytes_repr def serialize(self, obj): """Serialize the object tree to bytes.""" self.special_classes_registry_copy = _special_classes_registry.copy() # make it thread safe header = "# serpent utf-8 python3.2\n" out = [header] try: gc.disable() self.serialized_obj_ids = set() self._serialize(obj, out, 0) finally: gc.enable() self.special_classes_registry_copy = None del self.serialized_obj_ids return "".join(out).encode("utf-8") _shortcut_dispatch_types = {float, complex, tuple, list, dict, set, frozenset} def _serialize(self, obj, out, level): if level > self.maximum_level: raise ValueError( "Object graph nesting too deep. Increase serializer.maximum_level if you think you need more, " " but this may cause a RecursionError instead if Python's recursion limit doesn't allow it.") t = type(obj) if t in _bytes_types: out.append(_translate_byte_type(t, obj, self.bytes_repr)) return if t in _translate_types: obj = _translate_types[t](obj) t = type(obj) if t in _repr_types: out.append(repr(obj)) # just a simple repr() is enough for these objects return if t in self._shortcut_dispatch_types: # we shortcut these builtins directly to the dispatch function to avoid type lookup overhead below return self.dispatch[t](self, obj, out, level) # check special registered types: special_classes = self.special_classes_registry_copy for clazz in special_classes: if isinstance(obj, clazz): special_classes[clazz](obj, self, out, level) return # serialize dispatch try: func = self.dispatch[t] except KeyError: # walk the MRO until we find a base class we recognise for type_ in t.__mro__: if type_ in self.dispatch: func = self.dispatch[type_] break else: # fall back to the default class serializer func = Serializer.ser_default_class func(self, obj, out, level) def ser_builtins_float(self, float_obj, out, level): if math.isnan(float_obj): # there's no literal expression for a float NaN... out.append("{'__class__':'float','value':'nan'}") elif math.isinf(float_obj): # output a literal expression that overflows the float and results in +/-INF if float_obj > 0: out.append("1e30000") else: out.append("-1e30000") else: out.append(repr(float_obj)) dispatch[float] = ser_builtins_float def ser_builtins_complex(self, complex_obj, out, level): out.append("(") self.ser_builtins_float(complex_obj.real, out, level) if complex_obj.imag >= 0: out.append("+") self.ser_builtins_float(complex_obj.imag, out, level) out.append("j)") dispatch[complex] = ser_builtins_complex def ser_builtins_tuple(self, tuple_obj, out, level): append = out.append serialize = self._serialize if self.indent and tuple_obj: indent_chars = " " * level indent_chars_inside = indent_chars + " " append("(\n") for elt in tuple_obj: append(indent_chars_inside) serialize(elt, out, level + 1) append(",\n") out[-1] = out[-1].rstrip() # remove the last \n if len(tuple_obj) > 1: del out[-1] # undo the last , append("\n" + indent_chars + ")") else: append("(") for elt in tuple_obj: serialize(elt, out, level + 1) append(",") if len(tuple_obj) > 1: del out[-1] # undo the last , append(")") dispatch[tuple] = ser_builtins_tuple def ser_builtins_list(self, list_obj, out, level): if id(list_obj) in self.serialized_obj_ids: raise ValueError("Circular reference detected (list)") self.serialized_obj_ids.add(id(list_obj)) append = out.append serialize = self._serialize if self.indent and list_obj: indent_chars = " " * level indent_chars_inside = indent_chars + " " append("[\n") for elt in list_obj: append(indent_chars_inside) serialize(elt, out, level + 1) append(",\n") del out[-1] # remove the last ,\n append("\n" + indent_chars + "]") else: append("[") for elt in list_obj: serialize(elt, out, level + 1) append(",") if list_obj: del out[-1] # remove the last , append("]") self.serialized_obj_ids.discard(id(list_obj)) dispatch[list] = ser_builtins_list def _check_hashable_type(self, t): if t not in (bool, bytes, str, tuple) and not issubclass(t, numbers.Number): if issubclass(t, enum.Enum): return raise TypeError("one of the keys in a dict or set is not of a primitive hashable type: " + str(t) + ". Use simple types as keys or use a list or tuple as container.") def ser_builtins_dict(self, dict_obj, out, level): if id(dict_obj) in self.serialized_obj_ids: raise ValueError("Circular reference detected (dict)") self.serialized_obj_ids.add(id(dict_obj)) append = out.append serialize = self._serialize if self.indent and dict_obj: indent_chars = " " * level indent_chars_inside = indent_chars + " " append("{\n") dict_items = dict_obj.items() try: sorted_items = sorted(dict_items) except TypeError: # can occur when elements can't be ordered (Python 3.x) sorted_items = dict_items for key, value in sorted_items: append(indent_chars_inside) self._check_hashable_type(type(key)) serialize(key, out, level + 1) append(": ") serialize(value, out, level + 1) append(",\n") del out[-1] # remove last ,\n append("\n" + indent_chars + "}") else: append("{") for key, value in dict_obj.items(): self._check_hashable_type(type(key)) serialize(key, out, level + 1) append(":") serialize(value, out, level + 1) append(",") if dict_obj: del out[-1] # remove the last , append("}") self.serialized_obj_ids.discard(id(dict_obj)) dispatch[dict] = ser_builtins_dict def ser_builtins_set(self, set_obj, out, level): append = out.append serialize = self._serialize if self.indent and set_obj: indent_chars = " " * level indent_chars_inside = indent_chars + " " append("{\n") try: sorted_elts = sorted(set_obj) except TypeError: # can occur when elements can't be ordered (Python 3.x) sorted_elts = set_obj for elt in sorted_elts: append(indent_chars_inside) self._check_hashable_type(type(elt)) serialize(elt, out, level + 1) append(",\n") del out[-1] # remove the last ,\n append("\n" + indent_chars + "}") elif set_obj: append("{") for elt in set_obj: self._check_hashable_type(type(elt)) serialize(elt, out, level + 1) append(",") del out[-1] # remove the last , append("}") else: # empty set literal doesn't exist unfortunately, replace with empty tuple self.ser_builtins_tuple((), out, level) dispatch[set] = ser_builtins_set def ser_builtins_frozenset(self, set_obj, out, level): self.ser_builtins_set(set_obj, out, level) dispatch[frozenset] = ser_builtins_set def ser_decimal_Decimal(self, decimal_obj, out, level): # decimal is serialized as a string to avoid losing precision out.append(repr(str(decimal_obj))) dispatch[decimal.Decimal] = ser_decimal_Decimal def ser_datetime_datetime(self, datetime_obj, out, level): out.append(repr(datetime_obj.isoformat())) dispatch[datetime.datetime] = ser_datetime_datetime def ser_datetime_date(self, date_obj, out, level): out.append(repr(date_obj.isoformat())) dispatch[datetime.date] = ser_datetime_date def ser_datetime_timedelta(self, timedelta_obj, out, level): secs = timedelta_obj.total_seconds() out.append(repr(secs)) dispatch[datetime.timedelta] = ser_datetime_timedelta def ser_datetime_time(self, time_obj, out, level): out.append(repr(str(time_obj))) dispatch[datetime.time] = ser_datetime_time def ser_uuid_UUID(self, uuid_obj, out, level): out.append(repr(str(uuid_obj))) dispatch[uuid.UUID] = ser_uuid_UUID def ser_exception_class(self, exc_obj, out, level): value = { "__class__": self.get_class_name(exc_obj), "__exception__": True, "args": exc_obj.args, "attributes": vars(exc_obj) # add any custom attributes } self._serialize(value, out, level) dispatch[BaseException] = ser_exception_class def ser_array_array(self, array_obj, out, level): if array_obj.typecode == 'u': self._serialize(array_obj.tounicode(), out, level) else: self._serialize(array_obj.tolist(), out, level) dispatch[array.array] = ser_array_array def ser_default_class(self, obj, out, level): if id(obj) in self.serialized_obj_ids: raise ValueError("Circular reference detected (class)") self.serialized_obj_ids.add(id(obj)) try: # note: python 3.11+ object itself now has __getstate__ has_own_getstate = ( hasattr(type(obj), '__getstate__') and type(obj).__getstate__ is not getattr(object, '__getstate__', None) ) if has_own_getstate: value = obj.__getstate__() if isinstance(value, dict): self.ser_builtins_dict(value, out, level) return else: try: value = dict(vars(obj)) # make sure we can serialize anything that resembles a dict value["__class__"] = self.get_class_name(obj) except TypeError: if hasattr(obj, "__slots__"): # use the __slots__ instead of the vars dict value = {} for slot in obj.__slots__: value[slot] = getattr(obj, slot) value["__class__"] = self.get_class_name(obj) else: raise TypeError("don't know how to serialize class " + str(obj.__class__) + ". Give it vars() or an appropriate __getstate__") self._serialize(value, out, level) finally: self.serialized_obj_ids.discard(id(obj)) def get_class_name(self, obj): if self.module_in_classname: return "%s.%s" % (obj.__class__.__module__, obj.__class__.__name__) else: return obj.__class__.__name__ Serpent-serpent-1.41/setup.cfg000066400000000000000000000003431425606146200163640ustar00rootroot00000000000000[wheel] universal = 0 [bdist_rpm] doc_files = LICENSE docs/ [pycodestyle] max-line-length = 140 ignore = E402,E731 exclude = .git,__pycache__,.tox,docs,tests,build,dist,examples [metadata] license_file = LICENSE Serpent-serpent-1.41/setup.py000077500000000000000000000147401425606146200162660ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Serpent: ast.literal_eval() compatible object tree serialization. # Software license: "MIT software license". See http://opensource.org/licenses/MIT try: from setuptools import setup using_setuptools = True except ImportError: from distutils.core import setup using_setuptools = False import unittest import re import sys serpent_version = re.search(r'^__version__\s*=\s*"(.+)"', open("serpent.py", "rt").read(), re.MULTILINE).groups()[0] def serpent_test_suite(): testloader = unittest.TestLoader() testsuite = testloader.discover("tests", pattern="test*.py") return testsuite setup( name='serpent', version=serpent_version, py_modules=["serpent"], python_requires='>=3.2', license='MIT', author='Irmen de Jong', author_email='irmen@razorvine.net', url='https://github.com/irmen/Serpent', description='Serialization based on ast.literal_eval', long_description=""" Serpent is a simple serialization library based on ast.literal_eval. Because it only serializes literals and recreates the objects using ast.literal_eval(), the serialized data is safe to transport to other machines (over the network for instance) and de-serialize it there. *There is also a Java and a .NET (C#) implementation available. This allows for easy data transfer between the various ecosystems. You can get the full source distribution, a Java .jar file, and a .NET assembly dll.* The java library can be obtained from Maven central (groupid ``net.razorvine`` artifactid ``serpent``), and the .NET assembly can be obtained from Nuget.org (package ``Razorvine.Serpent``). **API** - ``ser_bytes = serpent.dumps(obj, indent=False, module_in_classname=False):`` # serialize obj tree to bytes - ``obj = serpent.loads(ser_bytes)`` # deserialize bytes back into object tree - You can use ``ast.literal_eval`` yourself to deserialize, but ``serpent.deserialize`` works around a few corner cases. See source for details. Serpent is more sophisticated than a simple repr() + literal_eval(): - it serializes directly to bytes (utf-8 encoded), instead of a string, so it can immediately be saved to a file or sent over a socket - it encodes byte-types as base-64 instead of inefficient escaping notation that repr would use (this does mean you have to base-64 decode these strings manually on the receiving side to get your bytes back. You can use the serpent.tobytes utility function for this.) - it contains a few custom serializers for several additional Python types such as uuid, datetime, array and decimal - it tries to serialize unrecognised types as a dict (you can control this with __getstate__ on your own types) - it can create a pretty-printed (indented) output for readability purposes - it outputs the keys of sets and dicts in alphabetical order (when pretty-printing) - it works around a few quirks of ast.literal_eval() on the various Python implementations Serpent allows comments in the serialized data (because it is just Python source code). Serpent can't serialize object graphs (when an object refers to itself); it will then crash with a ValueError pointing out the problem. Works with Python 3 recent versions. **FAQ** - Why not use XML? Answer: because XML. - Why not use JSON? Answer: because JSON is quite limited in the number of datatypes it supports, and you can't use comments in a JSON file. - Why not use pickle? Answer: because pickle has security problems. - Why not use ``repr()``/``ast.literal_eval()``? See above; serpent is a superset of this and provides more convenience. Serpent provides automatic serialization mappings for types other than the builtin primitive types. ``repr()`` can't serialize these to literals that ``ast.literal_eval()`` understands. - Why not a binary format? Answer: because binary isn't readable by humans. - But I don't care about readability. Answer: doesn't matter, ``ast.literal_eval()`` wants a literal string, so that is what we produce. - But I want better performance. Answer: ok, maybe you shouldn't use serpent in this case. Find an efficient binary protocol (protobuf?) - Why only Python, Java and C#/.NET, but no bindings for insert-favorite-language-here? Answer: I don't speak that language. Maybe you could port serpent yourself? - Where is the source? It's on Github: https://github.com/irmen/Serpent - Can I use it everywhere? Sure, as long as you keep the copyright and disclaimer somewhere. See the LICENSE file. **Demo** .. code:: python # -*- coding: utf-8 -*- import ast import uuid import datetime import pprint import serpent class DemoClass: def __init__(self): self.i=42 self.b=False data = { "names": ["Harry", "Sally", "Peter"], "big": 2**200, "colorset": { "red", "green" }, "id": uuid.uuid4(), "timestamp": datetime.datetime.now(), "class": DemoClass(), "unicode": "€" } # serialize it ser = serpent.dumps(data, indent=True) open("data.serpent", "wb").write(ser) print("Serialized form:") print(ser.decode("utf-8")) # read it back data = serpent.load(open("data.serpent", "rb")) print("Data:") pprint.pprint(data) # you can also use ast.literal_eval if you like ser_string = open("data.serpent", "r", encoding="utf-8").read() data2 = ast.literal_eval(ser_string) assert data2==data When you run this it prints: .. code:: python Serialized form: # serpent utf-8 python3.2 { 'big': 1606938044258990275541962092341162602522202993782792835301376, 'class': { '__class__': 'DemoClass', 'b': False, 'i': 42 }, 'colorset': { 'green', 'red' }, 'id': 'e461378a-201d-4844-8119-7c1570d9d186', 'names': [ 'Harry', 'Sally', 'Peter' ], 'timestamp': '2013-04-02T00:23:00.924000', 'unicode': '€' } Data: {'big': 1606938044258990275541962092341162602522202993782792835301376, 'class': {'__class__': 'DemoClass', 'b': False, 'i': 42}, 'colorset': {'green', 'red'}, 'id': 'e461378a-201d-4844-8119-7c1570d9d186', 'names': ['Harry', 'Sally', 'Peter'], 'timestamp': '2013-04-02T00:23:00.924000', 'unicode': '€'} """, keywords="serialization", platforms="any", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Software Development" ], test_suite="setup.serpent_test_suite" ) Serpent-serpent-1.41/syntax.bnf000066400000000000000000000027751425606146200165730ustar00rootroot00000000000000# BNF-ish syntax of a Serpent-serialized data string. expr = single | compound . single = int | float | complex | string | bool | none . compound = tuple | dict | list | set . digit = '0'...'9' . digitnonzero = '1'...'9' . int = ['-'] digitnonzero {digit} . float = pointfloat | exponentfloat . pointfloat = [int] fraction . fraction = '.' digit { digit } . exponentfloat = (int | pointfloat) exponent . exponent = ("e" | "E") ["+" | "-"] digit { digit } . complex = complextuple | imaginary . imaginary = ['+' | '-' ] ( float | int ) 'j' . complextuple = '(' ( float | int ) imaginary ')' . string = singlequoted | doublequoted . singlequoted = '\'' stringvalue_escaped_singlequoted '\'' . doublequoted = '"' stringvalue_escaped_doublequotes '"' . bool = 'True' | 'False' . none = 'None' . expr_list = expr { ',' expr } trailingcomma . tuple = tuple_empty | tuple_one | tuple_more tuple_empty = '()' . tuple_one = '(' expr ',' ')' . tuple_more = '(' expr_list ')' . trailingcomma = '' | ',' list = list_empty | list_nonempty . list_empty = '[]' . list_nonempty = '[' expr_list ']' . set = '{' expr_list '}' . dict = '{' keyvalue_list '}' . keyvalue_list = keyvalue { ',' keyvalue } trailingcomma . keyvalue = expr ':' expr . Serpent-serpent-1.41/tests/000077500000000000000000000000001425606146200157055ustar00rootroot00000000000000Serpent-serpent-1.41/tests/example.py000066400000000000000000000017101425606146200177110ustar00rootroot00000000000000import datetime import serpent class CustomClass(object): def __init__(self, name, age): self.name = name self.age = age def example(): data = { "tuple": (1, 2, 3), "date": datetime.datetime.now(), "set": {'a', 'b', 'c'}, "class": CustomClass("Sally", 26) } # serialize the object ser = serpent.dumps(data, indent=True) # print it to the screen, but usually you'd save the bytes to a file or transfer them over a network connection print("Serialized data:") print(ser.decode("UTF-8")) # deserialize the bytes and print the objects obj = serpent.loads(ser) print("Deserialized data:") print("tuple:", obj["tuple"]) print("date:", obj["date"]) print("set:", obj["set"]) clazz = obj["class"] print("class attributes: type={0} name={1} age={2}".format( clazz["__class__"], clazz["name"], clazz["age"])) if __name__ == "__main__": example() Serpent-serpent-1.41/tests/performance.py000066400000000000000000000144071425606146200205660ustar00rootroot00000000000000""" Prints a comparison between different serializers. Compares results based on size of the output, and time taken to (de)serialize. """ from timeit import default_timer as perf_timer import sys import datetime import decimal import uuid class Person(object): def __init__(self, name, age): self.name = name self.age = age guid = uuid.uuid4() data = { "bytes": bytes(x for x in range(256)) * 300, "bytearray": bytearray(x for x in range(256)) * 300, "str": "\"0123456789\"\n'abcdefghijklmnopqrstuvwxyz'\t" * 2000, "unicode": u"abcdefghijklmnopqrstuvwxyz\u20ac\u20ac\u20ac\u20ac\u20ac" * 2000, "int": [123456789] * 1000, "double": [12345.987654321] * 1000, "long": [123456789123456789123456789123456789] * 1000, "tuple": [(x * x, "tuple", (300, 400, (500, 600, (x * x, x * x)))) for x in range(200)], "list": [[x * x, "list", [300, 400, [500, 600, [x * x]]]] for x in range(200)], "set": set(x * x for x in range(1000)), "dict": {str(i * i): {str(1000 + j): chr(j + 65) for j in range(5)} for i in range(100)}, "exception": [ZeroDivisionError("test exeception", x * x) for x in range(1000)], "class": [Person("harry", x * x) for x in range(1000)], "datetime": [datetime.datetime.now() for x in range(1000)], "complex": [complex(x + x, x * x) for x in range(1000)], "decimal": [decimal.Decimal("1122334455667788998877665544332211.9876543212345678987654321123456789") for x in range(1000)], "uuid": [guid for x in range(1000)] } serializers = {} try: import pickle serializers["pickle"] = (pickle.dumps, pickle.loads) except ImportError: pass try: import cPickle serializers["cpickle"] = (cPickle.dumps, cPickle.loads) except ImportError: pass import json serializers["json"] = (lambda d: json.dumps(d).encode("utf-8"), lambda d: json.loads(d.decode("utf-8"))) import serpent serializers["serpent"] = (serpent.dumps, serpent.loads) import marshal serializers["marshal"] = (marshal.dumps, marshal.loads) try: import msgpack serializers["msgpack"] = (lambda d: msgpack.packb(d, use_bin_type=True), lambda d: msgpack.unpackb(d)) except ImportError: pass try: import xmlrpclib as xmlrpc except ImportError: import xmlrpc.client as xmlrpc def xmldumps(data): return xmlrpc.dumps((data,)).encode("utf-8") def xmlloads(data): return xmlrpc.loads(data.decode("utf-8"))[0] serializers["xmlrpc"] = (xmldumps, xmlloads) no_result = 9999999999 def run(): results = {} number = 10 repeat = 3 for ser in serializers: print("serializer:", ser) results[ser] = {"sizes": {}, "ser-times": {}, "deser-times": {}} for key in sorted(data): print(key, end="; ") sys.stdout.flush() try: serialized = serializers[ser][0](data[key]) except (TypeError, ValueError, OverflowError): print("error!") results[ser]["sizes"][key] = no_result results[ser]["ser-times"][key] = no_result results[ser]["deser-times"][key] = no_result else: results[ser]["sizes"][key] = len(serialized) durations_ser = [] durations_deser = [] serializer, deserializer = serializers[ser] serialized_data = serializer(data[key]) for _ in range(repeat): start = perf_timer() for _ in range(number): serializer(data[key]) durations_ser.append(perf_timer() - start) for _ in range(repeat): start = perf_timer() for _ in range(number): deserialized_data = deserializer(serialized_data) if type(deserialized_data) is dict: encoding = deserialized_data.get("encoding") if encoding=="base64": deserialized_data = serpent.tobytes(deserialized_data) durations_deser.append(perf_timer() - start) duration_ser = min(durations_ser) duration_deser = min(durations_deser) results[ser]["ser-times"][key] = round(duration_ser * 1e6 / number, 2) results[ser]["deser-times"][key] = round(duration_deser * 1e6 / number, 2) print() return results def tables_size(results): print("\nSIZE RESULTS\n") sizes_per_datatype = {} for ser in results: for datatype in results[ser]["sizes"]: size = results[ser]["sizes"][datatype] if datatype not in sizes_per_datatype: sizes_per_datatype[datatype] = [] sizes_per_datatype[datatype].append((size, ser)) sizes_per_datatype = {datatype: sorted(sizes) for datatype, sizes in sizes_per_datatype.items()} for dt in sorted(sizes_per_datatype): print(dt) for pos, (size, serializer) in enumerate(sizes_per_datatype[dt]): if size == no_result: size = "unsupported" else: size = "%8d" % size print(" %2d: %-8s %s" % (pos + 1, serializer, size)) print() def tables_speed(results, what_times, header): print("\n%s\n" % header) durations_per_datatype = {} for ser in results: for datatype in results[ser]["sizes"]: duration = results[ser][what_times][datatype] if datatype not in durations_per_datatype: durations_per_datatype[datatype] = [] durations_per_datatype[datatype].append((duration, ser)) durations_per_datatype = {datatype: sorted(durations) for datatype, durations in durations_per_datatype.items()} for dt in sorted(durations_per_datatype): print(dt) for pos, (duration, serializer) in enumerate(durations_per_datatype[dt]): if duration == no_result: duration = "unsupported" else: duration = "%8d" % duration print(" %2d: %-8s %s" % (pos + 1, serializer, duration)) print() if __name__ == "__main__": results = run() tables_size(results) tables_speed(results, "ser-times", "SPEED RESULTS (SERIALIZATION)") tables_speed(results, "deser-times", "SPEED RESULTS (DESERIALIZATION)") Serpent-serpent-1.41/tests/test_serpent.py000066400000000000000000001214231425606146200210010ustar00rootroot00000000000000""" Serpent: ast.literal_eval() compatible object tree serialization. Software license: "MIT software license". See http://opensource.org/licenses/MIT """ import sys import ast import timeit import datetime import uuid import decimal import array import tempfile import os import hashlib import traceback import threading import time import collections import enum import attr import unittest from collections.abc import KeysView, ValuesView, ItemsView import serpent def strip_header(ser): _, _, data = ser.partition(b"\n") return data class TestDeserialize(unittest.TestCase): def test_deserialize(self): data = serpent.loads(b"555") self.assertEqual(555, data) def test_deserialize_chr(self): unicodestring = u"euro\u20ac" encoded = repr(unicodestring).encode("utf-8") data = serpent.loads(encoded) self.assertEqual(unicodestring, data) def test_weird_complex(self): c1 = complex(float('inf'), 4) ser = serpent.dumps(c1) c2 = serpent.loads(ser) self.assertEqual(c1, c2) c3 = serpent.loads(b"(1e30000+4.0j)") self.assertEqual(c1, c3) def test_trailing_commas(self): v = serpent.loads(b"[1,2,3,]") self.assertEqual([1, 2, 3], v) v = serpent.loads(b"(1,2,3,)") self.assertEqual((1, 2, 3), v) v = serpent.loads(b"{'a':1, 'b':2, 'c':3,}") self.assertEqual({'a': 1, 'b': 2, 'c': 3}, v) def test_trailing_comma_set(self): v = serpent.loads(b"{1,2,3,}") self.assertEqual({1, 2, 3}, v) def test_unicode_escapes(self): v = serpent.loads(b"'\\u20ac'") self.assertEqual(u"\u20ac", v) v = serpent.loads(b"'\\U00022001'") self.assertEqual(u"\U00022001", v) def test_input_types(self): bytes_input = b"'text'" bytearray_input = bytearray(bytes_input) memview_input = memoryview(bytes_input) self.assertEqual("text", serpent.loads(bytes_input)) self.assertEqual("text", serpent.loads(bytearray_input)) self.assertEqual("text", serpent.loads(memview_input)) class TestBasics(unittest.TestCase): def test_py2_py3_unicode_repr(self): data = u"hello\u20ac" py2repr = b"# serpent utf-8 python2.6\n'hello\\u20ac'" result = serpent.loads(py2repr) self.assertEqual(data, result, "must understand python 2.x repr form of unicode string") py3repr = b"# serpent utf-8 python3.2\n'hello\xe2\x82\xac'" try: result = serpent.loads(py3repr) self.assertEqual(data, result, "must understand python 3.x repr form of unicode string") except ValueError: self.fail("must parse it correctly") def test_header(self): ser = serpent.dumps(None) header, _, rest = ser.partition(b"\n") hdr = "# serpent utf-8 python3.2".encode("utf-8") self.assertEqual(hdr, header) def test_comments(self): ser = b"""# serpent utf-8 python3.2 [ 1, 2, # some comments here 3, 4] # more here # and here.""" data = serpent.loads(ser) self.assertEqual([1, 2, 3, 4], data) ser = b"[ 1, 2 ]" # no header whatsoever data = serpent.loads(ser) self.assertEqual([1, 2], data) def test_sorting(self): obj = [3, 2, 1] ser = serpent.dumps(obj) data = strip_header(ser) self.assertEqual(b"[3,2,1]", data) obj = (3, 2, 1) ser = serpent.dumps(obj) data = strip_header(ser) self.assertEqual(b"(3,2,1)", data) obj = {3: "three", 4: "four", 2: "two", 1: "one"} ser = serpent.dumps(obj) data = strip_header(ser) self.assertEqual(36, len(data)) obj = {3, 4, 2, 1, 6, 5} ser = serpent.dumps(obj) data = strip_header(ser) self.assertEqual(13, len(data)) ser = serpent.dumps(obj, indent=True) data = strip_header(ser) self.assertEqual(b"{\n 1,\n 2,\n 3,\n 4,\n 5,\n 6\n}", data) # sorted obj = {3, "something"} ser = serpent.dumps(obj, indent=False) data = strip_header(ser) self.assertTrue(data == b"{3,'something'}" or data == b"{'something',3}") ser = serpent.dumps(obj, indent=True) data = strip_header(ser) self.assertTrue(data == b"{\n 3,\n 'something'\n}" or data == b"{\n 'something',\n 3\n}") obj = {3: "three", "something": 99} ser = serpent.dumps(obj, indent=False) data = strip_header(ser) self.assertTrue(data == b"{'something':99,3:'three'}" or data == b"{3:'three','something':99}") ser = serpent.dumps(obj, indent=True) data = strip_header(ser) self.assertTrue(data == b"{\n 'something': 99,\n 3: 'three'\n}" or data == b"{\n 3: 'three',\n 'something': 99\n}") obj = {3: "three", 4: "four", 5: "five", 2: "two", 1: "one"} ser = serpent.dumps(obj, indent=True) data = strip_header(ser) self.assertEqual(b"{\n 1: 'one',\n 2: 'two',\n 3: 'three',\n 4: 'four',\n 5: 'five'\n}", data) # sorted def test_none(self): ser = serpent.dumps(None) data = strip_header(ser) self.assertEqual(b"None", data) def test_string(self): ser = serpent.dumps("hello") data = strip_header(ser) self.assertEqual(b"'hello'", data) ser = serpent.dumps("quotes'\"") data = strip_header(ser) self.assertEqual(b"'quotes\\'\"'", data) ser = serpent.dumps("quotes2'") data = strip_header(ser) self.assertEqual(b"\"quotes2'\"", data) def test_string_with_escapes(self): ser = serpent.dumps("\n") d = strip_header(ser) self.assertEqual(b"'\\n'", d) ser = serpent.dumps("\a") d = strip_header(ser) self.assertEqual(b"'\\x07'", d) # repr() does this hex escape line = "'hello\nlastline\ttab\\@slash\a\b\f\n\r\t\v'" ser = serpent.dumps(line) d = strip_header(ser) self.assertEqual(b"\"'hello\\nlastline\\ttab\\\\@slash\\x07\\x08\\x0c\\n\\r\\t\\x0b'\"", d) # the hex escapes are done by repr() data = serpent.loads(ser) self.assertEqual(line, data) def test_nullbytesstring(self): ser = serpent.dumps(u"\x00null") data = serpent.loads(ser) self.assertEqual("\x00null", data) ser = serpent.dumps(u"\x01") self.assertEqual(b"'\\x01'", strip_header(ser)) data = serpent.loads(ser) self.assertEqual("\x01", data) ser = serpent.dumps(u"\x1f") self.assertEqual(b"'\\x1f'", strip_header(ser)) data = serpent.loads(ser) self.assertEqual("\x1f", data) ser = serpent.dumps(u"\x20") self.assertEqual(b"' '", strip_header(ser)) data = serpent.loads(ser) self.assertEqual(" ", data) def test_nullbytesstr(self): line = chr(0) + "null" ser = serpent.dumps(line) data = strip_header(ser) self.assertEqual(b"'\\x00null'", data, "must escape 0-byte") data = serpent.loads(ser) self.assertEqual(line, data) def test_detectNullByte(self): with self.assertRaises(ValueError) as ex: serpent.loads(b"'contains\x00nullbyte'") self.fail("must fail") self.assertTrue("0-bytes" in str(ex.exception)) with self.assertRaises(ValueError) as ex: serpent.loads(bytearray(b"'contains\x00nullbyte'")) self.fail("must fail") self.assertTrue("0-bytes" in str(ex.exception)) with self.assertRaises(ValueError) as ex: serpent.loads(memoryview(b"'contains\x00nullbyte'")) self.fail("must fail") self.assertTrue("0-bytes" in str(ex.exception)) serpent.loads(bytearray(b"'contains no nullbyte'")) serpent.loads(memoryview(b"'contains no nullbyte'")) def test_unicode_U(self): u = "euro" + chr(0x20ac)+"\U00022001" self.assertTrue(type(u) is str) ser = serpent.dumps(u) data = serpent.loads(ser) self.assertEqual(u, data) def test_unicode_escape_allchars(self): # this checks for all 0x0000-0xffff chars that they will be serialized # into a proper repr form and when processed back by ast.literal_parse directly # will get turned back into the chars 0x0000-0xffff again highest_char = 0xffff all_chars = u"".join(chr(c) for c in range(highest_char+1)) ser = serpent.dumps(all_chars) self.assertGreater(len(ser), len(all_chars)) ser = ser.decode("utf-8") data = ast.literal_eval(ser) self.assertEqual(highest_char+1, len(data)) for i, c in enumerate(data): if chr(i) != c: self.fail("char different for "+str(i)) def test_unicode_quotes(self): ser = serpent.dumps(str("quotes'\"")) data = strip_header(ser) self.assertEqual(b"'quotes\\'\"'", data) ser = serpent.dumps(str("quotes2'")) data = strip_header(ser) self.assertEqual(b"\"quotes2'\"", data) def test_utf8_correctness(self): u = u"\x00\x01\x80\x81\xfe\xffabcdef\u20ac" utf_8_correct = repr(u).encode("utf-8") if utf_8_correct.startswith(b"u"): utf_8_correct = utf_8_correct[1:] ser = serpent.dumps(u) d = strip_header(ser) self.assertEqual(utf_8_correct, d) def test_unicode_with_escapes_py3(self): ser = serpent.dumps(str("\n")) d = strip_header(ser) self.assertEqual(b"'\\n'", d) ser = serpent.dumps(str("\a")) d = strip_header(ser) self.assertEqual(b"'\\x07'", d) ser = serpent.dumps("\a"+chr(0x20ac)) d = strip_header(ser) self.assertEqual(b"'\\x07\xe2\x82\xac'", d) line = "'euro" + chr(0x20ac) + "\nlastline\ttab\\@slash\a\b\f\n\r\t\v'" ser = serpent.dumps(line) d = strip_header(ser) self.assertEqual(b"\"'euro\xe2\x82\xac\\nlastline\\ttab\\\\@slash\\x07\\x08\\x0c\\n\\r\\t\\x0b'\"", d) data = serpent.loads(ser) self.assertEqual(line, data) def test_numbers(self): ser = serpent.dumps(12345) data = strip_header(ser) self.assertEqual(b"12345", data) ser = serpent.dumps(123456789123456789123456789) data = strip_header(ser) self.assertEqual(b"123456789123456789123456789", data) ser = serpent.dumps(99.1234) data = strip_header(ser) if sys.platform == 'cli': self.assertEqual(b"99.123400000000004", data) else: self.assertEqual(b"99.1234", data) ser = serpent.dumps(decimal.Decimal("1234.9999999999")) data = strip_header(ser) self.assertEqual(b"'1234.9999999999'", data) ser = serpent.dumps(2 + 3j) data = strip_header(ser) self.assertEqual(b"(2.0+3.0j)", data) ser = serpent.dumps(2 - 3j) data = strip_header(ser) self.assertEqual(b"(2.0-3.0j)", data) def test_bool(self): ser = serpent.dumps(True) data = strip_header(ser) self.assertEqual(b"True", data) def test_dict(self): ser = serpent.dumps({}) data = strip_header(ser) self.assertEqual(b"{}", data) ser = serpent.dumps({}, indent=True) data = strip_header(ser) self.assertEqual(b"{}", data) mydict = { 42: 'fortytwo', 'status': False, 'name': 'Sally', 'sixteen-and-half': 16.5 } ser = serpent.dumps(mydict) data = strip_header(ser) self.assertEqual(69, len(data)) self.assertEqual(ord("{"), data[0]) self.assertEqual(ord("}"), data[-1]) ser = serpent.dumps(mydict, indent=True) data = strip_header(ser) self.assertEqual(86, len(data)) self.assertEqual(ord("{"), data[0]) self.assertEqual(ord("}"), data[-1]) def test_dict_str(self): data = {"key": str("value")} ser = serpent.dumps(data) data2 = serpent.loads(ser) self.assertEqual(str("value"), data2["key"]) data = {str("key"): 123} ser = serpent.dumps(data) data2 = serpent.loads(ser) self.assertEqual(123, data2[str("key")]) def test_dict_iters(self): data = {"john": 22, "sophie": 34, "bob": 26} ser = serpent.loads(serpent.dumps(data.keys())) self.assertIsInstance(ser, list) self.assertEqual(["bob", "john", "sophie"], sorted(ser)) ser = serpent.loads(serpent.dumps(data.values())) self.assertIsInstance(ser, list) self.assertEqual([22, 26, 34], sorted(ser)) ser = serpent.loads(serpent.dumps(data.items())) self.assertIsInstance(ser, list) self.assertEqual([("bob", 26), ("john", 22), ("sophie", 34)], sorted(ser)) def test_list(self): ser = serpent.dumps([]) data = strip_header(ser) self.assertEqual(b"[]", data) ser = serpent.dumps([], indent=True) data = strip_header(ser) self.assertEqual(b"[]", data) mylist = [42, "Sally", 16.5] ser = serpent.dumps(mylist) data = strip_header(ser) self.assertEqual(b"[42,'Sally',16.5]", data) ser = serpent.dumps(mylist, indent=True) data = strip_header(ser) self.assertEqual(b"""[ 42, 'Sally', 16.5 ]""", data) def test_tuple(self): ser = serpent.dumps(tuple()) data = strip_header(ser) self.assertEqual(b"()", data) ser = serpent.dumps(tuple(), indent=True) data = strip_header(ser) self.assertEqual(b"()", data) ser = serpent.dumps((1,)) data = strip_header(ser) self.assertEqual(b"(1,)", data) ser = serpent.dumps((1,), indent=True) data = strip_header(ser) self.assertEqual(b"(\n 1,\n)", data) mytuple = (42, "Sally", 16.5) ser = serpent.dumps(mytuple) data = strip_header(ser) self.assertEqual(b"(42,'Sally',16.5)", data) ser = serpent.dumps(mytuple, indent=True) data = strip_header(ser) self.assertEqual(b"""( 42, 'Sally', 16.5 )""", data) def test_set(self): ser = serpent.dumps(set()) data = strip_header(ser) self.assertEqual(b"()", data) ser = serpent.dumps(set(), indent=True) data = strip_header(ser) self.assertEqual(b"()", data) # test set-literals myset = {42, "Sally"} ser = serpent.dumps(myset) data = strip_header(ser) self.assertTrue(data == b"{42,'Sally'}" or data == b"{'Sally',42}") ser = serpent.dumps(myset, indent=True) data = strip_header(ser) self.assertTrue(data == b"{\n 42,\n 'Sally'\n}" or data == b"{\n 'Sally',\n 42\n}") # unicode elements data = {str("text1"), str("text2")} ser = serpent.dumps(data) data2 = serpent.loads(ser) self.assertEqual(2, len(data2)) self.assertIn(str("text1"), data2) self.assertIn(str("text2"), data2) def test_bytes_default(self): ser = serpent.dumps(bytes(b"abcdef")) data = serpent.loads(ser) self.assertEqual({'encoding': 'base64', 'data': 'YWJjZGVm'}, data) ser = serpent.dumps(bytearray(b"abcdef")) data = serpent.loads(ser) self.assertEqual({'encoding': 'base64', 'data': 'YWJjZGVm'}, data) ser = serpent.dumps(memoryview(b"abcdef")) data = serpent.loads(ser) self.assertEqual({'encoding': 'base64', 'data': 'YWJjZGVm'}, data) def test_bytes_repr(self): ser = serpent.dumps(bytes(b"abcdef\xff"), bytes_repr=True) data = serpent.loads(ser) self.assertEqual(b'abcdef\xff', data) ser = serpent.dumps(bytearray(b"abcdef\xff"), bytes_repr=True) data = serpent.loads(ser) self.assertEqual(b'abcdef\xff', data) ser = serpent.dumps(memoryview(b"abcdef\xff"), bytes_repr=True) data = serpent.loads(ser) self.assertEqual(b'abcdef\xff', data) def test_exception(self): x = ZeroDivisionError("wrong") ser = serpent.dumps(x) data = serpent.loads(ser) self.assertEqual({ '__class__': 'ZeroDivisionError', '__exception__': True, 'args': ('wrong',), 'attributes': {} }, data) x = ZeroDivisionError("wrong", 42) ser = serpent.dumps(x) data = serpent.loads(ser) self.assertEqual({ '__class__': 'ZeroDivisionError', '__exception__': True, 'args': ('wrong', 42), 'attributes': {} }, data) x.custom_attribute = "custom_attr" ser = serpent.dumps(x) data = serpent.loads(ser) self.assertEqual({ '__class__': 'ZeroDivisionError', '__exception__': True, 'args': ('wrong', 42), 'attributes': {'custom_attribute': 'custom_attr'} }, data) def test_exception2(self): x = ZeroDivisionError("wrong") ser = serpent.dumps(x, module_in_classname=True) data = serpent.loads(ser) self.assertEqual({ '__class__': "builtins.ZeroDivisionError", '__exception__': True, 'args': ('wrong',), 'attributes': {} }, data) def test_class_regular(self): c = Class1() ser = serpent.dumps(c) data = serpent.loads(ser) self.assertEqual({'__class__': 'Class1', 'attr': 1}, data) def test_class_getstate(self): c = Class2() ser = serpent.dumps(c) data = serpent.loads(ser) self.assertEqual({'attr': 42}, data) def test_class_slots(self): c = SlotsClass() ser = serpent.dumps(c) data = serpent.loads(ser) self.assertEqual({'__class__': 'SlotsClass', 'attr': 1}, data) def test_class_pprinter(self): import pprint p = pprint.PrettyPrinter(stream="dummy", width=99) ser = serpent.dumps(p) data = serpent.loads(ser) self.assertEqual("PrettyPrinter", data["__class__"]) self.assertEqual(99, data["_width"]) def test_class2(self): import pprint pp = pprint.PrettyPrinter(stream="dummy", width=42) ser = serpent.dumps(pp, module_in_classname=True) data = serpent.loads(ser) self.assertEqual('pprint.PrettyPrinter', data["__class__"]) def test_class_hashable_key_check(self): import pprint pp = pprint.PrettyPrinter(stream="dummy", width=42) with self.assertRaises(TypeError) as x: serpent.dumps({1: 1, 2: 1, 3: 1, strip_header: 1}) # can only serialize simple types as dict keys (hashable) self.assertTrue("hashable type" in str(x.exception)) with self.assertRaises(TypeError) as x: serpent.dumps({1: 1, 2: 1, 3: 1, pp: 1}) # can only serialize simple types as dict keys (hashable) self.assertTrue("hashable type" in str(x.exception)) def test_class_hashable_set_element_check(self): import pprint pp = pprint.PrettyPrinter(stream="dummy", width=42) with self.assertRaises(TypeError) as x: serpent.dumps({1, 2, 3, strip_header}) # can only serialize simple typles as set elements (hashable) self.assertTrue("hashable type" in str(x.exception)) with self.assertRaises(TypeError) as x: serpent.dumps({1, 2, 3, pp}) # can only serialize simple typles as set elements (hashable) self.assertTrue("hashable type" in str(x.exception)) def test_enum_hashable(self): class Color(enum.Enum): RED = 1 GREEN = 2 BLUE = 3 data = serpent.dumps({"abc", Color.RED, Color.GREEN, Color.BLUE}) orig = serpent.loads(data) self.assertEqual({"abc", 1, 2, 3}, orig) data = serpent.dumps({"abc": 1, Color.RED: 1, Color.GREEN: 1, Color.BLUE: 1}) orig = serpent.loads(data) self.assertEqual({"abc": 1, 1: 1, 2: 1, 3: 1}, orig) def test_array(self): ser = serpent.dumps(array.array('u', str("unicode"))) data = strip_header(ser) self.assertEqual(b"'unicode'", data) ser = serpent.dumps(array.array('i', [44, 45, 46])) data = strip_header(ser) self.assertEqual(b"[44,45,46]", data) ser = serpent.dumps(array.array('u', "normal")) data = strip_header(ser) self.assertEqual(b"'normal'", data) def test_time(self): ser = serpent.dumps(datetime.datetime(2013, 1, 20, 23, 59, 45, 999888)) data = strip_header(ser) self.assertEqual(b"'2013-01-20T23:59:45.999888'", data) ser = serpent.dumps(datetime.date(2013, 1, 20)) data = strip_header(ser) self.assertEqual(b"'2013-01-20'", data) ser = serpent.dumps(datetime.time(23, 59, 45, 999888)) data = strip_header(ser) self.assertEqual(b"'23:59:45.999888'", data) ser = serpent.dumps(datetime.time(23, 59, 45)) data = strip_header(ser) self.assertEqual(b"'23:59:45'", data) ser = serpent.dumps(datetime.timedelta(1, 4000, 999888, minutes=22)) data = strip_header(ser) if sys.platform == 'cli': self.assertEqual(b"91720.999888000006", data) else: self.assertEqual(b"91720.999888", data) ser = serpent.dumps(datetime.timedelta(seconds=12345)) data = strip_header(ser) self.assertEqual(b"12345.0", data) def test_timezone(self): import pytz # requires pytz library tz_nl = pytz.timezone("Europe/Amsterdam") dt_tz = tz_nl.localize(datetime.datetime(2013, 1, 20, 23, 59, 45, 999888)) ser = serpent.dumps(dt_tz) data = strip_header(ser) self.assertEqual(b"'2013-01-20T23:59:45.999888+01:00'", data) # normal time dt_tz = tz_nl.localize(datetime.datetime(2013, 5, 10, 13, 59, 45, 999888)) ser = serpent.dumps(dt_tz) data = strip_header(ser) self.assertEqual(b"'2013-05-10T13:59:45.999888+02:00'", data) # daylight saving time def test_pickle_api(self): ser = serpent.dumps([1, 2, 3]) serpent.loads(ser) tmpfn = tempfile.mktemp() with open(tmpfn, "wb") as outf: serpent.dump([1, 2, 3], outf, indent=True) with open(tmpfn, "rb") as inf: data = serpent.load(inf) self.assertEqual([1, 2, 3], data) os.remove(tmpfn) def test_weird_floats(self): values = [float('inf'), float('-inf'), float('nan'), complex(float('inf'), 4)] ser = serpent.dumps(values) ser = strip_header(serpent.dumps(values)) self.assertEqual(b"[1e30000,-1e30000,{'__class__':'float','value':'nan'},(1e30000+4.0j)]", ser) values2 = serpent.loads(ser) self.assertEqual([float('inf'), float('-inf'), {'__class__': 'float', 'value': 'nan'}, (float('inf')+4j)], values2) values2 = serpent.loads(b"[1e30000,-1e30000]") self.assertEqual([float('inf'), float('-inf')], values2) def test_float_precision(self): # make sure we don't lose precision when converting floats (including scientific notation) v = serpent.loads(serpent.dumps(1.2345678987654321)) self.assertEqual(1.2345678987654321, v) v = serpent.loads(serpent.dumps(5555.12345678987656)) self.assertEqual(5555.12345678987656, v) v = serpent.loads(serpent.dumps(98765432123456.12345678987656)) self.assertEqual(98765432123456.12345678987656, v) v = serpent.loads(serpent.dumps(98765432123456.12345678987656e+44)) self.assertEqual(98765432123456.12345678987656e+44, v) v = serpent.loads(serpent.dumps((98765432123456.12345678987656e+44+665544332211.9998877665544e+33j))) self.assertEqual((98765432123456.12345678987656e+44+665544332211.9998877665544e+33j), v) v = serpent.loads(serpent.dumps((-98765432123456.12345678987656e+44 -665544332211.9998877665544e+33j))) self.assertEqual((-98765432123456.12345678987656e+44 -665544332211.9998877665544e+33j), v) def test_enums(self): class Animal(enum.Enum): BEE = 1 CAT = 2 DOG = 3 v = serpent.loads(serpent.dumps(Animal.CAT)) self.assertEqual(2, v) class Animal2(enum.Enum): BEE = 1 CAT = 2 DOG = 3 HORSE = 4 RABBIT = 5 v = serpent.loads(serpent.dumps(Animal2.HORSE)) self.assertEqual(4, v) def test_tobytes(self): obj = b"test" self.assertIs(obj, serpent.tobytes(obj)) obj = memoryview(b"test") self.assertIs(obj, serpent.tobytes(obj)) obj = bytearray(b"test") self.assertIs(obj, serpent.tobytes(obj)) ser = {'data': 'dGVzdA==', 'encoding': 'base64'} out = serpent.tobytes(ser) self.assertEqual(b"test", out) self.assertIsInstance(out, bytes) with self.assertRaises(TypeError): serpent.tobytes({'@@@data': 'dGVzdA==', 'encoding': 'base64'}) with self.assertRaises(TypeError): serpent.tobytes({'data': 'dGVzdA==', '@@@encoding': 'base64'}) with self.assertRaises(TypeError): serpent.tobytes({'data': 'dGVzdA==', 'encoding': 'base99'}) with self.assertRaises(TypeError): serpent.tobytes({}) with self.assertRaises(TypeError): serpent.tobytes(42) @unittest.skip("no performance tests in default test suite") class TestSpeed(unittest.TestCase): def setUp(self): self.data = { "str": "hello", "unicode": chr(0x20ac), # euro-character "numbers": [123456789012345678901234567890, 999.1234, decimal.Decimal("1.99999999999999999991")], "bytes": bytearray(100), "list": [1, 2, 3, 4, 5, 6, 7, 8, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9], "tuple": (1, 2, 3, 4, 5, 6, 7, 8), "set": {1, 2, 3, 4, 5, 6, 7, 8, 9}, "dict": dict((i, str(i) * 4) for i in range(10)), "exc": ZeroDivisionError("fault"), "dates": [ datetime.datetime.now(), datetime.date.today(), datetime.time(23, 59, 45, 999888), datetime.timedelta(seconds=500) ], "uuid": uuid.uuid4() } self.floatlist = [12345.6789] * 1000 def test_ser_speed(self): print("serialize without indent:", timeit.timeit(lambda: serpent.dumps(self.data, False), number=1000)) print("serialize long list of floats:", timeit.timeit(lambda: serpent.dumps(self.floatlist, False), number=100)) print("serialize with indent:", timeit.timeit(lambda: serpent.dumps(self.data, True), number=1000)) def test_deser_speed(self): ser = serpent.dumps(self.data, False) print("deserialize without indent:", timeit.timeit(lambda: serpent.loads(ser), number=1000)) ser = serpent.dumps(self.data, True) print("deserialize with indent:", timeit.timeit(lambda: serpent.loads(ser), number=1000)) class TestIndent(unittest.TestCase): def test_indent_primitive(self): data = 12345 ser = serpent.dumps(data, indent=True).decode("utf-8") _, _, ser = ser.partition("\n") self.assertEqual("12345", ser) def test_indent_sorting(self): # non-indented should not be sorted, indented should data = {"ee": 1, "dd": 1, "cc": 1, "bb": 1, "aa": 1, 'ff': 1, 'hh': 1, 'gg': 1} ser = serpent.dumps(data, False) ser = strip_header(ser) self.assertNotEqual(b"{'aa':1,'bb':1,'cc':1,'dd':1,'ee':1,'ff':1,'gg':1,'hh':1}", ser) ser = serpent.dumps(data, True) ser = strip_header(ser) self.assertEqual(b"""{ 'aa': 1, 'bb': 1, 'cc': 1, 'dd': 1, 'ee': 1, 'ff': 1, 'gg': 1, 'hh': 1 }""", ser) data = set("irmen de jong irmen de jong666") ser = serpent.dumps(data, False) ser = strip_header(ser) self.assertNotEqual(b"' ','6','d','e','g','i','j','m','n','o','r'", ser[1:-1]) ser = serpent.dumps(data, True) ser = strip_header(ser) self.assertEqual(b"\n ' ',\n '6',\n 'd',\n 'e',\n 'g',\n 'i',\n 'j',\n 'm',\n 'n',\n 'o',\n 'r'\n", ser[1:-1]) def test_indent_containers(self): data = [1, 2, 3] ser = serpent.dumps(data, indent=True).decode("utf-8") _, _, ser = ser.partition("\n") self.assertEqual("""[ 1, 2, 3 ]""", ser) data = (1, 2, 3) ser = serpent.dumps(data, indent=True).decode("utf-8") _, _, ser = ser.partition("\n") self.assertEqual("""( 1, 2, 3 )""", ser) data = {1} ser = serpent.dumps(data, indent=True).decode("utf-8") _, _, ser = ser.partition("\n") self.assertEqual("""{ 1 }""", ser) data = {"one": 1} ser = serpent.dumps(data, indent=True).decode("utf-8") _, _, ser = ser.partition("\n") self.assertEqual("""{ 'one': 1 }""", ser) data = {"first": [1, 2, ("a", "b")], "second": {1: False}, "third": {1, 2}} ser = serpent.dumps(data, indent=True).decode("utf-8") _, _, ser = ser.partition("\n") self.assertEqual("""{ 'first': [ 1, 2, ( 'a', 'b' ) ], 'second': { 1: False }, 'third': { 1, 2 } }""", ser) class TestFiledump(unittest.TestCase): def testFile(self): datafile = "testserpent.utf8.bin" if not os.path.exists(datafile): mypath = os.path.split(__file__)[0] datafile = os.path.join(mypath, datafile) with open(datafile, "rb") as dfile: data = dfile.read() obj = serpent.loads(data) self.assertEqual(-3 + 8j, obj["numbers"][3]) class TestInterceptClass(unittest.TestCase): def testRegular(self): import pprint p = pprint.PrettyPrinter(stream="dummy", width=42) ser = serpent.dumps(p) data = serpent.loads(ser) self.assertEqual(42, data["_width"]) self.assertEqual("PrettyPrinter", data["__class__"]) def testIntercept(self): ex = ZeroDivisionError("wrong") ser = serpent.dumps(ex) data = serpent.loads(ser) # default behavior is to serialize the exception to a dict self.assertEqual({'__exception__': True, 'args': ('wrong',), '__class__': 'ZeroDivisionError', 'attributes': {}}, data) def custom_exception_translate(obj, serializer, stream, indent): serializer._serialize("custom_exception!", stream, indent) try: serpent.register_class(Exception, custom_exception_translate) ser = serpent.dumps(ex) data = serpent.loads(ser) self.assertEqual("custom_exception!", data) finally: serpent.unregister_class(Exception) class Something(object): def __init__(self, name, value): self.name = name self.value = value def __getstate__(self): return ("bogus", "state") class BaseClass(object): pass class SubClass(BaseClass): pass class TestCustomClasses(unittest.TestCase): def testCustomClass(self): def something_serializer(obj, serializer, stream, level): d = { "__class__": "Something", "custom": True, "name": obj.name, "value": obj.value } serializer.ser_builtins_dict(d, stream, level) serpent.register_class(Something, something_serializer) s = Something("hello", 42) d = serpent.dumps(s) x = serpent.loads(d) self.assertEqual({"__class__": "Something", "custom": True, "name": "hello", "value": 42}, x) serpent.unregister_class(Something) d = serpent.dumps(s) x = serpent.loads(d) self.assertEqual(("bogus", "state"), x) def testSubclass(self): def custom_serializer(obj, serializer, stream, level): serializer._serialize("[(sub)class=%s]" % type(obj), stream, level) serpent.register_class(BaseClass, custom_serializer) s = SubClass() d = serpent.dumps(s) x = serpent.loads(d) classname = __name__+".SubClass" self.assertEqual("[(sub)class=]", x) def testUUID(self): uid = uuid.uuid4() string_uid = str(uid) ser = serpent.dumps(uid) x = serpent.loads(ser) self.assertEqual(string_uid, x) def custom_uuid_translate(obj, serp, stream, level): serp._serialize("custom_uuid!", stream, level) serpent.register_class(uuid.UUID, custom_uuid_translate) try: ser = serpent.dumps(uid) x = serpent.loads(ser) self.assertEqual("custom_uuid!", x) finally: serpent.unregister_class(uuid.UUID) def testRegisterOrderPreserving(self): serpent._reset_special_classes_registry() serpent.register_class(BaseClass, lambda: None) serpent.register_class(SubClass, lambda: None) classes = list(serpent._special_classes_registry) self.assertEqual(KeysView, classes.pop(0)) self.assertEqual(ValuesView, classes.pop(0)) self.assertEqual(ItemsView, classes.pop(0)) self.assertEqual(collections.OrderedDict, classes.pop(0)) self.assertEqual(enum.Enum, classes.pop(0)) self.assertEqual(BaseClass, classes.pop(0)) self.assertEqual(SubClass, classes.pop(0)) self.assertEqual(0, len(classes)) class TestPyro4(unittest.TestCase): def testException(self): try: hashlib.new("non-existing-hash-name") ev = None except: et, ev, etb = sys.exc_info() tb_lines = traceback.format_exception(et, ev, etb) ev._pyroTraceback = tb_lines ser = serpent.dumps(ev, module_in_classname=False) data = serpent.loads(ser) self.assertTrue(data["__exception__"]) attrs = data["attributes"] self.assertIsInstance(attrs["_pyroTraceback"], list) tb_txt = "".join(attrs["_pyroTraceback"]) self.assertTrue(tb_txt.startswith("Traceback")) self.assertTrue(data["args"][0].startswith("unsupported hash")) self.assertEqual("ValueError", data["__class__"]) class TestCyclic(unittest.TestCase): def testTupleOk(self): t = (1, 2, 3) d = (t, t, t) data = serpent.dumps(d) serpent.loads(data) def testListOk(self): t = [1, 2, 3] d = [t, t, t] data = serpent.dumps(d) serpent.loads(data) def testDictOk(self): t = {"a": 1} d = {"x": t, "y": t, "z": t} data = serpent.dumps(d) serpent.loads(data) def testListCycle(self): d = [1, 2, 3] d.append(d) with self.assertRaises(ValueError) as e: serpent.dumps(d) self.assertEqual("Circular reference detected (list)", str(e.exception)) def testDictCycle(self): d = {"x": 1, "y": 2} d["d"] = d with self.assertRaises(ValueError) as e: serpent.dumps(d) self.assertEqual("Circular reference detected (dict)", str(e.exception)) def testClassCycle(self): d = Cycle() d.make_cycle(d) with self.assertRaises(ValueError) as e: serpent.dumps(d) self.assertEqual("Circular reference detected (class)", str(e.exception)) # noinspection PyUnreachableCode def testMaxLevel(self): ser = serpent.Serializer() self.assertGreater(ser.maximum_level, 10) # old Pypy appears to have a very low default recursionlimit array=[] arr=array for level in range(min(sys.getrecursionlimit()+10, 2000)): arr.append("level"+str(level)) arr2 = [] arr.append(arr2) arr=arr2 with self.assertRaises(ValueError) as x: ser.serialize(array) self.fail("should crash") self.assertTrue("too deep" in str(x.exception)) # check setting the maxlevel array = ["level1", ["level2", ["level3", ["level4"]]]] ser.maximum_level = 4 ser.serialize(array) # should work ser.maximum_level = 3 with self.assertRaises(ValueError) as x: ser.serialize(array) # should crash self.fail("should crash") self.assertTrue("too deep" in str(x.exception)) class Cycle(object): def __init__(self): self.name = "cycle" self.ref = None def make_cycle(self, ref): self.ref = ref class RegisterThread(threading.Thread): def __init__(self): super(RegisterThread, self).__init__() self.stop_running=False def run(self): i = 0 while not self.stop_running: serpent.register_class(type("clazz %d" % i, (), {}), None) # just register dummy serializer i += 1 class SerializationThread(threading.Thread): def __init__(self): super(SerializationThread, self).__init__() self.stop_running = False self.error = None def run(self): big_list = [Cycle() for _ in range(1000)] while not self.stop_running: try: _ = serpent.dumps(big_list) except RuntimeError as x: self.error = x print(x) break @unittest.skip class TestThreading(unittest.TestCase): def testThreadsafeTypeRegistrations(self): reg = RegisterThread() ser = SerializationThread() reg.daemon = ser.daemon = True reg.start() ser.start() time.sleep(1) reg.stop_running = ser.stop_running = True self.assertIsNone(ser.error) class TestCollections(unittest.TestCase): def testOrderedDict(self): o = collections.OrderedDict() o['apple'] = 1 o['banana'] = 2 o['orange'] = 3 d = serpent.dumps(o) o2 = serpent.loads(d) self.assertEqual({"__class__": "OrderedDict", "items": [('apple', 1), ('banana', 2), ('orange', 3)]}, o2) def testNamedTuple(self): Point = collections.namedtuple('Point', ['x', 'y']) p = Point(11, 22) d = serpent.dumps(p) p2 = serpent.loads(d) self.assertEqual((11, 22), p2) def testCounter(self): c = collections.Counter("even") d = serpent.dumps(c) c2 = serpent.loads(d) self.assertEqual({'e': 2, 'v': 1, 'n': 1}, c2) def testDeque(self): obj = collections.deque([1, 2, 3]) d = serpent.dumps(obj) obj2 = serpent.loads(d) self.assertEqual([1, 2, 3], obj2) def testChainMap(self): c = collections.ChainMap({"a": 1}, {"b": 2}, {"c": 3}) d = serpent.dumps(c) c2 = serpent.loads(d) self.assertEqual({'__class__': 'ChainMap', 'maps': [{'a': 1}, {'b': 2}, {'c': 3}]}, c2) def testDefaultDict(self): dd = collections.defaultdict(list) dd['a'] = 1 dd['b'] = 2 d = serpent.dumps(dd) dd2 = serpent.loads(d) self.assertEqual({'a': 1, 'b': 2}, dd2) def testUserDict(self): obj = collections.UserDict() obj['a'] = 1 obj['b'] = 2 d = serpent.dumps(obj) obj2 = serpent.loads(d) self.assertEqual({'a': 1, 'b': 2}, obj2) def testUserList(self): obj = collections.UserList([1, 2, 3]) d = serpent.dumps(obj) obj2 = serpent.loads(d) self.assertEqual([1, 2, 3], obj2) def testUserString(self): obj = collections.UserString("test") d = serpent.dumps(obj) obj2 = serpent.loads(d) self.assertEqual("test", obj2) class DataclassesTests(unittest.TestCase): # unfortunately, python 3.7 dataclasses are a syntax error in older python versions # def testDataclasses(self): # @dataclass # class InventoryItem: # name: str # unit_price: float # untyped: str # quantity_on_hand: int = 0 # item = InventoryItem("television", 1899.95, untyped="untyped", quantity_on_hand=5) # ser = serpent.dumps(item) # item2 = serpent.loads(ser) # self.assertDictEqual({"__class__": "InventoryItem", "name": "television", "quantity_on_hand": 5, # "unit_price": 1899.95, "untyped": "untyped"}, item2) def testAttr(self): @attr.s class InventoryItem(object): name = attr.ib(type=str) unit_price = attr.ib(type=float) quantity_on_hand = attr.ib(type=int) untyped = attr.ib() item = InventoryItem("television", 1899.95, untyped="untyped", quantity_on_hand=5) ser = serpent.dumps(item) item2 = serpent.loads(ser) self.assertDictEqual({"__class__": "InventoryItem", "name": "television", "quantity_on_hand": 5, "unit_price": 1899.95, "untyped": "untyped"}, item2) class Class1(object): def __init__(self): self.attr = 1 class Class2(object): def __getstate__(self): return {"attr": 42} class SlotsClass(object): __slots__ = ["attr"] def __init__(self): self.attr = 1 if __name__ == '__main__': unittest.main() Serpent-serpent-1.41/tests/test_unicode.py000066400000000000000000000025141425606146200207460ustar00rootroot00000000000000import sys import serpent import platform teststrings = [ u"", u"abc", u"\u20ac", u"\x00\x01\x80\x81\xfe\xff\u20ac\u4444\u0240slashu:\\uend.\\u20ac(no euro!)\\U00022001bigone" ] large = u"".join(chr(i) for i in range(256)) teststrings.append(large) large = u"".join(chr(i) for i in range(0x20ac+1)) teststrings.append(large) def main(): impl=platform.python_implementation()+"_{0}_{1}".format(sys.version_info[0], sys.version_info[1]) print("IMPL:", impl) with open("data_inputs_utf8.txt", "wb") as out: for source in teststrings: out.write(source.encode("utf-8")+b"\n") results = [] ser = serpent.Serializer() with open("data_"+impl+".serpent", "wb") as out: for i, source in enumerate(teststrings): data = ser.serialize(source) out.write(data) out.write(b"~\n~\n") assert b"\x00" not in data results.append(data) assert len(results)==len(teststrings) for i, source in enumerate(teststrings): print(i) result = serpent.loads(results[i]) if source!=result: print("ERRROR!!! RESULT AFTER serpent.loads IS NOT CORRECT!") print("SOURCE:",repr(source)) print("RESULT:",repr(result)) return print("OK") if __name__ == "__main__": main() Serpent-serpent-1.41/tests/test_unicode_parse.py000066400000000000000000000014071425606146200221400ustar00rootroot00000000000000import os import io import re import serpent from test_unicode import teststrings files = [f for f in os.listdir(".") if f.startswith("data_") and f.endswith(".serpent")] for f in files: print("Checking data file", f) resultstrings=[] with io.open(f, "rb") as inf: data = inf.read() data = re.split(b"~\n~\n", data)[:-1] assert len(data) == len(teststrings) # data = data[:-2] # XXX for num, d in enumerate(data, start=1): try: print("data item ",num,"...") resultstrings.append(serpent.loads(d)) except Exception as x: print("\nSERPENT ERROR", type(x)) if resultstrings==teststrings: print("OK") else: print("!!!FAIL!!!") Serpent-serpent-1.41/tests/testserpent.utf8.bin000066400000000000000000000023161425606146200216460ustar00rootroot00000000000000# serpent utf-8 python3.2 { # serpent supports comments inside the serialized data 'bytes': { 'data': 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', 'encoding': 'base64' }, 'dates': [ # some dates are here '2013-01-26T03:32:39.007001', '23:59:45.999888', 500.0 ], 'dict': { 0: '0000', 1: '1111', # some more comments 2: '2222', 3: '3333', 4: '4444', 5: '5555', 6: '6666', 7: '7777', 8: '8888', 9: '9999' }, 'exc': { '__class__': 'ZeroDivisionError', '__exception__': True, # this is an exception 'args': ( 'fault', ), 'message': 'fault' }, 'list': [ 1, 2, 3, 4, 5, 6, 7, 8 ], 'numbers': [ 123456789012345678901234567890, 999.1234, '1.99999999999999999991', (-3+8j) ], 'set': { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 'str': 'hello', 'tuple': ( 1, 2, 3, 4, 5, 6, 7, 8 ), 'unicode': '€', # the feared unicode. 'uuid': '57e12d82-511f-456b-ab65-f765144c6a90' }