Skip to content

Commit

Permalink
stdlib work
Browse files Browse the repository at this point in the history
Signed-off-by: George Lemon <[email protected]>
  • Loading branch information
georgelemon committed Mar 28, 2024
1 parent 7e46a4c commit 434be69
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 96 deletions.
83 changes: 79 additions & 4 deletions src/tim.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import std/[times, options, asyncdispatch,
import pkg/[watchout, httpx, websocketx]
import pkg/kapsis/cli

import tim/engine/[meta, parser, logging]
import tim/engine/[meta, parser, logging, std]
import tim/engine/compilers/html

from std/strutils import `%`, indent, split, parseInt, join
Expand All @@ -21,8 +21,8 @@ from std/xmltree import escape
const
DOCKTYPE = "<!DOCKTYPE html>"
defaultLayout = "base"

const localStorage* = CacheSeq"LocalStorage"
localStorage* = CacheSeq"LocalStorage"
# Compile-time Cache seq to handle local data

macro initCommonStorage*(x: untyped) =
## Initializes a common localStorage that can be
Expand Down Expand Up @@ -412,8 +412,83 @@ when defined napibuild:

elif not isMainModule:
# Expose Tim Engine API for Nim development (as a Nimble librayr)
export parser, html, json
import std/enumutils
import tim/engine/ast

export ast, parser, html, json, stdlib
export meta except TimEngine
export localModule, SourceCode, Arg, NodeType

proc initModule(modules: NimNode): NimNode =
result = newStmtList()
var functions: seq[string]
modules.expectKind nnkArgList
for mblock in modules[0]:
mblock.expectKind nnkBlockStmt
for m in mblock[1]:
case m.kind
of nnkProcDef:
let id = m[0]
var fn = "fn " & $m[0] & "*("
var fnReturnType: NodeType
var params: seq[string]
if m[3][0].kind != nnkEmpty:
for p in m[3][1..^1]:
add params, $p[0] & ":" & $p[1]
add fn, params.join(",")
add fn, "): "
add fn, $m[3][0]
fnReturnType = ast.getType(m[3][0])
else:
add fn, ")"
add functions, fn
var lambda = nnkLambda.newTree(newEmptyNode(), newEmptyNode(), newEmptyNode())
var procParams = newNimNode(nnkFormalParams)
procParams.add(
ident("Node"),
nnkIdentDefs.newTree(
ident("args"),
nnkBracketExpr.newTree(
ident("openarray"),
ident("Arg")
),
newEmptyNode()
),
nnkIdentDefs.newTree(
ident("returnType"),
ident("NodeType"),
ident(symbolName(ntLitString))
)
)
add lambda, procParams
add lambda, newEmptyNode()
add lambda, newEmptyNode()
add lambda, m[6]
add result,
newAssignment(
nnkBracketExpr.newTree(
ident"localModule", newLit($id)
),
lambda
)
else:
add result, m
add result,
newAssignment(
nnkBracketExpr.newTree(
ident("stdlib"),
newLit("*")
),
nnkTupleConstr.newTree(
ident("localModule"),
newCall(ident("SourceCode"), newLit(functions.join("\n")))
)
)
echo result.repr


macro initLocalModule*(x: varargs[untyped]): untyped =
initModule(x)

else:
# Build Tim Engine as a standalone CLI application
Expand Down
30 changes: 29 additions & 1 deletion src/tim/engine/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type
ntLitArray = "array"
ntLitObject = "object"
ntFunction = "function"
ntLitVoid = "void"

ntVariableDef = "Variable"
ntAssignExpr = "Assignment"
Expand All @@ -53,6 +54,12 @@ type
ntJsonSnippet = "JsonSnippet"
ntClientBlock = "ClientSideStatement"
ntStmtList = "StatementList"
ntRuntimeCode = "Runtime"

FunctionType* = enum
fnImportLocal
fnImportSystem
fnImportModule

CommandType* = enum
cmdEcho = "echo"
Expand Down Expand Up @@ -214,7 +221,8 @@ type
## if a function has no return type, then `ntUnknown`
## is used as default (void)
fnReturnHtmlElement*: HtmlTag
fnFwdDecl*, fnExport*, fnImportNim*: bool
fnFwdDecl*, fnExport*: bool
fnType*: FunctionType
fnSource*: string
of ntJavaScriptSnippet,
ntYamlSnippet, ntJsonSnippet:
Expand Down Expand Up @@ -248,6 +256,8 @@ type
## on the fly for templates marked as jit.
of ntStmtList:
stmtList*: seq[Node]
of ntRuntimeCode:
runtimeCode*: string
else: discard
meta*: Meta

Expand Down Expand Up @@ -439,6 +449,21 @@ proc getTag*(x: Node): string =
of tagWbr: "wbr"
else: x.stag # tagUnknown

proc getType*(x: NimNode): NodeType {.compileTime.} =
if x.strVal == "string":
return ntLitString
if x.strVal == "int":
return ntLitInt
if x.strVal == "float":
return ntLitFloat
if x.strVal == "bool":
return ntLitBool
if x.strVal == "object":
return ntLitObject
if x.strVal == "array":
return ntLitArray
result = ntUnknown

#
# AST to JSON convertors
#
Expand Down Expand Up @@ -510,6 +535,9 @@ proc newBool*(v: bool): Node =
result = newNode(ntLitBool)
result.bVal = v

var voidNode = newNode(ntLitVoid)
proc getVoidNode*(): Node = voidNode

proc newVariable*(varName: string, varValue: Node, meta: Meta): Node =
## Create a new variable definition Node
result = newNode(ntVariableDef)
Expand Down
72 changes: 46 additions & 26 deletions src/tim/engine/compilers/html.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import std/[tables, strutils, json,
jsonutils, options, terminal, sequtils]

import pkg/jsony
import ./tim, ../stdlib
import ./tim, ../std, ../parser

from std/xmltree import escape
from ../meta import TimEngine, TimTemplate, TimTemplateType,
Expand Down Expand Up @@ -88,7 +88,7 @@ const
domSetAttribute = "$1.setAttribute('$2','$3');"
domInsertAdjacentElement = "$1.insertAdjacentElement('beforeend',$2);"
domInnerText = "$1.innerText=\"$2\";"
stdlibPaths = ["std/system", "std/strings", "std/arrays", "std/os"]
stdlibPaths = ["std/system", "std/strings", "std/arrays", "std/os", "*"]

when not defined timStandalone:
# Scope API, available for library version of TimEngine
Expand All @@ -110,7 +110,7 @@ when not defined timStandalone:
else:
c.globalScope[node.varName] = node
of ntFunction:
if c.ast.src notin stdlibPaths:
if node.fnSource notin stdlibPaths:
if scopetables.len > 0:
scopetables[^1][node.fnIdent] = node
else: c.globalScope[node.fnIdent] = node
Expand Down Expand Up @@ -470,12 +470,15 @@ proc bracketEvaluator(c: var HtmlCompiler, node: Node,
scopetables: var seq[ScopeTable]): Node =
case node.bracketStorageType
of localStorage, globalStorage:
let x = c.evalStorage(node)
if likely(x != nil):
result = x.toTimNode
let index = c.getValue(node.bracketIndex, scopetables)
notnil index:
var x = c.evalStorage(node.bracketLHS)
notnil x:
result = x.toTimNode
return c.walkAccessorStorage(result, index, scopetables)
of scopeStorage:
let index = c.getValue(node.bracketIndex, scopetables)
if likely(index != nil):
notnil index:
result = c.walkAccessorStorage(node.bracketLHS, index, scopetables)

proc writeDotExpr(c: var HtmlCompiler,
Expand All @@ -498,7 +501,7 @@ proc evalCmd(c: var HtmlCompiler, node: Node,
return node.cmdValue
else:
var val = c.getValue(node.cmdValue, scopetables)
if val != nil:
notnil val:
case node.cmdType
of cmdEcho:
val.meta = node.cmdValue.meta
Expand Down Expand Up @@ -996,7 +999,7 @@ template loopEvaluator(kv, items: Node) =
of ntLitObject:
case kv.nt
of ntVariableDef:
for x, y in items.objectItems:
for k, y in items.objectItems:
newScope(scopetables)
node.loopItem.varValue = y
c.varExpr(node.loopItem, scopetables)
Expand Down Expand Up @@ -1039,8 +1042,15 @@ proc evalLoop(c: var HtmlCompiler, node: Node,
of ntIdent:
let some = c.getScope(node.loopItems.identName, scopetables)
if likely(some.scopeTable != nil):
let items = some.scopeTable[node.loopItems.identName]
loopEvaluator(node.loopItem, items.varValue)
var items: Node
case some.scopeTable[node.loopItems.identName].nt
of ntFunction:
items = c.unsafeCall(node.loopItems,
some.scopeTable[node.loopItems.identName], scopetables)
of ntVariableDef:
items = some.scopeTable[node.loopItems.identName].varValue
else: discard # error ?
loopEvaluator(node.loopItem, items)
else: compileErrorWithArgs(undeclaredVariable, [node.loopItems.identName])
of ntDotExpr:
let items = c.dotEvaluator(node.loopItems, scopetables)
Expand Down Expand Up @@ -1160,12 +1170,8 @@ proc fnDef(c: var HtmlCompiler, node: Node,
if p.pImplVal.nt != p.pType:
compileErrorWithArgs(typeMismatch,
[$(p.pImplVal.nt), $p.pType], p.meta)
# if node.fnReturnType != ntUnknown:
# check if function has a return type
# where tkUnknown acts like a void
c.stack(node.fnIdent, node, scopetables)
else:
# if node.fnParams
compileErrorWithArgs(fnRedefine, [node.fnIdent])

proc unsafeCall(c: var HtmlCompiler, node, fnNode: Node,
Expand All @@ -1177,20 +1183,34 @@ proc unsafeCall(c: var HtmlCompiler, node, fnNode: Node,
# is matching the total number of parameters
if node.identArgs.len > 0:
var i = 0
if fnNode.fnImportNim:
var args: seq[stdlib.Arg]
if fnNode.fnType in {fnImportSystem, fnImportModule}:
var args: seq[std.Arg]
for i in 0..node.identArgs.high:
try:
let param = fnNode.fnParams[params[i]]
let argValue = c.getValue(node.identArgs[i], scopetables)
if c.typeCheck(argValue, param[1]):
add args, (param[0][1..^1], argValue)
else: return # typeCheck returns `typeMismatch`
notnil argValue:
if c.typeCheck(argValue, param[1]):
add args, (param[0][1..^1], argValue)
else: return # typeCheck returns `typeMismatch`
do:
compileErrorWithArgs(fnReturnVoid, ["?"])
except Defect:
compileErrorWithArgs(fnExtraArg,
[node.identName, $(params.len), $(node.identArgs.len)])
try:
return stdlib.call(fnNode.fnSource, node.identName, args)
result = std.call(fnNode.fnSource, node.identName, args)
if result != nil:
case result.nt
of ntRuntimeCode:
{.gcsafe.}:
var p: Parser = parser.parseSnippet("", result.runtimeCode)
let phc = newCompiler(p.getAst)
if not phc.hasErrors:
add c.output, phc.getHtml()
return nil
else: discard
return # result
except SystemModule as e:
compileErrorWithArgs(internalError,
[e.msg, fnNode.fnSource, fnNode.fnIdent], node.meta)
Expand Down Expand Up @@ -1436,10 +1456,11 @@ proc walkNodes(c: var HtmlCompiler, nodes: seq[Node],
# if parentNodeType != ntFunction and x.nt != ntHtmlElement:
# compileErrorWithArgs(fnReturnMissingCommand, [node.identName, $(x.nt)])
let x: Node = c.getValue(node, scopetables)
if not c.isClientSide:
write x, true, node.identSafe
else:
add c.jsOutputCode, domInnerText % [xel, x.toString()]
notnil x:
if not c.isClientSide:
write x, true, node.identSafe
else:
add c.jsOutputCode, domInnerText % [xel, x.toString()]
of ntDotExpr:
let x: Node = c.dotEvaluator(node, scopetables)
if not c.isClientSide:
Expand Down Expand Up @@ -1565,7 +1586,6 @@ else:
)
if minify: setLen(result.nl, 0)
var scopetables = newSeq[ScopeTable]()

result.walkNodes(result.ast.nodes, scopetables)

proc newCompiler*(ast: Ast, minify = true, indent = 2): HtmlCompiler =
Expand Down
8 changes: 7 additions & 1 deletion src/tim/engine/meta.nim
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
# Made by Humans from OpenPeeps
# https://github.com/openpeeps/tim

import std/[macros, os, json, strutils, sequtils, base64, tables]
import std/[macros, os, json,
strutils, sequtils, base64, tables]
import pkg/[checksums/md5, flatty]

export getProjectPath
Expand Down Expand Up @@ -38,6 +39,10 @@ type
TemplateTable = TableRef[string, TimTemplate]

TimCallback* = proc() {.nimcall, gcsafe.}

TimPolicy* = ref object
# todo

TimEngine* = ref object
base, src, output: string
minify, htmlErrors: bool
Expand All @@ -46,6 +51,7 @@ type
errors*: seq[string]
placeholders: Table[string, seq[Ast]]
## A table containing available placeholders
policy: TimPolicy
when defined timStandalone:
globals: Globals
else:
Expand Down
Loading

0 comments on commit 434be69

Please sign in to comment.