···7474 throw new FileNotFoundException("Prexonite can't load assembly located in " +
7575 path);
76767777- eng.RegisterAssembly(Assembly.LoadFile(asmFile.FullName));
7777+ Assembly assembly;
7878+ if (asmFile is FileSpec { FullName: var asmFilePath })
7979+ {
8080+ // Use file path based loading to give the CLR file system context.
8181+ assembly = Assembly.LoadFrom(asmFilePath);
8282+ }
8383+ else
8484+ {
8585+ using var stream = asmFile.OpenStream();
8686+ using var buf = new MemoryStream();
8787+ stream.CopyTo(buf);
8888+ assembly = Assembly.Load(buf.GetBuffer());
8989+ }
9090+9191+ eng.RegisterAssembly(assembly);
7892 }
79938094 return PType.Null.CreatePValue();
···166166 {
167167 refSpec.Source = new FileSource(refSpec.ResolvedPath, Encoding);
168168 await Task.Yield(); // Need to yield at this point to keep
169169- // the critical section of the cache short
169169+ // the critical section of the cache update short
170170 return await _performPreflight(refSpec, actualToken);
171171 }, token);
172172 }
···256256 }
257257258258 [NotNull]
259259- private static readonly Regex _fileReferencePattern = new(@"^(\.|/|:)");
259259+ private static readonly Regex _fileReferencePattern = new(@"^([/.]|[a-zA-Z]:)");
260260261261 private static RefSpec _parseRefSpec(MetaEntry entry)
262262 {
+5-5
Prexonite/Compiler/Build/Plan.cs
···4141 /// <summary>List of modules included in the Prexonite assembly. Does not include 'sys' itself.</summary>
4242 private static readonly ISource[] _stdLibModules =
4343 {
4444- Source.FromEmbeddedResource("prxlib.prx.prim.pxs"),
4545- Source.FromEmbeddedResource("prxlib.prx.core.pxs"),
4646- Source.FromEmbeddedResource("prxlib.sys.pxs"),
4747- Source.FromEmbeddedResource("prxlib.prx.v1.pxs"),
4848- Source.FromEmbeddedResource("prxlib.prx.v1.prelude.pxs")
4444+ Source.FromEmbeddedPrexoniteResource("prxlib.prx.prim.pxs"),
4545+ Source.FromEmbeddedPrexoniteResource("prxlib.prx.core.pxs"),
4646+ Source.FromEmbeddedPrexoniteResource("prxlib.sys.pxs"),
4747+ Source.FromEmbeddedPrexoniteResource("prxlib.prx.v1.pxs"),
4848+ Source.FromEmbeddedPrexoniteResource("prxlib.prx.v1.prelude.pxs")
4949 };
50505151 public static IPlan CreateDefault()
+7-1
Prexonite/Compiler/Build/Source.cs
···7676 return FromStream(new MemoryStream(data, false), encoding, false);
7777 }
78787979- public static ISource FromEmbeddedResource([NotNull] string name)
7979+ public static ISource FromEmbeddedPrexoniteResource([NotNull] string name)
8080 {
8181 if (name == null) throw new ArgumentNullException(nameof(name));
8282 return new EmbeddedResourceSource(Assembly.GetExecutingAssembly(), "Prexonite." + name);
8383+ }
8484+8585+ public static ISource FromEmbeddedResource([NotNull] Assembly assembly, [NotNull] string name)
8686+ {
8787+ if (name == null) throw new ArgumentNullException(nameof(name));
8888+ return new EmbeddedResourceSource(assembly, name);
8389 }
84908591 [NotNull]
+63
Prexonite/Compiler/ISourceSpec.cs
···11+#nullable enable
22+using System;
33+using System.IO;
44+using System.Reflection;
55+using System.Text;
66+using Prexonite.Compiler.Build;
77+88+namespace Prexonite.Compiler;
99+1010+/// <summary>
1111+/// A retro-fitted precursor to <see cref="ISource"/> (<see cref="Source"/>). It is used to support including
1212+/// Prexonite Script files embedded as resources. Modern code should prefer using modules and the build system
1313+/// (with one module per embedded resource).
1414+/// </summary>
1515+/// <seealso cref="Plan"/>
1616+public interface ISourceSpec
1717+{
1818+ public string FullName { get; }
1919+ public string ShortName { get; }
2020+ public Stream OpenStream();
2121+ public string? LoadPath { get; }
2222+2323+ public ISource ToSource();
2424+2525+ public bool Exists();
2626+}
2727+2828+public sealed record FileSpec(FileInfo File) : ISourceSpec
2929+{
3030+ public FileSpec(string path) : this(new FileInfo(path)) { }
3131+3232+ public string FullName => File.FullName;
3333+ public string ShortName => File.Name;
3434+ public string? LoadPath => File.DirectoryName;
3535+3636+ public Stream OpenStream() => new FileStream(
3737+ File.FullName,
3838+ FileMode.Open,
3939+ FileAccess.Read,
4040+ FileShare.Read,
4141+ 4 * 1024,
4242+ FileOptions.SequentialScan);
4343+4444+ public ISource ToSource() => Source.FromFile(File, Encoding.UTF8);
4545+ public bool Exists() => System.IO.File.Exists(FullName);
4646+}
4747+4848+public sealed record ResourceSpec(Assembly ResourceAssembly, string Name) : ISourceSpec
4949+{
5050+ public const string Prefix = "resource:";
5151+ public string FullName => $"{Prefix}{ResourceAssembly.FullName ?? "<unknown assembly>"}:{Name}";
5252+ string ISourceSpec.ShortName => Name;
5353+5454+ public Stream OpenStream() => ResourceAssembly.GetManifestResourceStream(Name)
5555+ ?? throw new InvalidOperationException($"Assembly {ResourceAssembly.FullName} does not " +
5656+ $"contain a resource '{Name}' (or it is not accessible).");
5757+5858+ public string? LoadPath => null;
5959+6060+ public ISource ToSource() => Source.FromEmbeddedResource(ResourceAssembly, Name);
6161+6262+ public bool Exists() => ResourceAssembly.GetManifestResourceInfo(Name) != null;
6363+}
+102
Prexonite/Compiler/Internal/AssemblyResolver.cs
···11+#nullable enable
22+using System;
33+using System.Collections.Concurrent;
44+using System.Diagnostics;
55+using System.Linq;
66+using System.Reflection;
77+88+namespace Prexonite.Compiler.Internal;
99+1010+internal sealed class AssemblyResolver : ICloneable
1111+{
1212+ private readonly ConcurrentDictionary<AssemblyName, Assembly> _assemblies = new();
1313+ private readonly ConcurrentDictionary<string, Assembly> _cache = new();
1414+1515+ public Assembly Resolve(string name) => TryResolve(name) ??
1616+ throw new PrexoniteException($"Could not resolve assembly by name '{name}'. Is it loaded?");
1717+1818+ public Assembly? TryResolve(string name)
1919+ {
2020+ // as an optimization, check if there is a cache hit
2121+ if (_cache.TryGetValue(name, out var cacheHit))
2222+ {
2323+ return cacheHit;
2424+ }
2525+2626+ // try to resolve the assembly (note that we can't use GetOrAdd because the resolution might fail)
2727+ Assembly? resolvedAssembly = null;
2828+ foreach (var assembly in _assemblies.Values)
2929+ {
3030+ if (Engine.DefaultStringComparer.Equals(assembly.FullName, name)
3131+ || assembly.GetName() is var otherName &&
3232+ (Engine.DefaultStringComparer.Equals(otherName.Name, name)
3333+ || Engine.DefaultStringComparer.Equals($"{otherName.Name},{otherName.Version}", name)))
3434+ {
3535+ resolvedAssembly = assembly;
3636+ break;
3737+ }
3838+ }
3939+4040+ return resolvedAssembly == null ? resolvedAssembly : _cache.GetOrAdd(name, resolvedAssembly);
4141+ }
4242+4343+ /// <summary>
4444+ /// Determines whether an assembly is already registered for use by the Prexonite VM.
4545+ /// </summary>
4646+ /// <param name = "ass">An assembly reference.</param>
4747+ /// <returns>True if the supplied assembly is registered; false otherwise.</returns>
4848+ [DebuggerStepThrough]
4949+ public bool Contains(Assembly? ass) => ass != null && _assemblies.ContainsKey(ass.GetName());
5050+5151+ /// <summary>
5252+ /// Gets a list of all registered assemblies.
5353+ /// </summary>
5454+ /// <returns>A copy of the list of registered assemblies.</returns>
5555+ [DebuggerStepThrough]
5656+ public Assembly[] ToArray() => _assemblies.Values.ToArray();
5757+5858+ /// <summary>
5959+ /// Registers a new assembly for use by the Prexonite VM.
6060+ /// </summary>
6161+ /// <param name = "ass">An assembly reference.</param>
6262+ /// <exception cref = "ArgumentNullException"><paramref name = "ass" /> is null.</exception>
6363+ [DebuggerStepThrough]
6464+ public void Add(Assembly ass)
6565+ {
6666+ if (ass == null)
6767+ throw new ArgumentNullException(nameof(ass));
6868+ _assemblies.GetOrAdd(ass.GetName(), ass);
6969+ }
7070+7171+ /// <summary>
7272+ /// Removes an assembly from the list registered ones.
7373+ /// </summary>
7474+ /// <param name = "ass">The assembly to remove.</param>
7575+ /// <exception cref = "ArgumentNullException"><paramref name = "ass" /> is null.</exception>
7676+ [DebuggerStepThrough]
7777+ public void Remove(Assembly ass)
7878+ {
7979+ if (ass == null)
8080+ throw new ArgumentNullException(nameof(ass));
8181+ _assemblies.TryRemove(ass.GetName(), out _);
8282+ foreach (var (shortName, cached) in _cache)
8383+ {
8484+ if (ass == cached)
8585+ {
8686+ _cache.TryRemove(shortName, out _);
8787+ }
8888+ }
8989+ }
9090+9191+ public AssemblyResolver Clone()
9292+ {
9393+ var copy = new AssemblyResolver();
9494+ copy._assemblies.AddRange(_assemblies);
9595+ // NOTE: we intentionally don't copy the cache. The cache is an optimization that should remain tied to
9696+ // a single engine. The clone should not copy over cache entries (which might never be relevant to the
9797+ // clone).
9898+ return copy;
9999+ }
100100+101101+ object ICloneable.Clone() => Clone();
102102+}
+61
Prexonite/Compiler/Internal/PathSet.cs
···11+using System;
22+using System.Collections;
33+using System.Collections.Generic;
44+using System.IO;
55+66+namespace Prexonite.Compiler.Internal;
77+88+/// <summary>
99+/// A set of file system paths. Tries to normalize paths (on a best-effort basis).
1010+/// </summary>
1111+/// <remarks>
1212+/// This class is aware of <see cref="ResourceSpec"/>.</remarks>
1313+internal sealed class PathSet : ICollection<string>
1414+{
1515+ private readonly HashSet<string> _paths = new();
1616+ public IEnumerator<string> GetEnumerator()
1717+ {
1818+ return _paths.GetEnumerator();
1919+ }
2020+2121+ IEnumerator IEnumerable.GetEnumerator()
2222+ {
2323+ return ((IEnumerable)_paths).GetEnumerator();
2424+ }
2525+2626+ public void Add(string path) => _paths.Add(canonical(path));
2727+2828+ public void Clear()
2929+ {
3030+ _paths.Clear();
3131+ }
3232+3333+ public bool Contains(string path) => _paths.Contains(canonical(path));
3434+3535+ public void CopyTo(string[] array, int arrayIndex)
3636+ {
3737+ _paths.CopyTo(array, arrayIndex);
3838+ }
3939+4040+ public bool Remove(string path) => _paths.Remove(canonical(path));
4141+4242+ public int Count => _paths.Count;
4343+4444+ bool ICollection<string>.IsReadOnly => ((ICollection<string>)_paths).IsReadOnly;
4545+4646+ private static string canonical(string path)
4747+ {
4848+ if (!path.StartsWith(ResourceSpec.Prefix))
4949+ {
5050+ path = Path.GetFullPath(path);
5151+ if (OperatingSystem.IsMacOS() || OperatingSystem.IsWindows())
5252+ {
5353+ // PRX-58 This generalization is of course not correct, but there currently is no convenient API
5454+ // to get a canonical path.
5555+ path = path.ToUpperInvariant();
5656+ }
5757+ }
5858+5959+ return path;
6060+ }
6161+}
+59-35
Prexonite/Compiler/Loader.cs
···4848using Prexonite.Compiler.Symbolic.Internal;
4949using Prexonite.Internal;
5050using Prexonite.Modular;
5151-using Prexonite.Properties;
5251using Prexonite.Types;
5352using Debug = System.Diagnostics.Debug;
5453···255254256255 var target = new CompilerTarget(this, func,parentTarget,sourcePosition);
257256 if (_functionTargets.ContainsKey(func.Id) &&
258258- (!ParentApplication.Meta.GetDefault(Application.AllowOverridingKey, true).Switch))
257257+ !ParentApplication.Meta.GetDefault(Application.AllowOverridingKey, true).Switch)
259258 throw new PrexoniteException(
260259 $"The application {ParentApplication.Id} does not allow overriding of function {func.Id}.");
261260···665664 if (filePath != null)
666665 {
667666 lex.File = filePath;
668668- LoadedFiles.Add(Path.GetFullPath(filePath));
667667+ loadedFiles.Add(filePath);
669668 }
670669671670 _load(lex);
···711710 _loadFromFile(file);
712711 }
713712714714- private void _loadFromFile(FileInfo file)
713713+ private void _loadFromFile(ISourceSpec file)
715714 {
716715 if (file == null)
717716 throw new ArgumentNullException(nameof(file));
718718- LoadedFiles.Add(file.FullName);
719719- LoadPaths.Push(file.DirectoryName);
717717+ loadedFiles.Add(file.FullName);
718718+ if (file.LoadPath is { } loadPath)
719719+ {
720720+ LoadPaths.Push(loadPath);
721721+ }
722722+720723 try
721724 {
722722- using Stream str = new FileStream(
723723- file.FullName,
724724- FileMode.Open,
725725- FileAccess.Read,
726726- FileShare.Read,
727727- 4 * 1024,
728728- FileOptions.SequentialScan);
725725+ using var stream = file.OpenStream();
729726#if DEBUG
730727 var indent = new StringBuilder(_loadIndent);
731728 indent.Append(' ', 2 * (_loadIndent++));
732732- Console.WriteLine(Resources.Loader__begin_compiling, file.Name, indent, file.FullName);
729729+ Console.WriteLine(Properties.Resources.Loader__begin_compiling, file.ShortName, indent, file.FullName);
733730#endif
734734- _loadFromStream(str, file.Name);
731731+ _loadFromStream(stream, file.FullName);
735732#if DEBUG
736736- Console.WriteLine(Resources.Loader__end_compiling, file.Name, indent);
733733+ Console.WriteLine(Properties.Resources.Loader__end_compiling, file.ShortName, indent);
737734 _loadIndent--;
738735#endif
739736 }
740737 finally
741738 {
742742- LoadPaths.Pop();
739739+ if (file.LoadPath != null)
740740+ {
741741+ LoadPaths.Pop();
742742+ }
743743 }
744744 }
745745···753753 return;
754754 }
755755756756- if (LoadedFiles.Contains(file.FullName))
756756+ if (loadedFiles.Contains(file.FullName))
757757 return;
758758759759 _loadFromFile(file);
···894894 }
895895896896 private static readonly string _imageLocation =
897897- (new FileInfo(Assembly.GetExecutingAssembly().Location)).DirectoryName;
897897+ new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName;
898898899899- public FileInfo ApplyLoadPaths(string pathSuffix)
899899+ /// <summary>
900900+ /// Tries to find the indicated source file by applying the current load paths.
901901+ /// </summary>
902902+ /// <param name="pathSuffix">The file to search for.</param>
903903+ /// <returns>The source specification, if a source could be found; <c>null</c> otherwise.</returns>
904904+ /// <exception cref="ArgumentNullException"><paramref name="pathSuffix"/> is null</exception>
905905+ /// <exception cref="ArgumentException">An incorrectly formatted file name. This primarily applies to embedded <c>resource:</c> paths.</exception>
906906+ [CanBeNull]
907907+ public ISourceSpec ApplyLoadPaths(string pathSuffix)
900908 {
901909 if (pathSuffix == null)
902910 throw new ArgumentNullException(nameof(pathSuffix));
911911+ if (pathSuffix.StartsWith("resource:"))
912912+ {
913913+ var parts = pathSuffix.Split(":", 3);
914914+ if (parts.Length < 3)
915915+ {
916916+ throw new ArgumentException(
917917+ $"A resource: file path needs to have the form `resource:{{assembly}}:{{name}}`. Got '{pathSuffix}' instead.");
918918+ }
919919+ var assembly = ParentEngine.TryResolveAssembly(parts[1]);
920920+ if (assembly == null)
921921+ {
922922+ Trace.WriteLine($"Could not resolve assembly by name '{parts[1]}'. Is it loaded?");
923923+ return null;
924924+ }
925925+926926+ var spec = new ResourceSpec(assembly, parts[2]);
927927+ return spec.Exists() ? spec : null;
928928+ }
929929+903930 var path = pathSuffix.Replace(DirectorySeparator, Path.DirectorySeparatorChar);
904931905932 //Try to find in process environment
906933 if (File.Exists(path))
907907- return new FileInfo(path);
934934+ return new FileSpec(path);
908935909936 //Try to find in load paths
910937 foreach (var pathPrefix in LoadPaths)
911911- if (File.Exists((path = Path.Combine(pathPrefix, pathSuffix))))
912912- return new FileInfo(path);
938938+ if (File.Exists(path = Path.Combine(pathPrefix, pathSuffix)))
939939+ return new FileSpec(path);
913940914941 //Try to find in engine paths
915942 foreach (var pathPrefix in ParentEngine.Paths)
916916- if (File.Exists((path = Path.Combine(pathPrefix, pathSuffix))))
917917- return new FileInfo(path);
943943+ if (File.Exists(path = Path.Combine(pathPrefix, pathSuffix)))
944944+ return new FileSpec(path);
918945919946 //Try to find in current directory
920920- if (File.Exists((path = Path.Combine(Environment.CurrentDirectory, pathSuffix))))
921921- return new FileInfo(path);
947947+ if (File.Exists(path = Path.Combine(Environment.CurrentDirectory, pathSuffix)))
948948+ return new FileSpec(path);
922949923950 //Try to find next to image
924924- if (File.Exists((path = Path.Combine(_imageLocation, pathSuffix))))
925925- return new FileInfo(path);
951951+ if (File.Exists(path = Path.Combine(_imageLocation, pathSuffix)))
952952+ return new FileSpec(path);
926953927954 //Not found
928955 return null;
929956 }
930957931931- public SymbolCollection LoadedFiles { get; } = new();
958958+ private PathSet loadedFiles { get; } = new();
932959933960 #endregion
934961···10351062 _throwCannotFindScriptFile(path);
10361063 return PType.Null;
10371064 }
10381038- if (LoadedFiles.Contains(file.FullName))
10651065+ if (loadedFiles.Contains(file.FullName))
10391066 allLoaded = false;
10401067 else
10411068 _loadFromFile(file);
···10491076 delegate
10501077 {
10511078 var defaultFile = ApplyLoadPaths(DefaultScriptName);
10521052- if (defaultFile == null)
10531053- return DefaultScriptName;
10541054- else
10551055- return defaultFile.FullName;
10791079+ return defaultFile?.FullName ?? DefaultScriptName;
10561080 });
1057108110581082 BuildCommands.AddCompilerCommand(
+27-27
Prexonite/Engine.cs
···1414using Prexonite.Commands.List;
1515using Prexonite.Commands.Math;
1616using Prexonite.Commands.Text;
1717+using Prexonite.Compiler.Internal;
1718using Prexonite.Types;
1819using Char = Prexonite.Commands.Core.Char;
1920using Debug = Prexonite.Commands.Core.Debug;
···481482482483 #region Assembly management
483484484484- private readonly List<Assembly> _registeredAssemblies;
485485+ private readonly AssemblyResolver _registeredAssemblies;
485486486487 /// <summary>
487488 /// Determines whether an assembly is already registered for use by the Prexonite VM.
···489490 /// <param name = "ass">An assembly reference.</param>
490491 /// <returns>True if the supplied assembly is registered; false otherwise.</returns>
491492 [DebuggerStepThrough]
492492- public bool IsAssemblyRegistered(Assembly? ass)
493493- {
494494- if (ass == null)
495495- return false;
496496- return _registeredAssemblies.Contains(ass);
497497- }
493493+ public bool IsAssemblyRegistered(Assembly? ass) => _registeredAssemblies.Contains(ass);
498494499495 /// <summary>
500496 /// Gets a list of all registered assemblies.
501497 /// </summary>
502498 /// <returns>A copy of the list of registered assemblies.</returns>
503499 [DebuggerStepThrough]
504504- public Assembly[] GetRegisteredAssemblies()
505505- {
506506- return _registeredAssemblies.ToArray();
507507- }
500500+ public Assembly[] GetRegisteredAssemblies() => _registeredAssemblies.ToArray();
508501509502 /// <summary>
510503 /// Registers a new assembly for use by the Prexonite VM.
···512505 /// <param name = "ass">An assembly reference.</param>
513506 /// <exception cref = "ArgumentNullException"><paramref name = "ass" /> is null.</exception>
514507 [DebuggerStepThrough]
515515- public void RegisterAssembly(Assembly? ass)
516516- {
517517- if (ass == null)
518518- throw new ArgumentNullException(nameof(ass));
519519- if (!_registeredAssemblies.Contains(ass))
520520- _registeredAssemblies.Add(ass);
521521- }
508508+ public void RegisterAssembly(Assembly ass) => _registeredAssemblies.Add(ass);
522509523510 /// <summary>
524511 /// Removes an assembly from the list registered ones.
···526513 /// <param name = "ass">The assembly to remove.</param>
527514 /// <exception cref = "ArgumentNullException"><paramref name = "ass" /> is null.</exception>
528515 [DebuggerStepThrough]
529529- public void RemoveAssembly(Assembly? ass)
530530- {
531531- if (ass == null)
532532- throw new ArgumentNullException(nameof(ass));
533533- if (_registeredAssemblies.Contains(ass))
534534- _registeredAssemblies.Remove(ass);
535535- }
516516+ public void RemoveAssembly(Assembly ass) => _registeredAssemblies.Remove(ass);
517517+518518+ /// <summary>
519519+ /// Tries to find a registered assembly with a matching name. Matches fully qualified assembly names, simple names
520520+ /// and <c>{name},{version}</c>.
521521+ /// </summary>
522522+ /// <param name="name">The name to find a matching assembly for.</param>
523523+ /// <returns>Either the matching assembly or <c>null</c> if no matching assembly cna be found.</returns>
524524+ public Assembly? TryResolveAssembly(string name) => _registeredAssemblies.TryResolve(name);
525525+526526+ /// <summary>
527527+ /// Tries to find a registered assembly with a matching name. Matches fully qualified assembly names, simple names
528528+ /// and <c>{name},{version}</c>.
529529+ /// </summary>
530530+ /// <param name="name">The name to find a matching assembly for.</param>
531531+ /// <returns>Either the matching assembly.</returns>
532532+ /// <exception cref="PrexoniteVersion">If no matching assembly can be found.</exception>
533533+ public Assembly ResolveAssembly(string name) => _registeredAssemblies.Resolve(name);
536534537535 #endregion
538536···620618 };
621619622620 //Assembly registry
623623- _registeredAssemblies = new List<Assembly>();
621621+ _registeredAssemblies = new();
622622+ var prexoniteAssembly = Assembly.GetAssembly(typeof(Engine))!;
623623+ _registeredAssemblies.Add(prexoniteAssembly);
624624 foreach (
625625 var assName in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
626626 _registeredAssemblies.Add(Assembly.Load(assName.FullName));
···821821 _pTypeRegistry = new SymbolTable<Type>(prototype._pTypeRegistry.Count);
822822 _pTypeRegistry.AddRange(prototype._pTypeRegistry);
823823 PTypeRegistry = new PTypeRegistryIterator(this);
824824- _registeredAssemblies = new List<Assembly>(prototype._registeredAssemblies);
824824+ _registeredAssemblies = prototype._registeredAssemblies.Clone();
825825 Commands = new CommandTable();
826826 Commands.AddRange(prototype.Commands);
827827
···48484949internal static class Program
5050{
5151- public const string PrxScriptFileName = "prx.pxs";
5151+ public const string PrxScriptFileName = "prx_main.pxs";
52525353 public static string GetPrxPath()
5454 {
5555- return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
5555+ return AppContext.BaseDirectory;
5656 }
57575858 public static void Main(string[] args)
···224224 var node = e.Stack.Last;
225225 do
226226 {
227227- if (node.Value is FunctionContext ectx)
227227+ if (node?.Value is FunctionContext ectx)
228228 {
229229 if (ReferenceEquals(ectx.Implementation, rctx.Implementation))
230230 {
···232232 break;
233233 }
234234 }
235235- } while ((node = node.Previous) != null);
235235+ } while ((node = node?.Previous) != null);
236236237237 return PType.Null.CreatePValue();
238238 });
···288288289289 #endregion
290290291291- var bootstrapPath = Path.Combine(prxPath, PrxScriptFileName);
292292- var (entryPath, deleteSrc) = _ensureSourceAvailable(bootstrapPath, prxPath);
293293-294291 Tuple<Application, ITarget> result;
295292 try
296293 {
297297- var entryDesc = plan.AssembleAsync(Source.FromFile(entryPath, Encoding.UTF8)).Result;
294294+ var entryDesc = plan.AssembleAsync(Source.FromEmbeddedResource(Assembly.GetAssembly(typeof(Program))!, PrxScriptFileName)).Result;
298295 result = plan.Load(entryDesc.Name);
299296 }
300297 catch (BuildFailureException e)
···329326#endif
330327 }
331328332332- if (deleteSrc)
333333- Directory.Delete(Path.Combine(prxPath ,"src"), true);
334334-335329 if (result == null)
336330 {
337331 return null;
···344338 app.Meta["Version"] = Assembly.GetExecutingAssembly().GetName()!.Version!.ToString();
345339 return app;
346340347347- }
348348-349349- private static (string entryPath, bool deleteSrc) _ensureSourceAvailable(string entryPath, string prxPath)
350350- {
351351- if (File.Exists(entryPath))
352352- return (entryPath, false);
353353-354354- var srcDirPath = Path.Combine(prxPath, "src");
355355- //Load default CLI app
356356- entryPath = Path.Combine(srcDirPath, "prx_main.pxs");
357357-358358- if (File.Exists(entryPath))
359359- return (entryPath, false);
360360-361361- bool deleteSrc;
362362- if (!Directory.Exists(srcDirPath))
363363- {
364364- var di =
365365- Directory.CreateDirectory(srcDirPath);
366366- di.Attributes |= FileAttributes.Hidden;
367367- deleteSrc = true;
368368- }
369369- else
370370- {
371371- deleteSrc = false;
372372- }
373373-374374- //Unpack source
375375- var prx = Assembly.GetExecutingAssembly();
376376-377377- async Task extractFile(string name)
378378- {
379379- var resourceName = $"Prx.src.{name}";
380380- var stream = prx.GetManifestResourceStream(resourceName);
381381- if (stream == null)
382382- {
383383- throw new ArgumentException($"Embedded resource '{resourceName}' is missing.",
384384- nameof(name));
385385- }
386386-387387- await using (stream)
388388- {
389389- var filePath = Path.Combine(srcDirPath, name);
390390-391391- // We need the await here so that the state machine can close the streams afterwards
392392- await using (var dest = new FileStream(filePath, FileMode.Create, FileAccess.Write))
393393- await stream.CopyToAsync(dest, (CancellationToken) default);
394394- }
395395- }
396396-397397-398398- Task.WaitAll(
399399- extractFile("prx_main.pxs"),
400400- extractFile("prx_lib.pxs"),
401401- extractFile("prx_interactive.pxs")
402402- );
403403-404404- return (entryPath, deleteSrc);
405341 }
406342407343 private static bool _reportErrors(IEnumerable<Message> messages)
+22-3
Prx/Prx.csproj
···2323 <Product>Prexonite</Product>
2424 <Company>$(Product)</Company>
2525 <NeutralLanguage>en-US</NeutralLanguage>
2626+2727+ <!-- single file app -->
2828+ <PublishSingleFile>true</PublishSingleFile>
2929+ <SelfContained>true</SelfContained>
3030+ <RuntimeIdentifier Condition="$([MSBuild]::IsOSPlatform('Windows'))">win-x64</RuntimeIdentifier>
3131+ <RuntimeIdentifier Condition="$([MSBuild]::IsOSPlatform('Linux'))">linux-x64</RuntimeIdentifier>
3232+ <!-- Teady to run makes a HUGE difference in terms of startup time.
3333+ Reduces startup time from ~820ms down to ~210ms. -->
3434+ <PublishReadyToRun>true</PublishReadyToRun>
3535+ <!-- While compression can reduce the file size by about 50%, it also almost doubles the overhead for launching the
3636+ interpreter (210ms -> 350-450ms) to the point where the delay becomes much more noticable.
3737+ -->
3838+ <EnableCompressionInSingleFile>false</EnableCompressionInSingleFile>
3939+ <!-- Can't use assembly trimming because the Prx interpreter heavily relies on reflection.
4040+ Interestingly, there is sufficient overlap to run the basic prx application.
4141+ That means: if you are only interested in the compiler and a basic REPL, you can use a 19MB trimmed
4242+ application.
4343+ -->
4444+ <PublishTrimmed>false</PublishTrimmed>
2645 </PropertyGroup>
27462847 <ItemGroup>
2948 <None Remove="src\prx_interactive.pxs" />
3049 <None Remove="src\prx_lib.pxs" />
3150 <None Remove="src\prx_main.pxs" />
3232- <EmbeddedResource Include="src\prx_interactive.pxs" />
3333- <EmbeddedResource Include="src\prx_lib.pxs" />
3434- <EmbeddedResource Include="src\prx_main.pxs" />
5151+ <EmbeddedResource Include="src\prx_interactive.pxs" LogicalName="prx_interactive.pxs" />
5252+ <EmbeddedResource Include="src\prx_lib.pxs" LogicalName="prx_lib.pxs" />
5353+ <EmbeddedResource Include="src\prx_main.pxs" LogicalName="prx_main.pxs" />
3554 <None Update="psr\_2\psr\prop.pxs">
3655 <CopyToOutputDirectory>Always</CopyToOutputDirectory>
3756 </None>
+1-28
Prx/src/prx_interactive.pxs
···11-// Prexonite
22-//
33-// Copyright (c) 2011, Christian Klauser
44-// All rights reserved.
55-//
66-// Redistribution and use in source and binary forms, with or without modification,
77-// are permitted provided that the following conditions are met:
88-//
99-// Redistributions of source code must retain the above copyright notice,
1010-// this list of conditions and the following disclaimer.
1111-// Redistributions in binary form must reproduce the above copyright notice,
1212-// this list of conditions and the following disclaimer in the
1313-// documentation and/or other materials provided with the distribution.
1414-// The names of the contributors may be used to endorse or
1515-// promote products derived from this software without specific prior written permission.
1616-//
1717-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
1818-// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1919-// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2020-// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
2121-// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2222-// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2323-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
2424-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
2525-// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2626-2727-281build
292{
3030- require("prx_lib.pxs");
33+ require("resource:Prx:prx_lib.pxs");
314}
325336Import { System, System::Text, Prexonite, Prexonite::Compiler };
-26
Prx/src/prx_lib.pxs
···11-// Prexonite
22-//
33-// Copyright (c) 2011, Christian Klauser
44-// All rights reserved.
55-//
66-// Redistribution and use in source and binary forms, with or without modification,
77-// are permitted provided that the following conditions are met:
88-//
99-// Redistributions of source code must retain the above copyright notice,
1010-// this list of conditions and the following disclaimer.
1111-// Redistributions in binary form must reproduce the above copyright notice,
1212-// this list of conditions and the following disclaimer in the
1313-// documentation and/or other materials provided with the distribution.
1414-// The names of the contributors may be used to endorse or
1515-// promote products derived from this software without specific prior written permission.
1616-//
1717-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
1818-// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1919-// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2020-// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
2121-// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2222-// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2323-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
2424-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
2525-// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2626-271namespace prx.cli
282 import sys.*
293{
+5-28
Prx/src/prx_main.pxs
···11-// Prexonite
22-//
33-// Copyright (c) 2011, Christian Klauser
44-// All rights reserved.
55-//
66-// Redistribution and use in source and binary forms, with or without modification,
77-// are permitted provided that the following conditions are met:
88-//
99-// Redistributions of source code must retain the above copyright notice,
1010-// this list of conditions and the following disclaimer.
1111-// Redistributions in binary form must reproduce the above copyright notice,
1212-// this list of conditions and the following disclaimer in the
1313-// documentation and/or other materials provided with the distribution.
1414-// The names of the contributors may be used to endorse or
1515-// promote products derived from this software without specific prior written permission.
1616-//
1717-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
1818-// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1919-// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2020-// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
2121-// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2222-// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2323-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
2424-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
2525-// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2626-271Name prx::cli;
282Description "prx command line interface (compiler and REPL)";
293Author "Christian Klauser";
3043131-build does require("prx_interactive.pxs", "prx_lib.pxs");
55+build does require(
66+ "resource:Prx:prx_interactive.pxs",
77+ "resource:Prx:prx_lib.pxs"
88+);
3293310namespace prx.cli
3411 import sys.*,
···4017 var app; //<-- the compiled application
4118 var ldr; //<-- the loader used to compile 'app'
42194343- //The follwing code block is compiled as
2020+ //The following code block is compiled as
4421 // part of the initialization of the application.
4522 // Global variable assignments too are part of the this 'init' function.
4623 {