Skip to content

Commit

Permalink
template rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
SchlenkR committed Oct 20, 2023
1 parent ef83af7 commit b073e6a
Show file tree
Hide file tree
Showing 14 changed files with 570 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Trulla.SourceGenerator.Text
module Trulla.Core.Text

open System.Text

Expand Down
132 changes: 132 additions & 0 deletions src/NodeCli/CodeGen/Renderer.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
module Trulla.NodeCli.Renderer

open System
open TheBlunt
open Trulla.Core
open Trulla.Core.Utils
open Trulla.Core.Text

let [<Literal>] rootIdentifier = "model"
let [<Literal>] dotIntoMember = "."

module MemberExp =
let getLastSegment = function
| AccessExp accExp -> accExp.memberName
| IdentExp ident -> ident

let makeTypeName (potentialRecordNames: RecordDef list) tvar =
potentialRecordNames
|> List.find (fun x -> x.id = tvar)
|> fun x ->
match x.name.ToCharArray() |> Array.toList with
| c::cs -> Char.ToUpperInvariant c :: cs
| [] -> failwith "Empty possible record name is not supported."
|> List.toArray
|> String

// TODO: Make that configurable
let rec toTypeName potentialRecordNames typ =
match typ with
| Mono KnownTypes.string -> "string"
| Mono KnownTypes.bool -> "bool"
| Poly (KnownTypes.sequence, pt) -> $"List<{toTypeName potentialRecordNames pt}>"
| Record tvar -> makeTypeName potentialRecordNames tvar
// TODO: See comments in ModelInference / FinalTyp
//| Var _ -> "obj"
| _ -> failwithf "Unsupported reference for type '%A'." typ

let rec memberExpToIdent (exp: TVal<MemberExp>) =
match exp.value with
| IdentExp ident ->
let isBound = exp.bindingContext |> Map.containsKey ident
let rootPrefix = if isBound then "" else rootIdentifier + dotIntoMember
rootPrefix + ident
| AccessExp acc -> (memberExpToIdent acc.instanceExp) + dotIntoMember + acc.memberName

let renderTemplate (solution: Solution) (namespaceName: string) =
do namespaceName |> String.assertLetterDigitUnderscore "namespace"

let doubleQuotLit = "\""

let toStringLiteral (txt: string) =
// TODO: Escape that let txt = Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(txt, false)
let txt = txt
doubleQuotLit + txt + doubleQuotLit

text {
ln0 $"namespace {namespaceName};"
br

ln0 "using System;"
ln0 "using System.Collections.Generic;"
ln0 "using System.Linq;"
br

// render records
let records = solution.records
// TODO: Why this?
//if solution.records |> Map.containsKey Root
//then solution.records
//else solution.records |> Map.add Root []
for r in records do
let indent = 0
lni indent $"public record {makeTypeName records r.id} {{"
for field in r.fields do
lni (indent + 1) $"public required {toTypeName records field.typ} {field.name} {{ get; init; }}"
lni indent "}"
br

lni 0 $"public static class Rendering {{"
lni 1 $"public static string Render(this {makeTypeName solution.records Root} {rootIdentifier}) {{"

let sbAppend indent (txt: string) = text {
lni indent $"""__sb.Append({txt});"""
}
lni 2 "var __sb = new System.Text.StringBuilder();"

let rec render indent tree = text {
for texp in tree do
match texp with
| Text txt ->
sbAppend indent (toStringLiteral txt)
| Hole hole ->
sbAppend indent (memberExpToIdent hole)
| For (ident,exp,sep,body) ->
let elems = memberExpToIdent exp
let xIdent = $"x_{indent}"
let idxIdent = $"idx_{indent}"
lni indent $"foreach (var ({idxIdent},{ident.value}) in {elems}.Select(({xIdent},{idxIdent}) => ({idxIdent},{xIdent}))) {{"
render (indent + 1) body
let sep = sep.result |> Option.defaultValue ""
lni (indent + 1) $"""if ({idxIdent} < {elems}.Count - 1) {{"""
sbAppend (indent + 2) (toStringLiteral sep)
lni (indent + 1) "}"
lni indent "}"
| If (cond,body) ->
lni indent $"if ({memberExpToIdent cond}) {{"
render (indent + 1) body
lni indent "}"
| Else (cond,body) ->
lni indent "else {"
render (indent + 1) body
lni indent "}"
}

//lni (indent + 2) "}"

render 2 solution.tree
br
lni 2 "return __sb.ToString();"

lni 1 "}"

lni 0 "}"
}

let renderErrors(errors: TrullaError seq) =
let errorList = [ for error in errors do error.ToString() ]
$"""
Errors in Trulla template:
{String.concat "\n\n" errorList};
"""
4 changes: 3 additions & 1 deletion src/NodeCli/NodeCli.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
<Compile Include="..\Common\CoreFiles\Solver.fs" Link="CoreFiles\Solver.fs" />
<!-- Do not include this file in Fable compilation;. It won't compile. -->
<!-- <Compile Include="..\Common\CoreFiles\ReflectionRenderer.fs" Link="CoreFiles\ReflectionRenderer.fs" /> -->
<Compile Include="..\Common\CoreFiles\Text.fs" Link="CoreFiles\Text.fs" />

<Compile Include="trulla.fs" />
<Compile Include="CodeGen\Renderer.fs" />
<Compile Include="Trulla.fs" />
</ItemGroup>

<ItemGroup>
Expand Down
9 changes: 9 additions & 0 deletions src/NodeCli/TestTemplate.trulla
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Hello {{user.name}}, how are you?

Your Orders
===

{{for order in orders|---}}
ID: {{order.id}}
({{if order.isActive}}active{{else}}inactive{{end}})
{{end}}
2 changes: 1 addition & 1 deletion src/NodeCli/exe/trulla.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/usr/bin/env node
import '../lib/trulla.js';
import '../lib/Trulla.js';
Loading

0 comments on commit b073e6a

Please sign in to comment.