using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Text.Json.Serialization; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Platform; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace CrossLangDevStudio; public static class CrossLangFileExtensions { public static void Skip(this Stream strm, long bytes) { byte[] data = new byte[1024]; if (strm.CanSeek) strm.Position += bytes; else { do { int read = strm.Read(data, 0, (int)Math.Min(bytes, data.LongLength)); if (read == 0) return; bytes -= read; } while (bytes != 0); } } } public class CrossLangFile { const string CROSSVM = "TCROSSVM"; public void Load(string path) { if (File.Exists(path)) using (var strm = File.OpenRead(path)) Load(strm); } public void Load(Stream strm) { byte[] header = new byte[18]; strm.ReadExactly(header); for (int i = 0; i < CROSSVM.Length; i++) { if (header[i] != (byte)CROSSVM[i]) throw new Exception("Invalid signature"); } Version = new CrossLangVersion(header, 13); uint ReadInt() { strm.ReadExactly(header, 0, 4); if (BitConverter.IsLittleEndian) Array.Reverse(header, 0, 4); return BitConverter.ToUInt32(header, 0); } void ReadString() { uint length = ReadInt(); byte[] data = new byte[length]; strm.ReadExactly(data, 0, data.Length); Strings.Add(Encoding.UTF8.GetString(data)); } string ReadSectionName() { strm.ReadExactly(header, 0, 4); return Encoding.UTF8.GetString(header, 0, 4); } string GetString() { uint index = ReadInt(); return Strings[(int)index]; } uint count = ReadInt(); for (uint i = 0; i < count; i++) { string section = ReadSectionName(); uint sectionSize = ReadInt(); switch (section) { case "FUNS": { uint chks = ReadInt(); for (uint j = 0; j < chks; j++) { uint fnParts = ReadInt(); string[] parts = new string[fnParts]; for (uint k = 0; k < fnParts; k++) { parts[k] = GetString(); } uint fid = ReadInt(); Functions.Add(new CrossLangFunction(parts, fid)); } } break; case "CHKS": { uint chks = ReadInt(); for (uint j = 0; j < chks; j++) { uint fnParts = ReadInt(); string[] parts = new string[fnParts]; for (uint k = 0; k < fnParts; k++) { parts[k] = GetString(); } uint len = ReadInt(); strm.Skip(len); Chunks.Add(parts); } } break; case "STRS": { uint theCount = ReadInt(); for (uint j = 0; j < theCount; j++) { ReadString(); } } break; case "NAME": Name = GetString(); break; case "INFO": Info = GetString(); break; case "ICON": Icon = (int)ReadInt(); break; case "RESO": byte[] data = new byte[sectionSize]; strm.ReadExactly(data, 0, data.Length); Resources.Add(data); break; case "DEPS": if (sectionSize == 9) { string name = GetString(); strm.ReadExactly(header, 0, 5); var version = new CrossLangVersion(header); Dependencies.Add(new CrossLangDependency() { Name = name, Version = version }); } break; case "CLSS": { uint chks = ReadInt(); for (uint j = 0; j < chks; j++) { string docs = GetString(); uint namePartLen = ReadInt(); string[] nameParts = new string[namePartLen]; for (uint k = 0; k < namePartLen; k++) { nameParts[k] = GetString(); } string name = string.Join(".", nameParts); uint inhPartLen = ReadInt(); string[] inhParts = new string[inhPartLen]; for (uint k = 0; k < inhPartLen; k++) { inhParts[k] = GetString(); } string inh = string.Join(".", inhParts); uint entCount = ReadInt(); CrossLangClassEntry[] entries = new CrossLangClassEntry[entCount]; for (uint k = 0; k < entCount; k++) { /* Ensure(stream,main_header,1); uint8_t sig = main_header[0]; ent.isAbstract = (sig & 0b00001000) != 0; ent.isFunction = (sig & 0b00000100) == 0; ent.modifier = (TClassModifier)(sig & 3); ent.documentation = GetString(stream); ent.name = GetString(stream); uint32_t arglen = EnsureInt(stream); for(uint32_t l = 0; l < arglen; l++) ent.args.push_back(GetString(stream)); ent.chunkId = EnsureInt(stream); cls.entry.push_back(ent); */ int sig = strm.ReadByte(); if (sig == -1) throw new EndOfStreamException("Could not read flags byte"); bool isAbstract = ((byte)sig & 0b00001000) != 0; bool isFunction = ((byte)sig & 0b00000100) == 0; CrossLangClassEntryModifier modifier = (CrossLangClassEntryModifier)(sig & 3); string ent_doc = GetString(); string ent_name = GetString(); uint arglen = ReadInt(); string[] ent_args = new string[arglen]; for (uint l = 0; l < arglen; l++) { ent_args[l] = GetString(); } uint chunkId = ReadInt(); entries[k] = new CrossLangClassEntry(isAbstract, isFunction, modifier, ent_doc, ent_name, ent_args, chunkId); } Classes.Add(new CrossLangClass(docs, nameParts, name, inhParts, inh, entries)); } } break; default: strm.Skip(sectionSize); break; } } } public string Name { get; private set; } = ""; public CrossLangVersion Version { get; private set; } public string Info { get; private set; } = ""; public int Icon { get; private set; } = -1; public CrossLangInfo InfoObject => JsonConvert.DeserializeObject(Info) ?? new CrossLangInfo(); public Bitmap GetIcon(int height = 128) { using (Stream strm = (Icon > -1 && Icon < Resources.Count) ? new MemoryStream(Resources[Icon], false) : AssetLoader.Open(new Uri("avares://CrossLangDevStudio/Assets/crosslang.png"))) { return Bitmap.DecodeToHeight(strm, height); } } internal string GetPrettyName() { var info = InfoObject; if (info is not null) { if (!string.IsNullOrWhiteSpace(info.ShortNamePretty)) return info.ShortNamePretty; if (!string.IsNullOrWhiteSpace(info.ShortName)) return info.ShortName; } return this.Name; } public List Strings { get; } = new List(); public List Resources { get; } = new List(); public List Dependencies { get; } = new List(); public List Functions { get; } = new List(); public List Classes { get; } = new List(); public List Chunks { get; } = new List(); } public class CrossLangClass(string doc, string[] nameParts, string name, string[] inhParts, string inh, CrossLangClassEntry[] ents) { public string Documentation => doc; public string[] NameParts => nameParts; public string Name => name; public string[] InheritParts => inhParts; public string Inherit => inh; public CrossLangClassEntry[] Entries => ents; } public enum CrossLangClassEntryModifier { Private, Protected, Public, Static } public class CrossLangClassEntry(bool isAbstract, bool isFunction, CrossLangClassEntryModifier modifier, string documentation, string name, string[] args, uint chunkId) { public bool IsAbstract => isAbstract; public bool IsFunction => isFunction; public CrossLangClassEntryModifier Modifier => modifier; public string Documentation => documentation; public string Name => name; public string[] Arguments => args; public uint ChunkId => chunkId; /* Ensure(stream,main_header,1); uint8_t sig = main_header[0]; ent.isAbstract = (sig & 0b00001000) != 0; ent.isFunction = (sig & 0b00000100) == 0; ent.modifier = (TClassModifier)(sig & 3); ent.documentation = GetString(stream); ent.name = GetString(stream); uint32_t arglen = EnsureInt(stream); for(uint32_t l = 0; l < arglen; l++) ent.args.push_back(GetString(stream)); ent.chunkId = EnsureInt(stream); cls.entry.push_back(ent); */ } public class CrossLangFunction(string[] nameParts, uint chunkId) { public string[] NameParts => nameParts; public uint ChunkId => chunkId; } public class CrossLangConfig { [JsonProperty("name")] public string? Name { get; set; } [JsonProperty("version")] public CrossLangVersion Version { get; set; } [JsonProperty("dependencies")] public List Dependencies { get; set; } = new List(); [JsonProperty("info")] public CrossLangInfo Info { get; set; } = new CrossLangInfo(); [JsonProperty("project_dependencies")] public List ProjectDependencies { get; set; } = new List(); [JsonProperty("icon")] public string? Icon { get; set; } } public class CrossLangInfo { [JsonProperty("maintainer")] public string? Maintainer { get; set; } //Not checked when building project [JsonProperty("type")] public string? Type { get; set; } //gets checked whether it is compile_tool by build tool, possible values are in file: project_types.txt [JsonProperty("repo")] public string? Repoository { get; set; } //optional, is the place where the code is stored [JsonProperty("homepage")] public string? HomePage { get; set; } //optional, is the website for the project [JsonProperty("license")] public string? License { get; set; } //optional, but recommended to tell people what the license is [JsonProperty("description")] public string? Description { get; set; } //optional but tells people about the package [JsonProperty("short_name")] public string? ShortName { get; set; } public string? ToolName { get; set; } [JsonProperty("short_name_pretty")] public string? ShortNamePretty { get; set; } [JsonProperty("template_project_dependencies")] public JToken? TemplateDependencies { get; set; } [JsonProperty("template_info")] public JToken? TemplateInfo { get; set; } [JsonProperty("template_extra_text_ftles")] public JToken? TemplateExtraTextFiles { get; set; } [JsonProperty("template_ignored_files")] public JToken? TemplateIgnoredFiles { get; set; } } public class CrossLangDependency { [JsonProperty("name")] public string Name { get; set; } = ""; [JsonProperty("version")] public CrossLangVersion Version { get; set; } } public enum CrossLangVersionStage { Development = 0, Alpha = 1, Beta = 2, Production = 3 } public class CrossLangVersionConverter : Newtonsoft.Json.JsonConverter { public override CrossLangVersion ReadJson(JsonReader reader, Type objectType, CrossLangVersion existingValue, bool hasExistingValue, JsonSerializer serializer) { string? s = reader.Value as string; if (!string.IsNullOrWhiteSpace(s)) { if (CrossLangVersion.TryParse(s, out var version)) return version; } if (hasExistingValue) return existingValue; return new CrossLangVersion(); } public override void WriteJson(JsonWriter writer, CrossLangVersion value, JsonSerializer serializer) { writer.WriteValue(value.ToString()); } } [Newtonsoft.Json.JsonConverter(typeof(CrossLangVersionConverter))] public struct CrossLangVersion : IComparable, IComparable { public void CopyTo(byte[] data, int offset = 0) { if (offset + 5 > data.Length) throw new IndexOutOfRangeException("Cannot write outside of array"); data[offset + 0] = Major; data[offset + 1] = Minor; data[offset + 2] = Patch; data[offset + 3] = (byte)(BuildAndStage >> 8); data[offset + 4] = (byte)(BuildAndStage & 0xFF); } public byte[] ToArray() { byte[] data = new byte[5]; CopyTo(data); return data; } public static bool TryParse(string str, out CrossLangVersion version) { var dashPart = str.Split(new char[] { '-' }, 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); byte major = 1; byte minor = 0; byte patch = 0; ushort build = 0; CrossLangVersionStage stage = CrossLangVersionStage.Development; if (dashPart.Length >= 1) { var dotPart = dashPart[0].Split(new char[] { '.' }, 4, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); if (dotPart.Length > 0) { if (!byte.TryParse(dotPart[0], out major)) { version = new CrossLangVersion(); return false; } if (dotPart.Length > 1 && !byte.TryParse(dotPart[1], out minor)) { version = new CrossLangVersion(); return false; } if (dotPart.Length > 2 && !byte.TryParse(dotPart[2], out patch)) { version = new CrossLangVersion(); return false; } if (dotPart.Length > 3 && !ushort.TryParse(dotPart[3], out build)) { version = new CrossLangVersion(); return false; } if (dashPart.Length == 2) { switch (dashPart[1]) { case "dev": stage = CrossLangVersionStage.Development; break; case "alpha": stage = CrossLangVersionStage.Alpha; break; case "beta": stage = CrossLangVersionStage.Beta; break; case "prod": stage = CrossLangVersionStage.Production; break; default: version = new CrossLangVersion(); return false; } } version = new CrossLangVersion(major, minor, patch, build, stage); return true; } } version = new CrossLangVersion(); return false; } public CrossLangVersion() { } public CrossLangVersion(byte major, byte minor, byte patch, ushort build, CrossLangVersionStage stage) { Major = major; Minor = minor; Patch = patch; Build = build; Stage = stage; } public CrossLangVersion(byte[] data, int offset = 0) { if (offset + 5 > data.Length) return; Major = data[offset + 0]; Minor = data[offset + 1]; Patch = data[offset + 2]; ushort build = (ushort)(data[offset + 3] << 8 | data[offset + 4]); Build = (ushort)(build >> 2); Stage = (CrossLangVersionStage)(build & 3); } public byte Major { get; set; } = 1; public byte Minor { get; set; } = 0; public byte Patch { get; set; } = 0; public ushort Build { get; set; } = 0; public static CrossLangVersion OnePointZeroProd => new CrossLangVersion() { Major = 1, Minor = 0, Patch = 0, Build = 0, Stage = CrossLangVersionStage.Production }; public CrossLangVersionStage Stage { get; set; } = CrossLangVersionStage.Development; public ushort BuildAndStage => (ushort)(Build << 2 | (ushort)Stage); public ulong AsInteger => (ulong)((ulong)Major << 32 | (ulong)Minor << 24 | (ulong)Patch << 16 | (ulong)BuildAndStage); public int CompareTo(CrossLangVersion other) { return AsInteger.CompareTo(other.AsInteger); } public int CompareTo(object? obj) { if (obj is CrossLangVersion oth) return CompareTo(oth); return 1; } public override string ToString() { string stage = "dev"; switch (Stage) { case CrossLangVersionStage.Development: stage = "dev"; break; case CrossLangVersionStage.Alpha: stage = "alpha"; break; case CrossLangVersionStage.Beta: stage = "beta"; break; case CrossLangVersionStage.Production: stage = "prod"; break; } return $"{Major}.{Minor}.{Patch}.{Build}-{stage}"; } }