Prexonite, a .NET hosted scripting language with a focus on meta-programming and embedded DSLs
0

Configure Feed

Select the types of activity you want to include in your feed.

PRX-48: single-file prx binary

Enable single-file (runtime-included) prx binary (~66MB).

Also:
- implement reading of files from embedded resources for `build does add` and `build does require`.

authored by

Christian Klauser and committed by
Christian Klauser
(Apr 6, 2022, 8:59 PM +0200) 81e437a1 a1694c31

+426 -236
+15 -1
Prexonite/Commands/Core/LoadAssembly.cs
··· 74 74 throw new FileNotFoundException("Prexonite can't load assembly located in " + 75 75 path); 76 76 77 - eng.RegisterAssembly(Assembly.LoadFile(asmFile.FullName)); 77 + Assembly assembly; 78 + if (asmFile is FileSpec { FullName: var asmFilePath }) 79 + { 80 + // Use file path based loading to give the CLR file system context. 81 + assembly = Assembly.LoadFrom(asmFilePath); 82 + } 83 + else 84 + { 85 + using var stream = asmFile.OpenStream(); 86 + using var buf = new MemoryStream(); 87 + stream.CopyTo(buf); 88 + assembly = Assembly.Load(buf.GetBuffer()); 89 + } 90 + 91 + eng.RegisterAssembly(assembly); 78 92 } 79 93 80 94 return PType.Null.CreatePValue();
+1
Prexonite/Compiler/Build/ITarget.cs
··· 63 63 IReadOnlyCollection<Message> Messages { get; } 64 64 65 65 [PublicAPI] 66 + [CanBeNull] 66 67 Exception Exception { get; } 67 68 68 69 bool IsSuccessful { get; }
+2 -2
Prexonite/Compiler/Build/Internal/SelfAssemblingPlan.cs
··· 166 166 { 167 167 refSpec.Source = new FileSource(refSpec.ResolvedPath, Encoding); 168 168 await Task.Yield(); // Need to yield at this point to keep 169 - // the critical section of the cache short 169 + // the critical section of the cache update short 170 170 return await _performPreflight(refSpec, actualToken); 171 171 }, token); 172 172 } ··· 256 256 } 257 257 258 258 [NotNull] 259 - private static readonly Regex _fileReferencePattern = new(@"^(\.|/|:)"); 259 + private static readonly Regex _fileReferencePattern = new(@"^([/.]|[a-zA-Z]:)"); 260 260 261 261 private static RefSpec _parseRefSpec(MetaEntry entry) 262 262 {
+5 -5
Prexonite/Compiler/Build/Plan.cs
··· 41 41 /// <summary>List of modules included in the Prexonite assembly. Does not include 'sys' itself.</summary> 42 42 private static readonly ISource[] _stdLibModules = 43 43 { 44 - Source.FromEmbeddedResource("prxlib.prx.prim.pxs"), 45 - Source.FromEmbeddedResource("prxlib.prx.core.pxs"), 46 - Source.FromEmbeddedResource("prxlib.sys.pxs"), 47 - Source.FromEmbeddedResource("prxlib.prx.v1.pxs"), 48 - Source.FromEmbeddedResource("prxlib.prx.v1.prelude.pxs") 44 + Source.FromEmbeddedPrexoniteResource("prxlib.prx.prim.pxs"), 45 + Source.FromEmbeddedPrexoniteResource("prxlib.prx.core.pxs"), 46 + Source.FromEmbeddedPrexoniteResource("prxlib.sys.pxs"), 47 + Source.FromEmbeddedPrexoniteResource("prxlib.prx.v1.pxs"), 48 + Source.FromEmbeddedPrexoniteResource("prxlib.prx.v1.prelude.pxs") 49 49 }; 50 50 51 51 public static IPlan CreateDefault()
+7 -1
Prexonite/Compiler/Build/Source.cs
··· 76 76 return FromStream(new MemoryStream(data, false), encoding, false); 77 77 } 78 78 79 - public static ISource FromEmbeddedResource([NotNull] string name) 79 + public static ISource FromEmbeddedPrexoniteResource([NotNull] string name) 80 80 { 81 81 if (name == null) throw new ArgumentNullException(nameof(name)); 82 82 return new EmbeddedResourceSource(Assembly.GetExecutingAssembly(), "Prexonite." + name); 83 + } 84 + 85 + public static ISource FromEmbeddedResource([NotNull] Assembly assembly, [NotNull] string name) 86 + { 87 + if (name == null) throw new ArgumentNullException(nameof(name)); 88 + return new EmbeddedResourceSource(assembly, name); 83 89 } 84 90 85 91 [NotNull]
+63
Prexonite/Compiler/ISourceSpec.cs
··· 1 + #nullable enable 2 + using System; 3 + using System.IO; 4 + using System.Reflection; 5 + using System.Text; 6 + using Prexonite.Compiler.Build; 7 + 8 + namespace Prexonite.Compiler; 9 + 10 + /// <summary> 11 + /// A retro-fitted precursor to <see cref="ISource"/> (<see cref="Source"/>). It is used to support including 12 + /// Prexonite Script files embedded as resources. Modern code should prefer using modules and the build system 13 + /// (with one module per embedded resource). 14 + /// </summary> 15 + /// <seealso cref="Plan"/> 16 + public interface ISourceSpec 17 + { 18 + public string FullName { get; } 19 + public string ShortName { get; } 20 + public Stream OpenStream(); 21 + public string? LoadPath { get; } 22 + 23 + public ISource ToSource(); 24 + 25 + public bool Exists(); 26 + } 27 + 28 + public sealed record FileSpec(FileInfo File) : ISourceSpec 29 + { 30 + public FileSpec(string path) : this(new FileInfo(path)) { } 31 + 32 + public string FullName => File.FullName; 33 + public string ShortName => File.Name; 34 + public string? LoadPath => File.DirectoryName; 35 + 36 + public Stream OpenStream() => new FileStream( 37 + File.FullName, 38 + FileMode.Open, 39 + FileAccess.Read, 40 + FileShare.Read, 41 + 4 * 1024, 42 + FileOptions.SequentialScan); 43 + 44 + public ISource ToSource() => Source.FromFile(File, Encoding.UTF8); 45 + public bool Exists() => System.IO.File.Exists(FullName); 46 + } 47 + 48 + public sealed record ResourceSpec(Assembly ResourceAssembly, string Name) : ISourceSpec 49 + { 50 + public const string Prefix = "resource:"; 51 + public string FullName => $"{Prefix}{ResourceAssembly.FullName ?? "<unknown assembly>"}:{Name}"; 52 + string ISourceSpec.ShortName => Name; 53 + 54 + public Stream OpenStream() => ResourceAssembly.GetManifestResourceStream(Name) 55 + ?? throw new InvalidOperationException($"Assembly {ResourceAssembly.FullName} does not " + 56 + $"contain a resource '{Name}' (or it is not accessible)."); 57 + 58 + public string? LoadPath => null; 59 + 60 + public ISource ToSource() => Source.FromEmbeddedResource(ResourceAssembly, Name); 61 + 62 + public bool Exists() => ResourceAssembly.GetManifestResourceInfo(Name) != null; 63 + }
+102
Prexonite/Compiler/Internal/AssemblyResolver.cs
··· 1 + #nullable enable 2 + using System; 3 + using System.Collections.Concurrent; 4 + using System.Diagnostics; 5 + using System.Linq; 6 + using System.Reflection; 7 + 8 + namespace Prexonite.Compiler.Internal; 9 + 10 + internal sealed class AssemblyResolver : ICloneable 11 + { 12 + private readonly ConcurrentDictionary<AssemblyName, Assembly> _assemblies = new(); 13 + private readonly ConcurrentDictionary<string, Assembly> _cache = new(); 14 + 15 + public Assembly Resolve(string name) => TryResolve(name) ?? 16 + throw new PrexoniteException($"Could not resolve assembly by name '{name}'. Is it loaded?"); 17 + 18 + public Assembly? TryResolve(string name) 19 + { 20 + // as an optimization, check if there is a cache hit 21 + if (_cache.TryGetValue(name, out var cacheHit)) 22 + { 23 + return cacheHit; 24 + } 25 + 26 + // try to resolve the assembly (note that we can't use GetOrAdd because the resolution might fail) 27 + Assembly? resolvedAssembly = null; 28 + foreach (var assembly in _assemblies.Values) 29 + { 30 + if (Engine.DefaultStringComparer.Equals(assembly.FullName, name) 31 + || assembly.GetName() is var otherName && 32 + (Engine.DefaultStringComparer.Equals(otherName.Name, name) 33 + || Engine.DefaultStringComparer.Equals($"{otherName.Name},{otherName.Version}", name))) 34 + { 35 + resolvedAssembly = assembly; 36 + break; 37 + } 38 + } 39 + 40 + return resolvedAssembly == null ? resolvedAssembly : _cache.GetOrAdd(name, resolvedAssembly); 41 + } 42 + 43 + /// <summary> 44 + /// Determines whether an assembly is already registered for use by the Prexonite VM. 45 + /// </summary> 46 + /// <param name = "ass">An assembly reference.</param> 47 + /// <returns>True if the supplied assembly is registered; false otherwise.</returns> 48 + [DebuggerStepThrough] 49 + public bool Contains(Assembly? ass) => ass != null && _assemblies.ContainsKey(ass.GetName()); 50 + 51 + /// <summary> 52 + /// Gets a list of all registered assemblies. 53 + /// </summary> 54 + /// <returns>A copy of the list of registered assemblies.</returns> 55 + [DebuggerStepThrough] 56 + public Assembly[] ToArray() => _assemblies.Values.ToArray(); 57 + 58 + /// <summary> 59 + /// Registers a new assembly for use by the Prexonite VM. 60 + /// </summary> 61 + /// <param name = "ass">An assembly reference.</param> 62 + /// <exception cref = "ArgumentNullException"><paramref name = "ass" /> is null.</exception> 63 + [DebuggerStepThrough] 64 + public void Add(Assembly ass) 65 + { 66 + if (ass == null) 67 + throw new ArgumentNullException(nameof(ass)); 68 + _assemblies.GetOrAdd(ass.GetName(), ass); 69 + } 70 + 71 + /// <summary> 72 + /// Removes an assembly from the list registered ones. 73 + /// </summary> 74 + /// <param name = "ass">The assembly to remove.</param> 75 + /// <exception cref = "ArgumentNullException"><paramref name = "ass" /> is null.</exception> 76 + [DebuggerStepThrough] 77 + public void Remove(Assembly ass) 78 + { 79 + if (ass == null) 80 + throw new ArgumentNullException(nameof(ass)); 81 + _assemblies.TryRemove(ass.GetName(), out _); 82 + foreach (var (shortName, cached) in _cache) 83 + { 84 + if (ass == cached) 85 + { 86 + _cache.TryRemove(shortName, out _); 87 + } 88 + } 89 + } 90 + 91 + public AssemblyResolver Clone() 92 + { 93 + var copy = new AssemblyResolver(); 94 + copy._assemblies.AddRange(_assemblies); 95 + // NOTE: we intentionally don't copy the cache. The cache is an optimization that should remain tied to 96 + // a single engine. The clone should not copy over cache entries (which might never be relevant to the 97 + // clone). 98 + return copy; 99 + } 100 + 101 + object ICloneable.Clone() => Clone(); 102 + }
+61
Prexonite/Compiler/Internal/PathSet.cs
··· 1 + using System; 2 + using System.Collections; 3 + using System.Collections.Generic; 4 + using System.IO; 5 + 6 + namespace Prexonite.Compiler.Internal; 7 + 8 + /// <summary> 9 + /// A set of file system paths. Tries to normalize paths (on a best-effort basis). 10 + /// </summary> 11 + /// <remarks> 12 + /// This class is aware of <see cref="ResourceSpec"/>.</remarks> 13 + internal sealed class PathSet : ICollection<string> 14 + { 15 + private readonly HashSet<string> _paths = new(); 16 + public IEnumerator<string> GetEnumerator() 17 + { 18 + return _paths.GetEnumerator(); 19 + } 20 + 21 + IEnumerator IEnumerable.GetEnumerator() 22 + { 23 + return ((IEnumerable)_paths).GetEnumerator(); 24 + } 25 + 26 + public void Add(string path) => _paths.Add(canonical(path)); 27 + 28 + public void Clear() 29 + { 30 + _paths.Clear(); 31 + } 32 + 33 + public bool Contains(string path) => _paths.Contains(canonical(path)); 34 + 35 + public void CopyTo(string[] array, int arrayIndex) 36 + { 37 + _paths.CopyTo(array, arrayIndex); 38 + } 39 + 40 + public bool Remove(string path) => _paths.Remove(canonical(path)); 41 + 42 + public int Count => _paths.Count; 43 + 44 + bool ICollection<string>.IsReadOnly => ((ICollection<string>)_paths).IsReadOnly; 45 + 46 + private static string canonical(string path) 47 + { 48 + if (!path.StartsWith(ResourceSpec.Prefix)) 49 + { 50 + path = Path.GetFullPath(path); 51 + if (OperatingSystem.IsMacOS() || OperatingSystem.IsWindows()) 52 + { 53 + // PRX-58 This generalization is of course not correct, but there currently is no convenient API 54 + // to get a canonical path. 55 + path = path.ToUpperInvariant(); 56 + } 57 + } 58 + 59 + return path; 60 + } 61 + }
+59 -35
Prexonite/Compiler/Loader.cs
··· 48 48 using Prexonite.Compiler.Symbolic.Internal; 49 49 using Prexonite.Internal; 50 50 using Prexonite.Modular; 51 - using Prexonite.Properties; 52 51 using Prexonite.Types; 53 52 using Debug = System.Diagnostics.Debug; 54 53 ··· 255 254 256 255 var target = new CompilerTarget(this, func,parentTarget,sourcePosition); 257 256 if (_functionTargets.ContainsKey(func.Id) && 258 - (!ParentApplication.Meta.GetDefault(Application.AllowOverridingKey, true).Switch)) 257 + !ParentApplication.Meta.GetDefault(Application.AllowOverridingKey, true).Switch) 259 258 throw new PrexoniteException( 260 259 $"The application {ParentApplication.Id} does not allow overriding of function {func.Id}."); 261 260 ··· 665 664 if (filePath != null) 666 665 { 667 666 lex.File = filePath; 668 - LoadedFiles.Add(Path.GetFullPath(filePath)); 667 + loadedFiles.Add(filePath); 669 668 } 670 669 671 670 _load(lex); ··· 711 710 _loadFromFile(file); 712 711 } 713 712 714 - private void _loadFromFile(FileInfo file) 713 + private void _loadFromFile(ISourceSpec file) 715 714 { 716 715 if (file == null) 717 716 throw new ArgumentNullException(nameof(file)); 718 - LoadedFiles.Add(file.FullName); 719 - LoadPaths.Push(file.DirectoryName); 717 + loadedFiles.Add(file.FullName); 718 + if (file.LoadPath is { } loadPath) 719 + { 720 + LoadPaths.Push(loadPath); 721 + } 722 + 720 723 try 721 724 { 722 - using Stream str = new FileStream( 723 - file.FullName, 724 - FileMode.Open, 725 - FileAccess.Read, 726 - FileShare.Read, 727 - 4 * 1024, 728 - FileOptions.SequentialScan); 725 + using var stream = file.OpenStream(); 729 726 #if DEBUG 730 727 var indent = new StringBuilder(_loadIndent); 731 728 indent.Append(' ', 2 * (_loadIndent++)); 732 - Console.WriteLine(Resources.Loader__begin_compiling, file.Name, indent, file.FullName); 729 + Console.WriteLine(Properties.Resources.Loader__begin_compiling, file.ShortName, indent, file.FullName); 733 730 #endif 734 - _loadFromStream(str, file.Name); 731 + _loadFromStream(stream, file.FullName); 735 732 #if DEBUG 736 - Console.WriteLine(Resources.Loader__end_compiling, file.Name, indent); 733 + Console.WriteLine(Properties.Resources.Loader__end_compiling, file.ShortName, indent); 737 734 _loadIndent--; 738 735 #endif 739 736 } 740 737 finally 741 738 { 742 - LoadPaths.Pop(); 739 + if (file.LoadPath != null) 740 + { 741 + LoadPaths.Pop(); 742 + } 743 743 } 744 744 } 745 745 ··· 753 753 return; 754 754 } 755 755 756 - if (LoadedFiles.Contains(file.FullName)) 756 + if (loadedFiles.Contains(file.FullName)) 757 757 return; 758 758 759 759 _loadFromFile(file); ··· 894 894 } 895 895 896 896 private static readonly string _imageLocation = 897 - (new FileInfo(Assembly.GetExecutingAssembly().Location)).DirectoryName; 897 + new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName; 898 898 899 - public FileInfo ApplyLoadPaths(string pathSuffix) 899 + /// <summary> 900 + /// Tries to find the indicated source file by applying the current load paths. 901 + /// </summary> 902 + /// <param name="pathSuffix">The file to search for.</param> 903 + /// <returns>The source specification, if a source could be found; <c>null</c> otherwise.</returns> 904 + /// <exception cref="ArgumentNullException"><paramref name="pathSuffix"/> is null</exception> 905 + /// <exception cref="ArgumentException">An incorrectly formatted file name. This primarily applies to embedded <c>resource:</c> paths.</exception> 906 + [CanBeNull] 907 + public ISourceSpec ApplyLoadPaths(string pathSuffix) 900 908 { 901 909 if (pathSuffix == null) 902 910 throw new ArgumentNullException(nameof(pathSuffix)); 911 + if (pathSuffix.StartsWith("resource:")) 912 + { 913 + var parts = pathSuffix.Split(":", 3); 914 + if (parts.Length < 3) 915 + { 916 + throw new ArgumentException( 917 + $"A resource: file path needs to have the form `resource:{{assembly}}:{{name}}`. Got '{pathSuffix}' instead."); 918 + } 919 + var assembly = ParentEngine.TryResolveAssembly(parts[1]); 920 + if (assembly == null) 921 + { 922 + Trace.WriteLine($"Could not resolve assembly by name '{parts[1]}'. Is it loaded?"); 923 + return null; 924 + } 925 + 926 + var spec = new ResourceSpec(assembly, parts[2]); 927 + return spec.Exists() ? spec : null; 928 + } 929 + 903 930 var path = pathSuffix.Replace(DirectorySeparator, Path.DirectorySeparatorChar); 904 931 905 932 //Try to find in process environment 906 933 if (File.Exists(path)) 907 - return new FileInfo(path); 934 + return new FileSpec(path); 908 935 909 936 //Try to find in load paths 910 937 foreach (var pathPrefix in LoadPaths) 911 - if (File.Exists((path = Path.Combine(pathPrefix, pathSuffix)))) 912 - return new FileInfo(path); 938 + if (File.Exists(path = Path.Combine(pathPrefix, pathSuffix))) 939 + return new FileSpec(path); 913 940 914 941 //Try to find in engine paths 915 942 foreach (var pathPrefix in ParentEngine.Paths) 916 - if (File.Exists((path = Path.Combine(pathPrefix, pathSuffix)))) 917 - return new FileInfo(path); 943 + if (File.Exists(path = Path.Combine(pathPrefix, pathSuffix))) 944 + return new FileSpec(path); 918 945 919 946 //Try to find in current directory 920 - if (File.Exists((path = Path.Combine(Environment.CurrentDirectory, pathSuffix)))) 921 - return new FileInfo(path); 947 + if (File.Exists(path = Path.Combine(Environment.CurrentDirectory, pathSuffix))) 948 + return new FileSpec(path); 922 949 923 950 //Try to find next to image 924 - if (File.Exists((path = Path.Combine(_imageLocation, pathSuffix)))) 925 - return new FileInfo(path); 951 + if (File.Exists(path = Path.Combine(_imageLocation, pathSuffix))) 952 + return new FileSpec(path); 926 953 927 954 //Not found 928 955 return null; 929 956 } 930 957 931 - public SymbolCollection LoadedFiles { get; } = new(); 958 + private PathSet loadedFiles { get; } = new(); 932 959 933 960 #endregion 934 961 ··· 1035 1062 _throwCannotFindScriptFile(path); 1036 1063 return PType.Null; 1037 1064 } 1038 - if (LoadedFiles.Contains(file.FullName)) 1065 + if (loadedFiles.Contains(file.FullName)) 1039 1066 allLoaded = false; 1040 1067 else 1041 1068 _loadFromFile(file); ··· 1049 1076 delegate 1050 1077 { 1051 1078 var defaultFile = ApplyLoadPaths(DefaultScriptName); 1052 - if (defaultFile == null) 1053 - return DefaultScriptName; 1054 - else 1055 - return defaultFile.FullName; 1079 + return defaultFile?.FullName ?? DefaultScriptName; 1056 1080 }); 1057 1081 1058 1082 BuildCommands.AddCompilerCommand(
+27 -27
Prexonite/Engine.cs
··· 14 14 using Prexonite.Commands.List; 15 15 using Prexonite.Commands.Math; 16 16 using Prexonite.Commands.Text; 17 + using Prexonite.Compiler.Internal; 17 18 using Prexonite.Types; 18 19 using Char = Prexonite.Commands.Core.Char; 19 20 using Debug = Prexonite.Commands.Core.Debug; ··· 481 482 482 483 #region Assembly management 483 484 484 - private readonly List<Assembly> _registeredAssemblies; 485 + private readonly AssemblyResolver _registeredAssemblies; 485 486 486 487 /// <summary> 487 488 /// Determines whether an assembly is already registered for use by the Prexonite VM. ··· 489 490 /// <param name = "ass">An assembly reference.</param> 490 491 /// <returns>True if the supplied assembly is registered; false otherwise.</returns> 491 492 [DebuggerStepThrough] 492 - public bool IsAssemblyRegistered(Assembly? ass) 493 - { 494 - if (ass == null) 495 - return false; 496 - return _registeredAssemblies.Contains(ass); 497 - } 493 + public bool IsAssemblyRegistered(Assembly? ass) => _registeredAssemblies.Contains(ass); 498 494 499 495 /// <summary> 500 496 /// Gets a list of all registered assemblies. 501 497 /// </summary> 502 498 /// <returns>A copy of the list of registered assemblies.</returns> 503 499 [DebuggerStepThrough] 504 - public Assembly[] GetRegisteredAssemblies() 505 - { 506 - return _registeredAssemblies.ToArray(); 507 - } 500 + public Assembly[] GetRegisteredAssemblies() => _registeredAssemblies.ToArray(); 508 501 509 502 /// <summary> 510 503 /// Registers a new assembly for use by the Prexonite VM. ··· 512 505 /// <param name = "ass">An assembly reference.</param> 513 506 /// <exception cref = "ArgumentNullException"><paramref name = "ass" /> is null.</exception> 514 507 [DebuggerStepThrough] 515 - public void RegisterAssembly(Assembly? ass) 516 - { 517 - if (ass == null) 518 - throw new ArgumentNullException(nameof(ass)); 519 - if (!_registeredAssemblies.Contains(ass)) 520 - _registeredAssemblies.Add(ass); 521 - } 508 + public void RegisterAssembly(Assembly ass) => _registeredAssemblies.Add(ass); 522 509 523 510 /// <summary> 524 511 /// Removes an assembly from the list registered ones. ··· 526 513 /// <param name = "ass">The assembly to remove.</param> 527 514 /// <exception cref = "ArgumentNullException"><paramref name = "ass" /> is null.</exception> 528 515 [DebuggerStepThrough] 529 - public void RemoveAssembly(Assembly? ass) 530 - { 531 - if (ass == null) 532 - throw new ArgumentNullException(nameof(ass)); 533 - if (_registeredAssemblies.Contains(ass)) 534 - _registeredAssemblies.Remove(ass); 535 - } 516 + public void RemoveAssembly(Assembly ass) => _registeredAssemblies.Remove(ass); 517 + 518 + /// <summary> 519 + /// Tries to find a registered assembly with a matching name. Matches fully qualified assembly names, simple names 520 + /// and <c>{name},{version}</c>. 521 + /// </summary> 522 + /// <param name="name">The name to find a matching assembly for.</param> 523 + /// <returns>Either the matching assembly or <c>null</c> if no matching assembly cna be found.</returns> 524 + public Assembly? TryResolveAssembly(string name) => _registeredAssemblies.TryResolve(name); 525 + 526 + /// <summary> 527 + /// Tries to find a registered assembly with a matching name. Matches fully qualified assembly names, simple names 528 + /// and <c>{name},{version}</c>. 529 + /// </summary> 530 + /// <param name="name">The name to find a matching assembly for.</param> 531 + /// <returns>Either the matching assembly.</returns> 532 + /// <exception cref="PrexoniteVersion">If no matching assembly can be found.</exception> 533 + public Assembly ResolveAssembly(string name) => _registeredAssemblies.Resolve(name); 536 534 537 535 #endregion 538 536 ··· 620 618 }; 621 619 622 620 //Assembly registry 623 - _registeredAssemblies = new List<Assembly>(); 621 + _registeredAssemblies = new(); 622 + var prexoniteAssembly = Assembly.GetAssembly(typeof(Engine))!; 623 + _registeredAssemblies.Add(prexoniteAssembly); 624 624 foreach ( 625 625 var assName in Assembly.GetExecutingAssembly().GetReferencedAssemblies()) 626 626 _registeredAssemblies.Add(Assembly.Load(assName.FullName)); ··· 821 821 _pTypeRegistry = new SymbolTable<Type>(prototype._pTypeRegistry.Count); 822 822 _pTypeRegistry.AddRange(prototype._pTypeRegistry); 823 823 PTypeRegistry = new PTypeRegistryIterator(this); 824 - _registeredAssemblies = new List<Assembly>(prototype._registeredAssemblies); 824 + _registeredAssemblies = prototype._registeredAssemblies.Clone(); 825 825 Commands = new CommandTable(); 826 826 Commands.AddRange(prototype.Commands); 827 827
+9 -9
PrexoniteTests/Tests/Configurations/ModuleCache.cs
··· 72 72 { 73 73 var moduleName = new ModuleName("prx.v1", new Version(0, 0)); 74 74 var desc = Cache.CreateDescription(moduleName, 75 - Source.FromEmbeddedResource("prxlib.prx.v1.prelude.pxs"), 75 + Source.FromEmbeddedPrexoniteResource("prxlib.prx.v1.prelude.pxs"), 76 76 "prxlib/prx.v1.prelude.pxs", 77 77 Enumerable.Empty<ModuleName>()); 78 78 Cache.TargetDescriptions.Add(desc); ··· 89 89 _legacySymbolsDescription ??= _loadLegacySymbols(); 90 90 // ReSharper restore InconsistentNaming 91 91 92 - private static ITargetDescription StdlibDescription => _stdlibDescription ??= _loadStdlib(); 92 + private static ITargetDescription stdlibDescription => _stdlibDescription ??= _loadStdlib(); 93 93 94 94 private static ITargetDescription _loadStdlib() 95 95 { 96 96 try 97 97 { 98 98 var v1Name = new ModuleName("prx", new Version(1, 0)); 99 - var v1Desc = Cache.CreateDescription(v1Name, Source.FromEmbeddedResource("prxlib.prx.v1.pxs"), 99 + var v1Desc = Cache.CreateDescription(v1Name, Source.FromEmbeddedPrexoniteResource("prxlib.prx.v1.pxs"), 100 100 "prxlib/prx.v1.pxs", Enumerable.Empty<ModuleName>()); 101 101 Cache.TargetDescriptions.Add(v1Desc); 102 102 103 103 var primName = new ModuleName("prx.prim", new Version(0, 0)); 104 - var primDesc = Cache.CreateDescription(primName, Source.FromEmbeddedResource("prxlib.prx.prim.pxs"), 104 + var primDesc = Cache.CreateDescription(primName, Source.FromEmbeddedPrexoniteResource("prxlib.prx.prim.pxs"), 105 105 "prxlib/prx.prim.pxs", Enumerable.Empty<ModuleName>()); 106 106 Cache.TargetDescriptions.Add(primDesc); 107 107 108 108 var coreName = new ModuleName("prx.core", new Version(0, 0)); 109 - var coreDesc = Cache.CreateDescription(coreName, Source.FromEmbeddedResource("prxlib.prx.core.pxs"), 109 + var coreDesc = Cache.CreateDescription(coreName, Source.FromEmbeddedPrexoniteResource("prxlib.prx.core.pxs"), 110 110 "prxlib/prx.core.pxs", primName.Singleton()); 111 111 Cache.TargetDescriptions.Add(coreDesc); 112 112 113 113 var sysName = new ModuleName("sys", new Version(0, 0)); 114 - var desc = Cache.CreateDescription(sysName, Source.FromEmbeddedResource("prxlib.sys.pxs"), "prxlib/sys.pxs", 114 + var desc = Cache.CreateDescription(sysName, Source.FromEmbeddedPrexoniteResource("prxlib.sys.pxs"), "prxlib/sys.pxs", 115 115 new[]{ primName, coreName }); 116 116 Cache.TargetDescriptions.Add(desc); 117 117 ··· 149 149 var dependencies = script.Dependencies ?? Enumerable.Empty<string>(); 150 150 151 151 var file = environment.ApplyLoadPaths(path); 152 - if (file == null || !file.Exists) 152 + if (file == null) 153 153 throw new PrexoniteException($"Cannot find script {path}."); 154 154 155 155 var moduleName = new ModuleName(Path.GetFileNameWithoutExtension(path), new Version(0, 0)); ··· 167 167 new ModuleName(Path.GetFileNameWithoutExtension(dep), new Version(0, 0))).ToArray(); 168 168 169 169 // Manually add legacy symbol and stdlib dependencies 170 - var effectiveDependencies = dependencyNames.Append(LegacySymbolsDescription.Name).Append(StdlibDescription.Name); 171 - var desc = Cache.CreateDescription(moduleName, Source.FromFile(file,Encoding.UTF8), path, effectiveDependencies); 170 + var effectiveDependencies = dependencyNames.Append(LegacySymbolsDescription.Name).Append(stdlibDescription.Name); 171 + var desc = Cache.CreateDescription(moduleName, file.ToSource(), path, effectiveDependencies); 172 172 _trace.TraceEvent(TraceEventType.Information, 0, 173 173 "Adding new target description for cache on thread {0}: {1}.", Thread.CurrentThread.ManagedThreadId, 174 174 desc);
+1 -1
PrexoniteTests/Tests/EmbeddedPrxLibTests.cs
··· 42 42 43 43 private static void _checkEmbeddedResource(string name) 44 44 { 45 - Assert.True(Source.FromEmbeddedResource(name).TryOpen(out var reader), $"Cannot open {name}"); 45 + Assert.True(Source.FromEmbeddedPrexoniteResource(name).TryOpen(out var reader), $"Cannot open {name}"); 46 46 var contents = reader.ReadToEnd(); 47 47 Assert.That(contents, Is.Not.Null); 48 48 Assert.That(contents.Length, Is.GreaterThan(100));
+40
PrexoniteTests/Tests/LoaderTests.cs
··· 2 2 using System.Diagnostics; 3 3 using System.IO; 4 4 using System.Linq; 5 + using System.Reflection; 5 6 using NUnit.Framework; 6 7 using Prexonite; 7 8 using Prexonite.Compiler; ··· 102 103 public void Sub2ForwardSlashRelative() 103 104 { 104 105 _assertFindsFile("./sub1/sub2/sub2.pxs", "sub1", "sub2", "sub2.pxs"); 106 + } 107 + 108 + 109 + [Test] 110 + public void EmbeddedResourceByName() 111 + { 112 + var spec = _ldr.ApplyLoadPaths("resource:Prexonite:Prexonite.prxlib.sys.pxs"); 113 + 114 + Assert.That(spec, Is.Not.Null); 115 + Assert.That(spec, Is.InstanceOf<ResourceSpec>()); 116 + Assert.That(((ResourceSpec) spec).Name, Is.EqualTo("Prexonite.prxlib.sys.pxs")); 117 + Assert.That(((ResourceSpec) spec).ResourceAssembly, Is.SameAs(Assembly.GetAssembly(typeof(Engine)))); 118 + } 119 + 120 + [Test] 121 + public void EmbeddedResourceByFullName() 122 + { 123 + var spec = _ldr.ApplyLoadPaths($"resource:{Assembly.GetAssembly(typeof(Engine))!.FullName}:Prexonite.prxlib.sys.pxs"); 124 + 125 + Assert.That(spec, Is.Not.Null); 126 + Assert.That(spec, Is.InstanceOf<ResourceSpec>()); 127 + Assert.That(((ResourceSpec) spec).Name, Is.EqualTo("Prexonite.prxlib.sys.pxs")); 128 + Assert.That(((ResourceSpec) spec).ResourceAssembly, Is.SameAs(Assembly.GetAssembly(typeof(Engine)))); 129 + } 130 + 131 + [Test] 132 + public void EmbeddedResourceAssemblyNotFound() 133 + { 134 + var spec = _ldr.ApplyLoadPaths($"resource:DoesNotExist:Prexonite.prxlib.sys.pxs"); 135 + 136 + Assert.That(spec, Is.Null); 137 + } 138 + 139 + [Test] 140 + public void EmbeddedResourceNotFound() 141 + { 142 + var spec = _ldr.ApplyLoadPaths($"resource:Prexonite:doesnotexist.pxs"); 143 + 144 + Assert.That(spec, Is.Null); 105 145 } 106 146 107 147 private void _assertFindsFile(string logicalPath, params string[] physicalPathComponents)
+5 -69
Prx/Program.cs
··· 48 48 49 49 internal static class Program 50 50 { 51 - public const string PrxScriptFileName = "prx.pxs"; 51 + public const string PrxScriptFileName = "prx_main.pxs"; 52 52 53 53 public static string GetPrxPath() 54 54 { 55 - return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 55 + return AppContext.BaseDirectory; 56 56 } 57 57 58 58 public static void Main(string[] args) ··· 224 224 var node = e.Stack.Last; 225 225 do 226 226 { 227 - if (node.Value is FunctionContext ectx) 227 + if (node?.Value is FunctionContext ectx) 228 228 { 229 229 if (ReferenceEquals(ectx.Implementation, rctx.Implementation)) 230 230 { ··· 232 232 break; 233 233 } 234 234 } 235 - } while ((node = node.Previous) != null); 235 + } while ((node = node?.Previous) != null); 236 236 237 237 return PType.Null.CreatePValue(); 238 238 }); ··· 288 288 289 289 #endregion 290 290 291 - var bootstrapPath = Path.Combine(prxPath, PrxScriptFileName); 292 - var (entryPath, deleteSrc) = _ensureSourceAvailable(bootstrapPath, prxPath); 293 - 294 291 Tuple<Application, ITarget> result; 295 292 try 296 293 { 297 - var entryDesc = plan.AssembleAsync(Source.FromFile(entryPath, Encoding.UTF8)).Result; 294 + var entryDesc = plan.AssembleAsync(Source.FromEmbeddedResource(Assembly.GetAssembly(typeof(Program))!, PrxScriptFileName)).Result; 298 295 result = plan.Load(entryDesc.Name); 299 296 } 300 297 catch (BuildFailureException e) ··· 329 326 #endif 330 327 } 331 328 332 - if (deleteSrc) 333 - Directory.Delete(Path.Combine(prxPath ,"src"), true); 334 - 335 329 if (result == null) 336 330 { 337 331 return null; ··· 344 338 app.Meta["Version"] = Assembly.GetExecutingAssembly().GetName()!.Version!.ToString(); 345 339 return app; 346 340 347 - } 348 - 349 - private static (string entryPath, bool deleteSrc) _ensureSourceAvailable(string entryPath, string prxPath) 350 - { 351 - if (File.Exists(entryPath)) 352 - return (entryPath, false); 353 - 354 - var srcDirPath = Path.Combine(prxPath, "src"); 355 - //Load default CLI app 356 - entryPath = Path.Combine(srcDirPath, "prx_main.pxs"); 357 - 358 - if (File.Exists(entryPath)) 359 - return (entryPath, false); 360 - 361 - bool deleteSrc; 362 - if (!Directory.Exists(srcDirPath)) 363 - { 364 - var di = 365 - Directory.CreateDirectory(srcDirPath); 366 - di.Attributes |= FileAttributes.Hidden; 367 - deleteSrc = true; 368 - } 369 - else 370 - { 371 - deleteSrc = false; 372 - } 373 - 374 - //Unpack source 375 - var prx = Assembly.GetExecutingAssembly(); 376 - 377 - async Task extractFile(string name) 378 - { 379 - var resourceName = $"Prx.src.{name}"; 380 - var stream = prx.GetManifestResourceStream(resourceName); 381 - if (stream == null) 382 - { 383 - throw new ArgumentException($"Embedded resource '{resourceName}' is missing.", 384 - nameof(name)); 385 - } 386 - 387 - await using (stream) 388 - { 389 - var filePath = Path.Combine(srcDirPath, name); 390 - 391 - // We need the await here so that the state machine can close the streams afterwards 392 - await using (var dest = new FileStream(filePath, FileMode.Create, FileAccess.Write)) 393 - await stream.CopyToAsync(dest, (CancellationToken) default); 394 - } 395 - } 396 - 397 - 398 - Task.WaitAll( 399 - extractFile("prx_main.pxs"), 400 - extractFile("prx_lib.pxs"), 401 - extractFile("prx_interactive.pxs") 402 - ); 403 - 404 - return (entryPath, deleteSrc); 405 341 } 406 342 407 343 private static bool _reportErrors(IEnumerable<Message> messages)
+22 -3
Prx/Prx.csproj
··· 23 23 <Product>Prexonite</Product> 24 24 <Company>$(Product)</Company> 25 25 <NeutralLanguage>en-US</NeutralLanguage> 26 + 27 + <!-- single file app --> 28 + <PublishSingleFile>true</PublishSingleFile> 29 + <SelfContained>true</SelfContained> 30 + <RuntimeIdentifier Condition="$([MSBuild]::IsOSPlatform('Windows'))">win-x64</RuntimeIdentifier> 31 + <RuntimeIdentifier Condition="$([MSBuild]::IsOSPlatform('Linux'))">linux-x64</RuntimeIdentifier> 32 + <!-- Teady to run makes a HUGE difference in terms of startup time. 33 + Reduces startup time from ~820ms down to ~210ms. --> 34 + <PublishReadyToRun>true</PublishReadyToRun> 35 + <!-- While compression can reduce the file size by about 50%, it also almost doubles the overhead for launching the 36 + interpreter (210ms -> 350-450ms) to the point where the delay becomes much more noticable. 37 + --> 38 + <EnableCompressionInSingleFile>false</EnableCompressionInSingleFile> 39 + <!-- Can't use assembly trimming because the Prx interpreter heavily relies on reflection. 40 + Interestingly, there is sufficient overlap to run the basic prx application. 41 + That means: if you are only interested in the compiler and a basic REPL, you can use a 19MB trimmed 42 + application. 43 + --> 44 + <PublishTrimmed>false</PublishTrimmed> 26 45 </PropertyGroup> 27 46 28 47 <ItemGroup> 29 48 <None Remove="src\prx_interactive.pxs" /> 30 49 <None Remove="src\prx_lib.pxs" /> 31 50 <None Remove="src\prx_main.pxs" /> 32 - <EmbeddedResource Include="src\prx_interactive.pxs" /> 33 - <EmbeddedResource Include="src\prx_lib.pxs" /> 34 - <EmbeddedResource Include="src\prx_main.pxs" /> 51 + <EmbeddedResource Include="src\prx_interactive.pxs" LogicalName="prx_interactive.pxs" /> 52 + <EmbeddedResource Include="src\prx_lib.pxs" LogicalName="prx_lib.pxs" /> 53 + <EmbeddedResource Include="src\prx_main.pxs" LogicalName="prx_main.pxs" /> 35 54 <None Update="psr\_2\psr\prop.pxs"> 36 55 <CopyToOutputDirectory>Always</CopyToOutputDirectory> 37 56 </None>
+1 -28
Prx/src/prx_interactive.pxs
··· 1 - // Prexonite 2 - // 3 - // Copyright (c) 2011, Christian Klauser 4 - // All rights reserved. 5 - // 6 - // Redistribution and use in source and binary forms, with or without modification, 7 - // are permitted provided that the following conditions are met: 8 - // 9 - // Redistributions of source code must retain the above copyright notice, 10 - // this list of conditions and the following disclaimer. 11 - // Redistributions in binary form must reproduce the above copyright notice, 12 - // this list of conditions and the following disclaimer in the 13 - // documentation and/or other materials provided with the distribution. 14 - // The names of the contributors may be used to endorse or 15 - // promote products derived from this software without specific prior written permission. 16 - // 17 - // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 - // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 - // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 - // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 21 - // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 - // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 - // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 - // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 25 - // IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 - 27 - 28 1 build 29 2 { 30 - require("prx_lib.pxs"); 3 + require("resource:Prx:prx_lib.pxs"); 31 4 } 32 5 33 6 Import { System, System::Text, Prexonite, Prexonite::Compiler };
-26
Prx/src/prx_lib.pxs
··· 1 - // Prexonite 2 - // 3 - // Copyright (c) 2011, Christian Klauser 4 - // All rights reserved. 5 - // 6 - // Redistribution and use in source and binary forms, with or without modification, 7 - // are permitted provided that the following conditions are met: 8 - // 9 - // Redistributions of source code must retain the above copyright notice, 10 - // this list of conditions and the following disclaimer. 11 - // Redistributions in binary form must reproduce the above copyright notice, 12 - // this list of conditions and the following disclaimer in the 13 - // documentation and/or other materials provided with the distribution. 14 - // The names of the contributors may be used to endorse or 15 - // promote products derived from this software without specific prior written permission. 16 - // 17 - // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 - // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 - // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 - // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 21 - // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 - // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 - // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 - // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 25 - // IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 - 27 1 namespace prx.cli 28 2 import sys.* 29 3 {
+5 -28
Prx/src/prx_main.pxs
··· 1 - // Prexonite 2 - // 3 - // Copyright (c) 2011, Christian Klauser 4 - // All rights reserved. 5 - // 6 - // Redistribution and use in source and binary forms, with or without modification, 7 - // are permitted provided that the following conditions are met: 8 - // 9 - // Redistributions of source code must retain the above copyright notice, 10 - // this list of conditions and the following disclaimer. 11 - // Redistributions in binary form must reproduce the above copyright notice, 12 - // this list of conditions and the following disclaimer in the 13 - // documentation and/or other materials provided with the distribution. 14 - // The names of the contributors may be used to endorse or 15 - // promote products derived from this software without specific prior written permission. 16 - // 17 - // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 - // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 - // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 - // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 21 - // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 - // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 - // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 - // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 25 - // IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 - 27 1 Name prx::cli; 28 2 Description "prx command line interface (compiler and REPL)"; 29 3 Author "Christian Klauser"; 30 4 31 - build does require("prx_interactive.pxs", "prx_lib.pxs"); 5 + build does require( 6 + "resource:Prx:prx_interactive.pxs", 7 + "resource:Prx:prx_lib.pxs" 8 + ); 32 9 33 10 namespace prx.cli 34 11 import sys.*, ··· 40 17 var app; //<-- the compiled application 41 18 var ldr; //<-- the loader used to compile 'app' 42 19 43 - //The follwing code block is compiled as 20 + //The following code block is compiled as 44 21 // part of the initialization of the application. 45 22 // Global variable assignments too are part of the this 'init' function. 46 23 {
+1 -1
prx.sln
··· 27 27 {A1CE6652-346C-43FE-AAC1-918C8F85DFE2}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 28 {A1CE6652-346C-43FE-AAC1-918C8F85DFE2}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 29 {A1CE6652-346C-43FE-AAC1-918C8F85DFE2}.Release|Any CPU.Build.0 = Release|Any CPU 30 - EndGlobalSection 30 + EndGlobalSection 31 31 GlobalSection(SolutionProperties) = preSolution 32 32 HideSolutionNode = FALSE 33 33 EndGlobalSection