Skip to content

Commit

Permalink
Merge pull request wyvernlang#335 from jennyafish/io-library
Browse files Browse the repository at this point in the history
printformatting TSL
  • Loading branch information
JonathanAldrich authored Jul 3, 2019
2 parents 28c015c + 17d2b84 commit 8b92a6d
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 13 deletions.
17 changes: 15 additions & 2 deletions examples/io-lib-redesign/stdioTest.wyv
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,21 @@ import stdout
import stdin
import wyvern.option
type Option = option.Option
val filesys = fileSystem(java)
//val filesys = fileSystem(java)
val out = stdout(java)
val in = stdin(java)

in.read()
val prompt : Int = in.read()
out.print("the int ")
out.printInt(prompt)
out.println()

val promptStr : Option[String] = in.readLine()
out.print("hello " + promptStr.getOrElse(() => "none"))
out.println()

in.close()

//in.read() //this throws an error after closing stdin

//just reads in int from stdin without waiting for \n
212 changes: 212 additions & 0 deletions examples/tsls/formatstring.wyv
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
module formatstring

import wyvern.ast
import wyvern.option
import wyvern.util.matching.regex
import metadata wyvern.collections.list
import wyvern.Int
import wyvern.String

type AST = ast.AST

type ParsedExpr
def expr():option.Option[AST]
def rest():String

def makeParsedExpr(e:option.Option[AST],r:String):ParsedExpr = new
def expr():option.Option[AST] = e
def rest():String = r

