Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions src/Sage.Engine.Tests/CompilerTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Copyright (c) 2022, salesforce.com, inc.
// Copyright (c) 2022, salesforce.com, inc.
// All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/Apache-2.0

using Microsoft.Extensions.DependencyInjection;
using Sage.Engine.Runtime;

namespace Sage.Engine.Tests
Expand All @@ -22,7 +23,7 @@ public void TestAssemblyGeneration(string sourceFile)
string pathToTest = Path.Combine(TestContext.CurrentContext.TestDirectory, "Corpus", "Compiler", sourceFile);

Compiler.CompilationOptions options = new CompilerOptionsBuilder()
.WithInputFile(new FileInfo(pathToTest))
.WithContent(new LocalFileContent(pathToTest, 1))
.Build();

CompileResult result = CSharpCompiler.GenerateAssemblyFromSource(options);
Expand All @@ -42,5 +43,18 @@ public void TestAssemblyGeneration(string sourceFile)
?.Invoke(null, variables);
Assert.That(context.PopContext(), Is.EqualTo("Hello World"));
}

[Test]
[TestCase("%%=ADD(1,2)=%%", ContentType.AMPscript, "3")]
public void TestRenderer(string input, ContentType type, string expected)
{
var content = new EmbeddedContent(input, "TEST", "TEST", 1, type);
CompilationOptions options = new CompilerOptionsBuilder()
.WithContent(content)
.Build();
var result = _serviceProvider.GetRequiredService<Renderer>().Render(options);

Assert.That(result, Is.EqualTo(expected));
}
}
}
}
4 changes: 2 additions & 2 deletions src/Sage.Engine.Tests/SageTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Sage.Engine.Compiler;
using Sage.Engine.DependencyInjection;
using Sage.Engine.Extensions;
using Sage.Engine.Tests.Compatibility;
Expand Down Expand Up @@ -51,8 +52,7 @@ protected CompilationOptions TestCompilationOptions()
{
return new CompilationOptions()
{
InputFile = new FileInfo("TEST.ampscript"),
InputName = "TEST",
Content = new EmbeddedContent("TEST", "TEST", "TEST.ampscript", 1, ContentType.AMPscript),
SymbolStream = new MemoryStream(),
AssemblyStream = new MemoryStream(),
OptimizationLevel = OptimizationLevel.Debug,
Expand Down
3 changes: 2 additions & 1 deletion src/Sage.Engine.Tests/TestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ private static RuntimeContext GetTestRuntimeContext(IServiceProvider serviceProv
options,
test.SubscriberContext);
}


/// <summary>
/// Executes the engine and gets the expected result from the engine
/// </summary>
public static EngineTestResult GetOutputFromTest(IServiceProvider serviceProvider, CorpusData test)
{
CompilationOptions options = new CompilerOptionsBuilder()
.WithSourceCode(test.FileFriendlyName, test.Code)
.WithContent(new EmbeddedContent(test.Code, test.FileFriendlyName, test.FileFriendlyName, 1, ContentType.AMPscript))
.Build();

try
Expand Down
13 changes: 1 addition & 12 deletions src/Sage.Engine/CommandLine/CommandLineExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,12 @@
// SPDX-License-Identifier: Apache-2.0
// For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/Apache-2.0

using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.NamingConventionBinder;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Antlr4.Runtime.Misc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Sage.Engine.CommandLine;
using Sage.Engine.Compiler;
using Sage.Engine.Extensions;
using static Sage.Engine.AmpscriptCommand;

namespace Sage.Engine
{
Expand Down Expand Up @@ -52,7 +41,7 @@ public static void AddSageLaunchCommand(

appServices.Configure<CompilationOptions>((o) =>
{
o.InputFile = ampscriptOption.Source;
o.Content = new LocalFileContent(ampscriptOption.Source.FullName, 1);
o.GeneratedMethodName = CompilerOptionsBuilder.BuildMethodFromFilename(ampscriptOption.Source.FullName);
});
});
Expand Down
25 changes: 25 additions & 0 deletions src/Sage.Engine/Compiler/AmpscriptCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) 2022, salesforce.com, inc.
// All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/Apache-2.0

using Sage.Engine.Runtime;

namespace Sage.Engine.Compiler
{
/// <summary>
/// A class that compiles AMPscript source code into C# and optionally will execute it.
/// </summary>
internal class AmpscriptCompiler : IAmpscriptCompiler
{
public bool CanCompile(IContent content)
{
return content.ContentType == ContentType.AMPscript;
}

public string Compile(CompilationOptions options, RuntimeContext runtimeContext, SubscriberContext context)
{
return CSharpCompiler.CompileAndExecute(options, runtimeContext, out var _);
}
}
}
19 changes: 6 additions & 13 deletions src/Sage.Engine/Compiler/CSharpCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@
{
public static string CompileAndExecute(CompilationOptions options, RuntimeContext context, out CompileResult result)
{
if (string.IsNullOrEmpty(options.SourceCode))
{
options.SourceCode = File.ReadAllText(options.InputFile.FullName);
}
result = GenerateAssemblyFromSource(options);

result.Execute(context);
Expand All @@ -46,12 +42,9 @@
/// </summary>
public static CompileResult GenerateAssemblyFromSource(CompilationOptions options)
{
if (options.SourceCode == null)
{
throw new ArgumentNullException(nameof(options.SourceCode));
}
string code = options.Content.GetTextReader().ReadToEnd();

Check warning on line 45 in src/Sage.Engine/Compiler/CSharpCompiler.cs

View workflow job for this annotation

GitHub Actions / build_test / build_test_archive

Dereference of a possibly null reference.

CSharpTranspiler transpiler = CSharpTranspiler.CreateFromSource(options.InputFile.FullName, options.GeneratedMethodName, options.SourceCode);
CSharpTranspiler transpiler = CSharpTranspiler.CreateFromSource(options.Content.Location, options.GeneratedMethodName, code);

CompilationUnitSyntax compilationUnit = transpiler.GenerateCode();

Expand All @@ -63,7 +56,7 @@
(EmitResult emitResult, Assembly? assembly, AssemblyLoadContext? assemblyContext) = GenerateAssembly(options, compilation, sourceText);

return new CompileResult(
options.InputFile.FullName,
options.Content.Location,
options.GeneratedMethodName,
compilationUnit,
compilation,
Expand Down Expand Up @@ -106,7 +99,7 @@

SyntaxTree encoded = CSharpSyntaxTree.Create(
syntaxRootNode,
path: $"{options.InputName}.generated.cs",
path: $"{options.Content.Name}.generated.cs",
encoding: encoding);

var compilation = CSharpCompilation.Create(
Expand All @@ -130,7 +123,7 @@

var embeddedTexts = new List<EmbeddedText>
{
EmbeddedText.FromSource($"{options.InputName}.generated.cs", sourceText)
EmbeddedText.FromSource($"{options.Content.Name}.generated.cs", sourceText)
};

EmitResult emitResult = compilation.Emit(
Expand All @@ -144,7 +137,7 @@

if (emitResult.Success)
{
var context = new AssemblyLoadContext(options.InputName, true);
var context = new AssemblyLoadContext(options.Content.Location, true);

Assembly assembly = context.LoadFromStream(options.AssemblyStream, options.SymbolStream);

Expand Down
10 changes: 3 additions & 7 deletions src/Sage.Engine/Compiler/CompilationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,15 @@ namespace Sage.Engine.Compiler
/// <summary>
/// Options specifying how the AMPscript code compiler should operate
/// </summary>
/// <param name="InputName">Name of the input, usually the file name of the file</param>
/// <param name="InputFile">Path to the input ampscript file. Can be relative to the current working directory or absolute.</param>
/// <param name="SourceCode">The AMPscript source code</param>
/// <param name="SourceCode">The name of the generated method in the assembly</param>
/// <param name="Content">The AMPscript source code</param>
/// <param name="GeneratedMethodName">The name of the generated method in the assembly</param>
/// <param name="OptimizationLevel">Optimization level of the generated code</param>
/// <param name="OutputDirectory">What directory to output generated files</param>
/// <param name="AssemblyStream">Stream for the assembly to be generated into</param>
/// <param name="SymbolStream">Stream for the symbol file to be generated into</param>
public class CompilationOptions
{
public required string InputName { get; set; }
public required FileInfo InputFile { get; set; }
public string? SourceCode { get; set; }
public required IContent Content { get; set; }
public required string GeneratedMethodName { get; set; }
public required OptimizationLevel OptimizationLevel { get; set; }
public required DirectoryInfo OutputDirectory { get; set; }
Expand Down
47 changes: 7 additions & 40 deletions src/Sage.Engine/Compiler/CompilerOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/// </summary>
public class CompilerOptionsBuilder
{
public CompilerOptionsBuilder()

Check warning on line 16 in src/Sage.Engine/Compiler/CompilerOptionsBuilder.cs

View workflow job for this annotation

GitHub Actions / build_test / build_test_archive

Non-nullable property 'Content' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
{
this.AssemblyStream = new MemoryStream();
this.SymbolStream = new MemoryStream();
Expand All @@ -21,7 +21,7 @@
this.OutputDirectory = new DirectoryInfo(Environment.CurrentDirectory);
}

public CompilerOptionsBuilder(CompilationOptions current)

Check warning on line 24 in src/Sage.Engine/Compiler/CompilerOptionsBuilder.cs

View workflow job for this annotation

GitHub Actions / build_test / build_test_archive

Non-nullable property 'Content' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
{
this.AssemblyStream = new MemoryStream();
this.SymbolStream = new MemoryStream();
Expand All @@ -29,48 +29,17 @@
this.OutputDirectory = current.OutputDirectory;
}

/// <summary>
/// Name of the input, usually the file name of the file
/// </summary>
public string? InputName { get; private set; }

/// <summary>
/// Path to the input ampscript file. Can be relative to the current working directory or absolute.
/// </summary>
public FileInfo? InputFile { get; private set; }

/// <summary>
/// The AMPscript source code
/// </summary>
public string? SourceCode { get; private set; }
public IContent Content { get; private set; }

/// <summary>
/// Use the provided source file to compile code. This is not compatible with WithSourceCode.
/// </summary>
public CompilerOptionsBuilder WithInputFile(FileInfo inputFile)
public CompilerOptionsBuilder WithContent(IContent content)
{
this.InputFile = inputFile;
this.SourceCode = File.ReadAllText(inputFile.FullName);
this.InputName = inputFile.Name;

return this;
}

/// <summary>
/// This is dynamically generated code to compile that did not come from a file on disk.
/// </summary>
public CompilerOptionsBuilder WithSourceCode(string name, string sourceCode)
{
this.InputName = name;

if (!this.OutputDirectory.Exists)
{
this.OutputDirectory.Create();
}

this.InputFile = new FileInfo(Path.Combine(this.OutputDirectory.FullName, name + ".generated.ampscript"));
File.WriteAllText(InputFile.FullName, this.SourceCode);
this.SourceCode = sourceCode;
Content = content;

return this;
}
Expand Down Expand Up @@ -157,18 +126,16 @@
/// </summary>
public CompilationOptions Build()
{
if (this.InputName == null || this.InputFile == null || this.SourceCode == null)
if (this.Content == null)
{
throw new InvalidOperationException("No input has been specified");
throw new InvalidOperationException("No content has been specified");
}

string generatedMethodName = BuildMethodFromFilename(this.InputName);
string generatedMethodName = BuildMethodFromFilename(Content.Name);

return new CompilationOptions()
{
InputName = this.InputName,
InputFile = this.InputFile,
SourceCode = this.SourceCode,
Content = Content,
GeneratedMethodName = generatedMethodName.ToString(),
OptimizationLevel = this.OptimizationLevel,
OutputDirectory = this.OutputDirectory,
Expand Down
14 changes: 14 additions & 0 deletions src/Sage.Engine/Compiler/IAmpscriptCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2023, salesforce.com, inc.
// All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/Apache-2.0

namespace Sage.Engine.Compiler
{
/// <summary>
/// Renders a piece of AMPscript content to a string.
/// </summary>
public interface IAmpscriptCompiler : ICompiler
{
}
}
8 changes: 8 additions & 0 deletions src/Sage.Engine/Content/ContentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,13 @@ public static void AddLocalDiskContentClient(this IServiceCollection services, A
services.Configure(options);
}
}

/// <summary>
/// Infers the type of content based on the path of the file.
/// </summary>
public static ContentType InferContentTypeFromFilename(string path)
{
return ContentType.AMPscript;
}
}
}
40 changes: 40 additions & 0 deletions src/Sage.Engine/Content/EmbeddedFileContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) 2022, salesforce.com, inc.
// All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/Apache-2.0

namespace Sage.Engine.Compiler
{
/// <summary>
/// Content that is contained within other content.
/// </summary>
/// <remarks>
/// Recursive rendering, such as TREATASCONTENT, enables referencing content within content. This is the representation of that.
/// </remarks>
public class EmbeddedContent : IContent
{
public string Name { get; private set; }

public string Location { get; }

public int ContentVersion { get; private set; }

public ContentType ContentType { get; }

private string _content;

public EmbeddedContent(string content, string name, string location, int contentVersion, ContentType type)
{
_content = content;
Name = name;
Location = location;
ContentVersion = contentVersion;
ContentType = type;
}

public TextReader? GetTextReader()
{
return new StringReader(_content);
}
}
}
Loading
Loading