Skip to content

Commit

Permalink
Const literal parsing and prompting
Browse files Browse the repository at this point in the history
Added some basic bits needed to operationalize components in Chalk, such as being able to ask for parameters on the command line (see params.nim) and to parse inputs that are expected to be Con4m literals.
Note if the expected type is 'string' then we accept their entire input without quotes. Otherwise, we parse the text under the assumption it's a con4m literal.
  • Loading branch information
viega committed Oct 9, 2023
1 parent e974465 commit dc55752
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 125 deletions.
4 changes: 2 additions & 2 deletions files/con4m.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@

import con4m/[errmsg, types, lex, parse, st, builtins, treecheck, typecheck,
eval, dollars, spec, run, c42spec, getopts, stack, legacy, doc,
components, strcursor]
components, strcursor, params]
import streams, nimutils, os
export errmsg, types, lex, parse, st, builtins, treecheck, typecheck, eval,
dollars, spec, run, c42spec, getopts, stack, legacy, doc, components,
strcursor
strcursor, params

const compilerC42FileName = "con4m/c4m/compiler-config.c42spec"
const compilerConfigFName = "con4m/c4m/c4m-cmdline.c4m"
Expand Down
132 changes: 110 additions & 22 deletions files/con4m/components.nim
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ proc cacheComponent*(component: ComponentInfo, str: string, force = false) =
proc cacheComponent*(component: ComponentInfo, stream: Stream) =
component.cacheComponent(stream.readAll())

proc fetchComponent*(item: ComponentInfo, extension = ".c4m") =
proc fetchComponent*(item: ComponentInfo, extension = ".c4m", force = false) =
var source: string

if item.hash == "":
if force or item.hash == "":
if item.url.startsWith("https://"):
source = item.url.fetchAttempt(extension)

Expand All @@ -89,20 +89,41 @@ proc fetchComponent*(item: ComponentInfo, extension = ".c4m") =
raise newException(IOError, "Could not retrieve needed source " &
"file: " & item.url)

item.cacheComponent(source)
item.cacheComponent(source, force)

proc fetchComponent*(s: ConfigState, name, loc: string, extension = ".c4m"):
ComponentInfo =
proc fetchComponent*(s: ConfigState, name, loc: string, extension = ".c4m",
force = false): ComponentInfo =
## This returns a parsed component, but does NOT go beyond that. The
## parse phase will NOT attempt to semantically validate a component,
## will NOT go and fetch dependent comonents, and will NOT do cycle
## checking at all. Use loadComponent below for those.
##
## This will raise an exception if anything goes wrong.

result = s.getComponentReference(name & extension, loc)
result = s.getComponentReference(name, loc)

fetchComponent(result, extension)
fetchComponent(result, extension, force)

proc getUsedComponents*(component: ComponentInfo, paramOnly = false):
seq[ComponentInfo] =
var
allDependents: seq[ComponentInfo] = @[component]

for sub in component.componentsUsed:
let sublist = sub.getUsedComponents()
for item in sublist:
if item == component:
raise newException(ValueError, "Cyclical components not allowed-- " &
"component " & component.url & " can import itself")
if item notin allDependents:
allDependents.add(item)

if not paramOnly:
return allDependents
else:
for item in allDependents:
if item.varParams.len() != 0 or item.attrParams.len() != 0:
result.add(item)

proc loadComponent*(s: ConfigState, component: ComponentInfo):
seq[ComponentInfo] {.discardable.} =
Expand Down Expand Up @@ -134,21 +155,57 @@ proc loadComponent*(s: ConfigState, component: ComponentInfo):

if component in result:
raise newException(ValueError, "Cyclical components are not allowed-- " &
"component " & component.url & "can import itself")
"component " & component.url & " can import itself")

proc loadCurrentComponent*(s: ConfigState) =
s.loadComponent(s.currentComponent)
proc fullUrlToParts*(url: string): (string, string, string) =
var fullPath: string

template setParamValue*(s: ConfigState,
componentName: string,
location: string,
paramName: string,
value: Box,
valueType: Con4mType,
paramStore: untyped) =
if url.startswith("http://"):
raise newException(ValueError, "Only https URLs and local files accepted")
if url.startswith("https://") or url.startswith("/"):
fullPath = url
else:
fullPath = url.resolvePath()

let component = s.getComponentReference(componentName, location)
result = fullPath.splitFile()

proc componentAtUrl*(s: ConfigState, url: string, force: bool): ComponentInfo =
## Unlike the rest of this API, this call assumes the url is either:
## - A full https URL or;
## - A local filename, either as an absolutely path or relative to cwd.
##
## Here, unlike the other functions, we look for a file extension and chop
## it off.

let
(base, module, ext) = fullUrlToParts(url)

result = s.fetchComponent(module, base, ext, force)

s.loadComponent(result)

proc loadComponentFromUrl*(s: ConfigState, url: string): ComponentInfo =
return s.componentAtUrl(url, force = true)

proc haveComponentFromUrl*(s: ConfigState, url: string): Option[ComponentInfo] =
## Does not fetch, only returns the component if we're using it.

let
(base, module, ext) = fullUrlToParts(url)
component = s.fetchComponent(module, base, ext)

if component.source != "":
return some(component)

proc loadCurrentComponent*(s: ConfigState) =
s.loadComponent(s.currentComponent)

template setParamValue*(s: ConfigState,
component: ComponentInfo,
paramName: string,
value: Box,
valueType: Con4mType,
paramStore: untyped) =
if paramName notin component.paramStore:
raise newException(ValueError, "Parameter not found: " & paramName)

Expand All @@ -159,23 +216,54 @@ template setParamValue*(s: ConfigState,

parameter.value = some(value)

proc setVariableParamValue*(s: ConfigState,
component: ComponentInfo,
paramName: string,
value: Box,
valueType: Con4mType) =
s.setParamValue(component, paramName, value, valueType, varParams)


proc setAttributeParamValue*(s: ConfigState,
component: ComponentInfo,
paramName: string,
value: Box,
valueType: Con4mType) =
s.setParamValue(component, paramName, value, valueType, attrParams)

proc setVariableParamValue*(s: ConfigState,
urlKey: string,
paramName: string,
value: Box,
valueType: Con4mType) =
let component = s.getComponentReference(urlKey)
s.setParamValue(component, paramName, value, valueType, varParams)

proc setAttribueParamValue*(s: ConfigState,
urlKey: string,
paramName: string,
value: Box,
valueType: Con4mType) =
let component = s.getComponentReference(urlKey)
s.setParamValue(component, paramName, value, valueType, attrParams)

proc setVariableParamValue*(s: ConfigState,
componentName: string,
location: string,
paramName: string,
value: Box,
valueType: Con4mType) =
s.setParamValue(componentName, location, paramName, value, valueType,
varParams)
let component = s.getComponentReference(componentName, location)
s.setParamValue(component, paramName, value, valueType, varParams)

proc setAttributeParamValue*(s: ConfigState,
componentName: string,
location: string,
paramName: string,
value: Box,
valueType: Con4mType) =
s.setParamValue(componentName, location, paramName, value, valueType,
attrParams)
let component = s.getComponentReference(componentName, location)
s.setParamValue(component, paramName, value, valueType, attrParams)

proc getAllVariableParamInfo*(s: ConfigState,
name, location: string): seq[ParameterInfo] =
Expand Down
55 changes: 55 additions & 0 deletions files/con4m/params.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import types, treecheck, options, tables, nimutils, eval, dollars


proc basicConfigureOneParam(state: ConfigState,
component: ComponentInfo,
param: ParameterInfo) =
var
boxOpt: Option[Box]
default = "<<no default>>"

if param.value.isSome():
boxOpt = param.value
elif param.default.isSome():
boxOpt = param.default
elif param.defaultCb.isSome():
boxOpt = state.sCall(param.defaultCb.get(), @[])

if boxOpt.isSome():
default = param.defaultType.oneArgToString(boxOpt.get())
let
short = param.shortDoc.getOrElse("No description provided")
long = param.doc.getOrElse("")

print("### Configuring: " & param.name & " -- " & short & "\n" & long)

if boxOpt.isSome():
print("Press [enter] to accept default, or enter a value: ")

while true:
let line = stdin.readLine()

if line != "":
try:
boxOpt = some(line.parseConstLiteral(param.defaultType))
except:
print("<red>error:</red> " & getCurrentExceptionMsg())

if boxOpt.isNone():
print("<red>error:</red> Must enter a valid value.")
continue
elif line == "":
print("<atomiclime>success:</atomiclime> Accepting the default value.")
else:
print("<atomiclime>success:</atomiclime> Value accepted.")

param.value = boxOpt
break

proc basicConfigureParameters*(state: ConfigState,
component: ComponentInfo,
componentList: seq[ComponentInfo]) =
print("# Congiguring Component: " & component.url)
for subcomp in componentList:
for _, param in subcomp.varParams:
state.basicConfigureOneParam(subcomp, param)
25 changes: 25 additions & 0 deletions files/con4m/parse.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1149,3 +1149,28 @@ proc parse*(s: Stream, filename: string = "<<unknown>>"): Con4mNode =

result = toParse.parse(filename)
s.close()

proc parseLiteral*(s: string): Con4mNode =
let
(valid, tokens) = s.lex()

if not valid:
let
tok = tokens[^1]
msg = case tok.kind:
of ErrorTok: "Invalid character found"
of ErrorLongComment: "Unterminated comment"
of ErrorStringLit: "Unterminated string"
of ErrorCharLit: "Invalid char literal"
of ErrorOtherLit: "Unterminated literal"
else: "Unknown error"

fatal(msg, tok)
var ctx = ParseCtx(tokens: tokens,
curTokIx: 0,
nesting: 0,
nlWatch: false)
setCurrentFileName("<<literal parser>>")
result = ctx.literal()
if ctx.curTok().kind != TtEof:
parseError("Invalid contents after literal")
Loading

0 comments on commit dc55752

Please sign in to comment.