// recursively determines position of the rbrace ending the expression, -1 if not found
def getExprPos(input : String, lbraceCt : Int, inString : Boolean, pos : Int) : Int
if (input.length() == pos)
-1
else
val c = input.charAt(pos)
if (c == #"}" || c == #"{" || c == #"\"")
(c == #"}").ifTrue(
() => (lbraceCt == 0 && !inString).ifTrue(
() => pos,
() => inString.ifTrue(
() => getExprPos(input, lbraceCt, inString, pos+1),
() => getExprPos(input, lbraceCt - 1, inString, pos+1)
)
),
() => (c == #"\"").ifTrue(
() => (pos == 0).ifTrue(
() => getExprPos(input, lbraceCt, !inString, pos+1),
() => (input.charAt(pos-1) == #"\\").ifTrue(
() => getExprPos(input, lbraceCt, inString, pos+1),
() => getExprPos(input, lbraceCt, !inString, pos+1)
)
),
() => (c == #"{" && !inString).ifTrue(
() => getExprPos(input, lbraceCt+1, inString, pos+1),
() => getExprPos(input, lbraceCt, inString, pos+1)
)
)
)
else
getExprPos(input, lbraceCt, inString, pos+1)

// takes in a Wyvern expression as a string and returns a string wrapped in an AST
// floatPrecision -1 indicates that no float precision was specified
def convStringAST(input : String, ctx : system.Context, floatPrecision : Int) : option.Option[AST]
val exp : AST = ast.parseExpression(input, ctx)
val ty : ast.Type = ast.getType(exp, ctx)
if (ast.types.equals(ty, ast.types.string(), ctx))
option.Some[AST](exp)
else
val str = ast.parseExpression("String", ctx) //wyvern.String
if (ast.types.equals(ty, ast.types.int(), ctx))
option.Some[AST](ast.call(str, "ofInt", {exp}))
else
if (ast.types.equals(ty, ast.types.float(), ctx))
if (floatPrecision == -1)
option.Some[AST](ast.call(str, "ofFloat", {exp}))
else
val format : String = "%." + String.ofInt(floatPrecision) + "f"
option.Some[AST](ast.call(str, "ofFormattedFloat", {ast.string(format), exp}))
else
if (ast.types.equals(ty, ast.types.boolean(), ctx))
option.Some[AST](ast.call(exp, "ifTrue", {ast.parseExpression("() => \"true\"", ctx), ast.parseExpression("() => \"false\"", ctx)}))
else
option.None[AST]()


def parseExpr(input : String, ctx : system.Context) : ParsedExpr
// if %{...}, we want to get the contents of the braces
val normal : option.Option[String] = regex("%\\{").findPrefixMatchOf(input).map[String]((m:regex.Match) => m.after())
if (normal.isDefined)
val eoparse : Int = getExprPos(normal.get(), 0, false, 0)
(eoparse == -1).ifTrue(
() => makeParsedExpr(option.None[AST](), normal.get()),
() => makeParsedExpr(convStringAST(normal.get().substring(0,eoparse), ctx, -1), normal.get().substring(eoparse+1, normal.get().length()))
)
else
// we check if %.x{...}, for float precision to x decimal places
val floatspec : option.Option[regex.Match] = regex("%\\.\\d+\\{").findPrefixMatchOf(input)
val formatspec : option.Option[String] = floatspec.map[String]((m:regex.Match) => m.matched())
if (floatspec.isDefined)
val precision : Int = Int.from(formatspec.get().substring(2, formatspec.get().length()-1))
val floatstring : String = floatspec.get().after()
val floatstringend : Int = getExprPos(floatstring, 0, false, 0)
(floatstringend == -1).ifTrue(
() => makeParsedExpr(option.None[AST](), floatstring),
() => makeParsedExpr(convStringAST(floatstring.substring(0, floatstringend), ctx, precision),
floatstring.substring(floatstringend+1, floatstring.length()))
)
else
// if nothing found then % should've been escaped, so throw error
makeParsedExpr(option.None[AST](), input)


//val prefixRegex : regex.Regex = regex("(\\\\\\\\|\\\\%|[^%])+")
val prefixRegex : regex.Regex = regex("[^\\\\%]+")
//val prefixRegex : regex.Regex = regex("[^%]+")

// recursively parses the input, returning option.None[AST] if parsing error
def parse(input : String, ctx : system.Context) : option.Option[AST]
val regxMatch : option.Option[regex.Match] = prefixRegex.findPrefixMatchOf(input)
val prefixString : option.Option[String] = regxMatch.map[String]((m:regex.Match) => m.matched())
val rest : option.Option[String] = regxMatch.map[String]((m:regex.Match) => m.after())
if (input == "")
option.Some[AST](ast.string(""))
else
if (prefixString.isDefined)
if (rest.get() == "")
option.Some[AST](ast.string(prefixString.get()))
else
val prefix : String = prefixString.get()
val suffix : String = rest.get()
if (suffix.length() >= 2)
// checking for escaped characters
if (suffix.charAt(0) == #"\\")
if (suffix.charAt(1) == #"%")
val recursiveCall : option.Option[AST] = parse(suffix.substring(2, suffix.length()), ctx)
if (recursiveCall.isDefined)
option.Some[AST](ast.call(ast.string(prefix + "%"), "+", {recursiveCall.get()}))
else
option.None[AST]()
else
if (suffix.charAt(1) == #"\\")
val recursiveCall : option.Option[AST] = parse(suffix.substring(2, suffix.length()), ctx)
if (recursiveCall.isDefined)
option.Some[AST](ast.call(ast.string(prefix + "\\"), "+", {recursiveCall.get()}))
else
option.None[AST]()
else
//would throw error but this could also be part of an escaped string sequence
val recursiveCall : option.Option[AST] = parse(suffix.substring(1, suffix.length()), ctx)
if (recursiveCall.isDefined)
option.Some[AST](ast.call(ast.string(prefix + "\\"), "+", {recursiveCall.get()}))
else
option.None[AST]()
else
val g : ParsedExpr = parseExpr(suffix, ctx)
if (g.expr().isDefined)
val recursiveCall : option.Option[AST] = parse(g.rest(), ctx)
if (recursiveCall.isDefined)
option.Some[AST](ast.call(ast.call(ast.string(prefixString.get()), "+", {g.expr().get()}), "+", {recursiveCall.get()}))
else
option.None[AST]() //recursive call gives None
else
option.None[AST]() //cannot parse expression
else
if (suffix == "")
option.Some[AST](ast.string(prefix))
else
val g : ParsedExpr = parseExpr(rest.getOrElse(() => ""), ctx)
if (g.expr().isDefined)
val recursiveCall = parse(g.rest(), ctx)
if (recursiveCall.isDefined)
option.Some[AST](ast.call(ast.call(ast.string(prefixString.get()), "+", {g.expr().get()}), "+", {recursiveCall.get()}))
else
option.None[AST]() //recursive call gives None
else
option.None[AST]() //cannot parse expression
else
val g : ParsedExpr = parseExpr(rest.getOrElse(() => input), ctx)
if (g.expr().isDefined)
val recursiveCall = parse(g.rest(), ctx)
if (recursiveCall.isDefined)
option.Some[AST](ast.call(g.expr().get(), "+", {recursiveCall.get()}))
else
option.None[AST]() //recursive call gives None
else
// can only reach here if first character starts with \
if (input.charAt(0) == #"\\")
if (input.length() >= 2)
if (input.charAt(1) == #"%")
val recursiveCall : option.Option[AST] = parse(input.substring(2,input.length()), ctx)
if (recursiveCall.isDefined)
option.Some[AST](ast.call(ast.string("%"), "+", {recursiveCall.get()}))
else
option.None[AST]()
else
if (input.charAt(1) == #"\\")
val recursiveCall : option.Option[AST] = parse(input.substring(2,input.length()), ctx)
if (recursiveCall.isDefined)
option.Some[AST](ast.call(ast.string("\\"), "+", {recursiveCall.get()}))
else
option.None[AST]()
else
val recursiveCall : option.Option[AST] = parse(input.substring(1,input.length()), ctx)
if (recursiveCall.isDefined)
option.Some[AST](ast.call(ast.string("\\"), "+", {recursiveCall.get()}))
else
option.None[AST]()
else
option.Some[AST](ast.string(input))
else
option.None[AST]() //cannot parse expression


type FormatString = String
metadata new
def parseTSL(input : String, ctx : system.Context) : option.Option[AST]
//TODO: only strip leading whitespace for ~ referencing, not {} TSL syntax
parse(ast.stripLeadingWhitespace(input, false), ctx)



21 changes: 21 additions & 0 deletions examples/tsls/formatstringClient.wyv
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require stdout

import metadata tsls.formatstring
import wyvern.String

def printf(str:formatstring.FormatString):Unit
stdout.print(str)
stdout.println()

//just a plain string
printf(~)
Hello \\\%World!\n

printf(~)
Hello %{"string!"}%{" \"}{mm}"} and %{ "another string!" }

val x = 3
printf(~)
%{x} %{4} %.5{1.6} %{1.7}

printf({%{1 == 2}%{true}})
4 changes: 2 additions & 2 deletions stdlib/platform/java/stdin.wyv
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ effect Read = {fileEffects.Read}

val sc = stdio.initStdin()

def read() : {this.Read} Int
def read() : {Read} Int
stdio.read(sc)

def readLine() : {this.Read} Option[String]
def readLine() : {Read} Option[String]
val line = stdio.readLine(sc)
if (stdio.isNull(line))
option.None[String]()
Expand Down
3 changes: 3 additions & 0 deletions stdlib/wyvern/ast.wyv
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,6 @@ def formalArg(name : String, argType : Type) : FormalArg = ast.formalArg(name, a
def stripLeadingWhitespace(input : String, mustStrip : Boolean) : String = ast.stripLeadingWhitespace(input, mustStrip)

def genIdent() : String = ast.genIdent()

def getType(e : AST, ctx : system.Context) : Type
ast.getType(e, ctx)
30 changes: 21 additions & 9 deletions stdlib/wyvern/internal/ast.wyv
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import wyvern.internal.list
import java:wyvern.stdlib.support.AST.utils

type AST
val ast:Unit
val ast:Unit

type Decl
val decl:Unit
val decl:Unit

type Type
val typ:Unit
val typ:Unit

type DeclType
val declType:Unit
Expand All @@ -37,10 +37,10 @@ def bind(bindings : list.List, inExpr : AST) : AST = new
val ast:Unit = utils.bind(bindings, inExpr)

def object(decls : list.List) : AST = new
val ast:Unit = utils.object(decls)
val ast:Unit = utils.object(decls)

def defDeclaration(name : String, formalArgs : list.List[FormalArg], returnType : Type, body : AST):Decl = new
val decl:Unit = utils.defDeclaration(name, formalArgs, returnType, body)
val decl:Unit = utils.defDeclaration(name, formalArgs, returnType, body)

def forwardDeclaration(forwardType : Type, fieldName : String) : Decl = new
val decl:Unit = utils.forwardDeclaration(forwardType, fieldName)
Expand All @@ -58,19 +58,19 @@ def varDeclaration(fieldName : String, fieldType : Type, value : AST) : Decl = n
val decl:Unit = utils.varDeclaration(fieldName, fieldType, value)

def int(i:Int):AST = new
val ast:Unit = utils.intLiteral(i)
val ast:Unit = utils.intLiteral(i)

def boolean(b:Boolean):AST = new
val ast:Unit = utils.booleanLiteral(b)

def string(s:String):AST = new
val ast:Unit = utils.stringLiteral(s)
val ast:Unit = utils.stringLiteral(s)

def variable(s:String):AST = new
val ast:Unit = utils.variable(s)
val ast:Unit = utils.variable(s)

def call(receiver:AST, methodName:String, arguments:list.List):AST = new
val ast:Unit = utils.methodCall(receiver,methodName, arguments)
val ast:Unit = utils.methodCall(receiver,methodName, arguments)

def cast(toCastExpr : AST, exprType : Type) : AST = new
val ast:Unit = utils.cast(toCastExpr, exprType)
Expand Down Expand Up @@ -140,6 +140,9 @@ val types = new

def string():Type = new
val typ:Unit = utils.stringType()

def float():Type = new
val typ:Unit = utils.floatType()

def nominal(pathVariable : String, typeMember : String):Type = new
val typ:Unit = utils.nominalType(pathVariable, typeMember)
Expand All @@ -149,10 +152,19 @@ val types = new

def refinement(typeParams : list.List[Type], base : Type):Type = new
val typ:Unit = utils.refinementType(typeParams, base)

def equals(t1 : Type, t2 : Type, ctx : system.Context):Boolean
val ctxDyn:Dyn = ctx
utils.checkTypeEquality(t1, t2, ctxDyn)

def stripLeadingWhitespace(input : String, mustStrip : Boolean) : String = utils.stripLeadingWhitespace(input, mustStrip)

def genIdent() : String = utils.genIdent()

def let(ident : String, bindingType : Type, bindingValue : AST, inExpr : AST) : AST = new
val ast:Unit = utils.let(ident, bindingType, bindingValue, inExpr)

def getType(e : AST, ctx : system.Context) : Type
val ctxDyn:Dyn = ctx
new
val typ:Unit = utils.getObjectType(e, ctxDyn)
Loading

0 comments on commit 8b92a6d

Please sign in to comment.