diff --git a/Compiler/Commands/BuildCommand.cs b/Compiler/Commands/BuildCommand.cs index a7d2f43c..37de55df 100644 --- a/Compiler/Commands/BuildCommand.cs +++ b/Compiler/Commands/BuildCommand.cs @@ -25,7 +25,7 @@ private async Task HandleCommandAsync(ParseResult result, CancellationToken config.Validate(); - using var host = CompilerHost.CompilerHostBuilder.Create() + using var host = CompilerHost.CompilerHostBuilder.Create(config.WorkingDirectory) .WithDefaults() #if !WASI_WASM_BUILD .WithExtensions(config.Extensions) @@ -33,6 +33,7 @@ private async Task HandleCommandAsync(ParseResult result, CancellationToken .Build(); var compiler = new BebopCompiler(host); + Helpers.WriteHostInfo(host); BebopSchema schema = default; string? tempFilePath = null; try diff --git a/Compiler/Commands/LanguageServerCommand.cs b/Compiler/Commands/LanguageServerCommand.cs index 787411e5..9211aadd 100644 --- a/Compiler/Commands/LanguageServerCommand.cs +++ b/Compiler/Commands/LanguageServerCommand.cs @@ -22,12 +22,9 @@ private async Task HandleCommandAsync(ParseResult result, CancellationToken { #if !WASI_WASM_BUILD Dictionary extensions = []; - var config = result.GetValue(CliStrings.ConfigFlag); - if (config is not null) - { - extensions = config.Extensions; - } - using var host = CompilerHost.CompilerHostBuilder.Create().WithDefaultDecorators().WithExtensions(extensions).Build(); + var config = result.GetValue(CliStrings.ConfigFlag)!; + extensions = config.Extensions; + using var host = CompilerHost.CompilerHostBuilder.Create(config.WorkingDirectory).WithDefaultDecorators().WithExtensions(extensions).Build(); await BebopLangServer.RunAsync(host, token); #endif return BebopCompiler.Ok; diff --git a/Compiler/Commands/WatchCommand.cs b/Compiler/Commands/WatchCommand.cs index a27b9d50..220264ca 100644 --- a/Compiler/Commands/WatchCommand.cs +++ b/Compiler/Commands/WatchCommand.cs @@ -17,13 +17,14 @@ private async Task HandleCommandAsync(ParseResult result, CancellationToken { var config = result.GetValue(CliStrings.ConfigFlag)!; config.Validate(); - using var host = CompilerHost.CompilerHostBuilder.Create() + using var host = CompilerHost.CompilerHostBuilder.Create(config.WorkingDirectory) .WithDefaults() #if !WASI_WASM_BUILD .WithExtensions(config.Extensions) #endif .Build(); + Helpers.WriteHostInfo(host); var compiler = new BebopCompiler(host); var watcher = new SchemaWatcher(config.WorkingDirectory, config, compiler); return await watcher.StartAsync(token); diff --git a/Compiler/Helpers.cs b/Compiler/Helpers.cs index 8c19820a..6e8488db 100644 --- a/Compiler/Helpers.cs +++ b/Compiler/Helpers.cs @@ -1,11 +1,16 @@ using System; using System.CommandLine; using System.IO; +using System.Linq; +using Core; + #if WASI_WASM_BUILD using Core.Exceptions; #endif using Core.Generators; +using Core.Logging; using Core.Meta; +using Spectre.Console; namespace Compiler; @@ -84,6 +89,28 @@ public static void MergeConfig(ParseResult parseResults, BebopConfig config) } } + public static void WriteHostInfo(CompilerHost host) + { + if (host.Extensions.Any()) + { + DiagnosticLogger.Instance.Error.MarkupLine("[yellow]Using extensions defined in bebop.json[/]"); + DiagnosticLogger.Instance.Error.MarkupLine("[blue]- Extensions:[/]"); + foreach (var extension in host.Extensions) + { + DiagnosticLogger.Instance.Error.MarkupLine($" - [white]{extension.Name}[/]: [green]{extension.Version}[/]"); + } + } + if (host.EnvironmentVariableStore.DevVarsCount > 0) + { + DiagnosticLogger.Instance.Error.MarkupLine("[yellow]Using vars defined in .dev.vars[/]"); + DiagnosticLogger.Instance.Error.MarkupLine("[blue]- Vars:[/]"); + foreach (var name in host.EnvironmentVariableStore.DevVarNames) + { + DiagnosticLogger.Instance.Error.MarkupLine($" - [white]{name}[/]: [green](hidden)[/]"); + } + } + } + public static int ProcessId { get diff --git a/Compiler/LangServer/Handlers/SemanticTokenHandler.cs b/Compiler/LangServer/Handlers/SemanticTokenHandler.cs index ef5d5fa5..ceb4e24a 100644 --- a/Compiler/LangServer/Handlers/SemanticTokenHandler.cs +++ b/Compiler/LangServer/Handlers/SemanticTokenHandler.cs @@ -36,7 +36,7 @@ public SemanticTokenHandler( _tokenTypes = new Dictionary { - { TokenKind.ReadOnly, SemanticTokenType.Modifier }, + { TokenKind.Mut, SemanticTokenType.Keyword }, { TokenKind.Array, SemanticTokenType.Keyword }, { TokenKind.Enum, SemanticTokenType.Keyword }, { TokenKind.Map, SemanticTokenType.Keyword }, diff --git a/Core/CompilerHost.cs b/Core/CompilerHost.cs index 17f12f04..ab7ebe28 100644 --- a/Core/CompilerHost.cs +++ b/Core/CompilerHost.cs @@ -23,11 +23,13 @@ public class CompilerHost : IDisposable private readonly FrozenDictionary _decorators; private readonly FrozenDictionary _generators; private readonly ExtensionRuntime? _extensionRuntime; - private CompilerHost(FrozenDictionary decorators, FrozenDictionary generators, ExtensionRuntime? extensionRuntime) + private readonly EnvironmentVariableStore _environmentVariableStore; + private CompilerHost(FrozenDictionary decorators, FrozenDictionary generators, EnvironmentVariableStore environmentVariableStore, ExtensionRuntime? extensionRuntime) { _decorators = decorators; _generators = generators; _extensionRuntime = extensionRuntime; + _environmentVariableStore = environmentVariableStore; } public bool TryGetDecorator(string identifier, [NotNullWhen(true)] out DecoratorDefinition? decorator) @@ -40,6 +42,8 @@ public bool TryGetGenerator(string alias, [NotNullWhen(true)] out BaseGenerator? return _generators.TryGetValue(alias, out generator); } + public EnvironmentVariableStore EnvironmentVariableStore => _environmentVariableStore; + public IEnumerable<(string Alias, string Name)> Generators { get @@ -87,16 +91,19 @@ public class CompilerHostBuilder private ExtensionRuntime? _extensionRuntime; private readonly Dictionary _decorators; private readonly Dictionary _generators; + private readonly EnvironmentVariableStore _environmentVariableStore; - private CompilerHostBuilder() + private CompilerHostBuilder(string workingDirectory) { + _decorators = []; _generators = []; + _environmentVariableStore = new EnvironmentVariableStore(workingDirectory); } - public static CompilerHostBuilder Create() + public static CompilerHostBuilder Create(string workingDirectory) { - return new CompilerHostBuilder(); + return new CompilerHostBuilder(workingDirectory); } public CompilerHost Build() @@ -106,7 +113,7 @@ public CompilerHost Build() throw new CompilerException("A compiler host has already been built."); } _isBuilt = true; - return new CompilerHost(_decorators.ToFrozenDictionary(), _generators.ToFrozenDictionary(), _extensionRuntime); + return new CompilerHost(_decorators.ToFrozenDictionary(), _generators.ToFrozenDictionary(), _environmentVariableStore, _extensionRuntime); } public CompilerHostBuilder WithDefaults() diff --git a/Core/EnvironmentVariableStore.cs b/Core/EnvironmentVariableStore.cs new file mode 100644 index 00000000..a975c5fb --- /dev/null +++ b/Core/EnvironmentVariableStore.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security; +using System.Text.RegularExpressions; +using Core.Exceptions; +using Core.Lexer.Tokenization.Models; +namespace Core; +public sealed partial class EnvironmentVariableStore +{ + private readonly FrozenDictionary _devVariables; + + public EnvironmentVariableStore(string workingDirectory) + { + var devEnvFilePath = Path.Combine(workingDirectory, ".dev.vars"); + if (File.Exists(devEnvFilePath)) + { + _devVariables = File.ReadAllLines(devEnvFilePath) + .Select(line => line.Split('=', 2)) + .Where(parts => parts.Length == 2) + .ToDictionary(parts => parts[0], parts => parts[1]) + .ToFrozenDictionary(); + } + else + { + _devVariables = FrozenDictionary.Empty; + } + } + public int DevVarsCount => _devVariables.Count; + public IEnumerable DevVarNames => _devVariables.Keys; + + public string Replace(string input, List errors, Span span) + { + return TemplateRegex().Replace(input, match => + { + string varName = match.Groups[1].Value; + string? envVar = null; + try + { + envVar = Get(varName); + if (string.IsNullOrEmpty(envVar)) + { + errors.Add(new EnvironmentVariableNotFoundException(span, $"String substitution failed: environment variable '{varName}' was not found.")); + return string.Empty; + } + return envVar.Trim(); + } + catch (SecurityException) + { + errors.Add(new EnvironmentVariableNotFoundException(span, $"String substitution failed: environment variable '{varName}' was not found.")); + return string.Empty; + } + }); + } + + public string? Get(string variableName) + { + if (_devVariables.TryGetValue(variableName, out var value)) + { + return value.Trim(); + } + return Environment.GetEnvironmentVariable(variableName)?.Trim(); + } + + [GeneratedRegex(@"\$\{([^\}]+)\}")] + private static partial Regex TemplateRegex(); +} \ No newline at end of file diff --git a/Core/Exceptions/Exceptions.cs b/Core/Exceptions/Exceptions.cs index 24cb7edb..82ba6a23 100644 --- a/Core/Exceptions/Exceptions.cs +++ b/Core/Exceptions/Exceptions.cs @@ -407,6 +407,13 @@ public MissingValueForArgumentException(string identifier, string paramName, Spa : base($"Decorator '{identifier}' is missing a value for parameter '{paramName}'.", span, 146, hint) { } } + [Serializable] + public class EnvironmentVariableNotFoundException : SpanException + { + public EnvironmentVariableNotFoundException(Span span, string reason) + : base(reason, span, 147, severity: Severity.Error) + { } + } @@ -426,12 +433,4 @@ public DeprecatedFeatureWarning(Span span, string reason) { } } - [Serializable] - public class EnvironmentVariableNotFoundException : SpanException - { - public EnvironmentVariableNotFoundException(Span span, string reason) - : base(reason, span, 202, severity: Severity.Warning) - { } - } - } diff --git a/Core/Internal/EnvironmentVariableReplacer.cs b/Core/Internal/EnvironmentVariableReplacer.cs deleted file mode 100644 index e1a93832..00000000 --- a/Core/Internal/EnvironmentVariableReplacer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security; -using System.Text.RegularExpressions; -using Core.Exceptions; -using Core.Lexer.Tokenization.Models; -namespace Core.Internal; -internal static partial class EnvironmentVariableHelper -{ - - private static readonly Regex _templateRegex = TemplateRegex(); - - public static string Replace(string input, List errors, Span span) - { - return _templateRegex.Replace(input, match => - { - string varName = match.Groups[1].Value; - string? envVar = null; - try - { - envVar = Get(varName); - if (string.IsNullOrEmpty(envVar)) - { - errors.Add(new EnvironmentVariableNotFoundException(span, $"String substitution failed: environment variable '{varName}' was not found.")); - } - return envVar.Trim(); - } - catch (SecurityException) - { - errors.Add(new EnvironmentVariableNotFoundException(span, $"String substitution failed: environment variable '{varName}' was not found.")); - return string.Empty; - } - }); - } - - public static string Get(string variableName) => Environment.GetEnvironmentVariable(variableName)?.Trim() ?? string.Empty; - - [GeneratedRegex(@"\$\{([^\}]+)\}")] - private static partial Regex TemplateRegex(); -} \ No newline at end of file diff --git a/Core/Parser/SchemaParser.cs b/Core/Parser/SchemaParser.cs index 745a07a4..09295ff2 100644 --- a/Core/Parser/SchemaParser.cs +++ b/Core/Parser/SchemaParser.cs @@ -486,10 +486,11 @@ private bool EatEnvironmentVariable([NotNullWhen(true)] out string? envValue) Expect(TokenKind.CloseBrace); try { - envValue = EnvironmentVariableHelper.Get(envVar); + envValue = _compilerHost.EnvironmentVariableStore.Get(envVar); if (string.IsNullOrEmpty(envValue)) { _errors.Add(new EnvironmentVariableNotFoundException(Span.Combine(startSpan, CurrentToken.Span), $"String substitution failed: environment variable '{envVar}' was not found.")); + envValue = string.Empty; } return true; } @@ -539,7 +540,7 @@ private Literal ParseLiteral(TypeBase type) // the string has possible template variables if (str.Contains('$')) { - str = EnvironmentVariableHelper.Replace(str, _errors, token.Span); + str = _compilerHost.EnvironmentVariableStore.Replace(str, _errors, token.Span); } return new StringLiteral(st, token.Span, str); case ScalarType st when st.BaseType == BaseType.Guid: