diff --git a/README.md b/README.md index 1edda45..1266b7d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# STWL: Strongly Typed Web Language +# Solace +A language to find solace after using JavaScript Proposal: A strongly typed, programming language that compiles to JavaScript, with the following features: * Error unions: `Error!string` @@ -15,9 +16,6 @@ Syntax example (very early proposal, might change): import { func1, func2, func3 as funFunc } from './MyCoolModule' import Cool from './MyCoolModule' - // although I might add something like this in the future: - import './MyCoolModule' as Cool // because it just reads so much nicer - // Declarations can be constant, variable or live (a.k.a. reactive): // Constants can never change @@ -64,5 +62,61 @@ Syntax example (very early proposal, might change): _ -> 'outside town, hopefully' } } + + // Values can be optional + var userName: ?string = None // we don't know the name, yet + + // Errors can be involved as well + var userName: !string = Error('user not found') + if (userFound) { + userName = 'Hans' + } + + /* Optional values and Error Unions explained: + * + * ?string would be string | null in Typescript, + * !string would be string | Error, + * !?string would be (string | null) | Error + * Error unions can be specific: NetworkError!string + * Optional values can either be a value or None (there is no null in Solace) + */ + + // undefined + var userName: string // undefined, because it is not initialized + userName = undefined // Error! You cannot set anything to undefined + + // In Solace, a value can either be explicitely optional and set to None, + // or it can be undefined, because there was no assignment, yet. + // This makes the distinction between undefined and null very clear: + if (userName is undefined) // not initialized at all + if (userName is None) // explicitely set to None + + // Guards: certain places can make use of guards to tighten their matches: + match userName when Some(name) { + name => console.log('User is named', name) + } else { + console.log('User has no name?!') + } + + // defer allows you to run a code block after a function finished + async fn deleteUser(userId: string) { + const user: ?User = await fetch(`/api/users/${userId}`) + + // lets not forget to send the user a mail + defer when user is Some(_) { + sendEmail(userId, `Greetings ${user.name}, you have been deleted!`) + } + + await deleteUser(userId) + } + + + // functions look like this + fn doMath(): ?number { + if (!computer.ready) return None + return 42 + } + + // ``` diff --git a/package.json b/package.json index 83b1d54..dfd547c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "ohm-grammar-stwl", + "name": "ohm-grammar-solace", "version": "0.0.1", - "description": "Strongly Typed Web Language", + "description": "A language to find solace after using JavaScript", "license": "MIT", "keywords": [ "ohm", @@ -9,12 +9,12 @@ "javascript", "typed", "peg", - "stwl" + "solace" ], "author": "koehr ", - "homepage": "https://git.koehr.ing/n/ohm-grammar-stwl", + "homepage": "https://git.koehr.ing/n/ohm-grammar-solace", "bugs": { - "url": "https://git.koehr.ing/n/ohm-grammar-stwl/issues" + "url": "https://git.koehr.ing/n/ohm-grammar-solace/issues" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/stwl.ohm b/stwl.ohm index 955612c..b544c49 100644 --- a/stwl.ohm +++ b/stwl.ohm @@ -1,85 +1,12 @@ -StronglyTypedWebLanguage { - Program = Statement* - - Statement = ConstDec - | VarDec - | LiveDec - | ComputedDec - | TrackStmt - | ExprStmt - | Block - - ConstDec = "const" identifier "=" Expr - VarDec = "var" identifier "=" Expr - LiveDec = "live" identifier "=" Expr - ComputedDec = "computed" identifier Block - - TrackStmt = "track" ListOf LambdaParams? Block - ExprStmt = Expr // necessary? - - Block = "{" Statement* "}" - LambdaParams = "|" ListOf "|" - - ParExpr = "(" Expr ")" - Expr = AssignExpr - AssignExpr = CompareExpr ("=" CompareExpr)? - - CompareExpr = AddExpr (CompareOp AddExpr)? - CompareOp = "==" | "!=" | "<=" | ">=" | "<" | ">" - - AddExpr = MulExpr (("+"|"-") MulExpr)* - MulExpr = UnaryExpr (("*"|"/"|"%") UnaryExpr)* - - UnaryExpr = "void" UnaryExpr -- voidExp - | "++" UnaryExpr -- preIncrement - | "--" UnaryExpr -- preDecrement - | "+" UnaryExpr -- unaryPlus - | "-" UnaryExpr -- unaryMinus - | "~" UnaryExpr -- bnot - | "!" UnaryExpr -- lnot - | PostfixExpr - | FunctionExpr - - PostfixExpr = CallExpr "++" -- postIncrement - | CallExpr "--" -- postDecrement - | CallExpr - - CallExpr = MemberExpr ("(" ListOf ")")? - MemberExpr = Literal ("." identifier)* - - FunctionExpr = "fn" identifier "(" ListOf* ")" Block - - Literal = numberLiteral - | stringLiteral - | booleanLiteral - | nullLiteral - | identifier - | ParExpr - | Block - - // 123 or 12.34 - numberLiteral = digit+ ("." digit+)? - - stringLiteral = "\"" (~"\"" any)* "\"" - | "'" (~"'" any)* "'" - | "`" (~"`" any)* "`" - - identifier = ~reservedWord identifierStart (letter | digit | "$" | "_")* - identifierStart = letter | "$" | "_" - reservedWord = keyword | futureReservedWord | nullLiteral | booleanLiteral - - nullLiteral = "null" - booleanLiteral = "true" | "false" - - keyword = "const" | "var" | "live" | "computed" | "track" | "when" | "while" - | "if" | "else" | "for" | "unreachable" | "catch" | "async" | "await" - | "interface" | "struct" | "private" | "public" | "defer" | "fn" - | "break" | "void" | "return" | "import" | "export" | "switch" - | "continue" - - futureReservedWord = "new" | "class" | "enum" | "extends" | "super" - | "implements" | "yield" +STWL { + Program = &(Directive*) SourceElement* + /* + STWL - Strongly Typed Web Language is just a working title + This grammar is based on the ES5 and ES6 grammars and extends/cuts it where + necessary to clear STWL from many, in my opinion unnecessary, features + while extending it with a clearer syntax + */ sourceCharacter = any @@ -94,8 +21,6 @@ StronglyTypedWebLanguage { | "\uFEFF" -- byteOrderMark | unicodeSpaceSeparator - unicodeSpaceSeparator = "\u2000".."\u200B" | "\u3000" - lineTerminator = "\n" | "\r" | "\u2028" | "\u2029" lineTerminatorSequence = "\n" | "\r" ~"\n" | "\u2028" | "\u2029" | "\r\n" @@ -103,4 +28,397 @@ StronglyTypedWebLanguage { multiLineComment = "/*" (~"*/" sourceCharacter)* "*/" singleLineComment = "//" (~lineTerminator sourceCharacter)* + + identifier (an identifier) = ~reservedWord identifierName + identifierName = identifierStart identifierPart* + + identifierStart = letter | "$" | "_" + identifierPart = identifierStart | unicodeDigit + letter += unicodeCategoryNl + unicodeCategoryNl + = "\u2160".."\u2182" | "\u3007" | "\u3021".."\u3029" + unicodeDigit (a digit) + = "\u0030".."\u0039" | "\u0660".."\u0669" | "\u06F0".."\u06F9" | "\u0966".."\u096F" | "\u09E6".."\u09EF" | "\u0A66".."\u0A6F" | "\u0AE6".."\u0AEF" | "\u0B66".."\u0B6F" | "\u0BE7".."\u0BEF" | "\u0C66".."\u0C6F" | "\u0CE6".."\u0CEF" | "\u0D66".."\u0D6F" | "\u0E50".."\u0E59" | "\u0ED0".."\u0ED9" | "\u0F20".."\u0F29" | "\uFF10".."\uFF19" + + unicodeSpaceSeparator = "\u2000".."\u200B" | "\u3000" + + reservedWord = keyword | futureReservedWord | nullLiteral | booleanLiteral + + // Note: keywords that are the complete prefix of another keyword should + // be prioritized (e.g. 'in' should come before 'instanceof') + keyword = for | while | do | break | continue | match + | if | else | const | var | live | computed + | catch | fn | return | void | this + | has | enum | interface | extends | implements | struct + | export | import | defer + + futureReservedWord = fallthrough + + literal = nullLiteral | booleanLiteral | numericLiteral + | stringLiteral | regularExpressionLiteral + + // Optionals, which can be either Some or None, are translated to T | null + nullLiteral = "None" ~identifierPart + booleanLiteral = ("true" | "false") ~identifierPart + + // For semantics on how decimal literals are constructed, see section 7.8.3 + + // Note that the ordering of hexIntegerLiteral and decimalLiteral is reversed w.r.t. the spec + // This is intentional: the order decimalLiteral | hexIntegerLiteral will parse + // "0x..." as a decimal literal "0" followed by "x..." + numericLiteral = octalIntegerLiteral | hexIntegerLiteral | decimalLiteral + + decimalLiteral = decimalIntegerLiteral "." decimalDigit* exponentPart -- bothParts + | "." decimalDigit+ exponentPart -- decimalsOnly + | decimalIntegerLiteral exponentPart -- integerOnly + + decimalIntegerLiteral = nonZeroDigit decimalDigit* -- nonZero + | "0" -- zero + decimalDigit = "0".."9" + nonZeroDigit = "1".."9" + + exponentPart = exponentIndicator signedInteger -- present + | -- absent + exponentIndicator = "e" | "E" + signedInteger = "+" decimalDigit* -- positive + | "-" decimalDigit* -- negative + | decimalDigit+ -- noSign + + hexIntegerLiteral = "0x" hexDigit+ + | "0X" hexDigit+ + + // hexDigit defined in Ohm's built-in rules (otherwise: hexDigit = "0".."9" | "a".."f" | "A".."F") + + octalIntegerLiteral = "0" octalDigit+ + + octalDigit = "0".."7" + zeroToThree = "0".."3" + fourToSeven = "4".."7" + + // For semantics on how string literals are constructed, see section 7.8.4 + stringLiteral = "\"" doubleStringCharacter* "\"" + | "'" singleStringCharacter* "'" + doubleStringCharacter = ~("\"" | "\\" | lineTerminator) sourceCharacter -- nonEscaped + | "\\" escapeSequence -- escaped + | lineContinuation -- lineContinuation + singleStringCharacter = ~("'" | "\\" | lineTerminator) sourceCharacter -- nonEscaped + | "\\" escapeSequence -- escaped + | lineContinuation -- lineContinuation + lineContinuation = "\\" lineTerminatorSequence + escapeSequence = unicodeEscapeSequence + | hexEscapeSequence + | octalEscapeSequence + | characterEscapeSequence // Must come last. + characterEscapeSequence = singleEscapeCharacter + | nonEscapeCharacter + singleEscapeCharacter = "'" | "\"" | "\\" | "b" | "f" | "n" | "r" | "t" | "v" + nonEscapeCharacter = ~(escapeCharacter | lineTerminator) sourceCharacter + escapeCharacter = singleEscapeCharacter | decimalDigit | "x" | "u" + octalEscapeSequence = zeroToThree octalDigit octalDigit -- whole + | fourToSeven octalDigit -- eightTimesfourToSeven + | zeroToThree octalDigit ~decimalDigit -- eightTimesZeroToThree + | octalDigit ~decimalDigit -- octal + hexEscapeSequence = "x" hexDigit hexDigit + unicodeEscapeSequence = "u" hexDigit hexDigit hexDigit hexDigit + + // §7.8.5 Regular Expression Literals -- https://es5.github.io/#x7.8.5 + + regularExpressionLiteral = "/" regularExpressionBody "/" regularExpressionFlags + regularExpressionBody = regularExpressionFirstChar regularExpressionChar* + regularExpressionFirstChar = ~("*" | "\\" | "/" | "[") regularExpressionNonTerminator + | regularExpressionBackslashSequence + | regularExpressionClass + regularExpressionChar = ~("\\" | "/" | "[") regularExpressionNonTerminator + | regularExpressionBackslashSequence + | regularExpressionClass + regularExpressionBackslashSequence = "\\" regularExpressionNonTerminator + regularExpressionNonTerminator = ~(lineTerminator) sourceCharacter + regularExpressionClass = "[" regularExpressionClassChar* "]" + regularExpressionClassChar = ~("]" | "\\") regularExpressionNonTerminator + | regularExpressionBackslashSequence + regularExpressionFlags = identifierPart* + + // === Implementation-level rules (not part of the spec) === + + multiLineCommentNoNL = "/*" (~("*/" | lineTerminator) sourceCharacter)* "*/" + + // does not accept lineTerminators, not even implicit ones in a multiLineComment (cf. section 7.4) + spacesNoNL = (whitespace | singleLineComment | multiLineCommentNoNL)* + + // A semicolon is "automatically inserted" if a newline or the end of the input stream is + // reached, or the offending token is "}". + // See https://es5.github.io/#x7.9 for more information. + // NOTE: Applications of this rule *must* appear in a lexical context -- either in the body of a + // lexical rule, or inside `#()`. + sc = space* (";" | end) + | spacesNoNL (lineTerminator | ~multiLineCommentNoNL multiLineComment | &"}") + + // Convenience rules for parsing keyword tokens. + for = "for" ~identifierPart + while = "while" ~identifierPart + do = "do" ~identifierPart // will we support do-while loops? + break = "break" ~identifierPart + continue = "continue" ~identifierPart + if = "if" ~identifierPart + else = "else" ~identifierPart + const = "const" ~identifierPart + var = "var" ~identifierPart + live = "live" ~identifierPart + track = "track" ~identifierPart + computed = "computed" ~identifierPart + catch = "catch" ~identifierPart + fn = "fn" ~identifierPart + return = "return" ~identifierPart + void = "void" ~identifierPart + this = "_" ~identifierPart + in = "in" ~identifierPart -- value in ArrayLike + has = "has" ~identifierPart -- struct has Property + enum = "enum" ~identifierPart + interface = "interface" ~identifierPart + extends = "extends" ~identifierPart + implements = "implements" ~identifierPart + export = "export" ~identifierPart + import = "import" ~identifierPart + struct = "struct" ~identifierPart + defer = "defer" ~identifierPart + match = "match" ~identifierPart + fallthrough = "fallthrough" ~identifierPart + + // end of lexical rules + + noIn = ~in + withIn = + + noHas = ~has + withHas = + + noInHas = ~(in | has) + withInHas = + + // §A.3 Expressions -- https://es5.github.io/#A.3 + + PrimaryExpression = this + | identifier + | literal + // ( litToken.type === "regexp" + // ? this.ast(_fromIdx, "RegExpExpr",{body: litToken.value.body + // flags: litToken.value.flags}, []) + // : this.ast(_fromIdx, "LiteralExpr",{type: litToken.type + // value: litToken.value}, []) ) + | ArrayLiteral + | ObjectLiteral + | "(" Expression ")" -- parenExpr + + ArrayLiteral = "[" ListOf "]" + AssignmentExpressionOrElision = AssignmentExpression + | -- elision + + ObjectLiteral = "{" ListOf "}" -- noTrailingComma + | "{" NonemptyListOf "," "}" -- trailingComma + + PropertyAssignment = get PropertyName "(" ")" "{" FunctionBody "}" -- getter + | set PropertyName "(" FormalParameter ")" "{" FunctionBody "}" -- setter + | PropertyName ":" AssignmentExpression -- simple + + PropertyName = identifierName + | stringLiteral + | numericLiteral + + MemberExpression = MemberExpression "[" Expression "]" -- arrayRefExp + | MemberExpression "." identifierName -- propRefExp + | new MemberExpression Arguments -- newExp + | FunctionExpression + | PrimaryExpression + + NewExpression = MemberExpression + | new NewExpression -- newExp + + CallExpression = CallExpression "[" Expression "]" -- arrayRefExp + | CallExpression "." identifierName -- propRefExp + | CallExpression Arguments -- callExpExp + | MemberExpression Arguments -- memberExpExp + + Arguments = "(" ListOf, ","> ")" + + LeftHandSideExpression = CallExpression + | NewExpression + + PostfixExpression = LeftHandSideExpression #(spacesNoNL "++") -- postIncrement + | LeftHandSideExpression #(spacesNoNL "--") -- postDecrement + | LeftHandSideExpression + + UnaryExpression = void UnaryExpression -- voidExp + | "++" UnaryExpression -- preIncrement + | "--" UnaryExpression -- preDecrement + | "+" UnaryExpression -- unaryPlus + | "-" UnaryExpression -- unaryMinus + | "~" UnaryExpression -- bnot + | "!" UnaryExpression -- lnot + | PostfixExpression + + MultiplicativeExpression = MultiplicativeExpression "*" UnaryExpression -- mul + | MultiplicativeExpression "/" UnaryExpression -- div + | MultiplicativeExpression "%" UnaryExpression -- mod + | UnaryExpression + + AdditiveExpression = AdditiveExpression "+" MultiplicativeExpression -- add + | AdditiveExpression "-" MultiplicativeExpression -- sub + | MultiplicativeExpression + + ShiftExpression = ShiftExpression "<<" AdditiveExpression -- lsl + | ShiftExpression ">>>" AdditiveExpression -- lsr + | ShiftExpression ">>" AdditiveExpression -- asr + | AdditiveExpression + + RelationalExpression + = RelationalExpression "<" ShiftExpression -- lt + | RelationalExpression ">" ShiftExpression -- gt + | RelationalExpression "<=" ShiftExpression -- le + | RelationalExpression ">=" ShiftExpression -- ge + | RelationalExpression guardIn "in" ShiftExpression -- inExp + | ShiftExpression + + EqualityExpression + = EqualityExpression "==" RelationalExpression -- equal + | EqualityExpression "!=" RelationalExpression -- notEqual + | EqualityExpression "===" RelationalExpression -- eq + | EqualityExpression "!==" RelationalExpression -- notEq + | RelationalExpression + + BitwiseANDExpression + = BitwiseANDExpression "&" EqualityExpression -- band + | EqualityExpression + + BitwiseXORExpression + = BitwiseXORExpression "^" BitwiseANDExpression -- bxor + | BitwiseANDExpression + + BitwiseORExpression + = BitwiseORExpression "|" BitwiseXORExpression -- bor + | BitwiseXORExpression + + LogicalANDExpression + = LogicalANDExpression "&&" BitwiseORExpression -- land + | BitwiseORExpression + + LogicalORExpression + = LogicalORExpression "||" LogicalANDExpression -- lor + | LogicalANDExpression + + ConditionalExpression + = LogicalORExpression "?" AssignmentExpression ":" AssignmentExpression -- conditional + | LogicalORExpression + + AssignmentExpression + = LeftHandSideExpression assignmentOperator AssignmentExpression -- assignment + | ConditionalExpression + + Expression (an expression) + = Expression "," AssignmentExpression -- commaExp + | AssignmentExpression + + assignmentOperator = "=" | ">>>=" | "<<=" | ">>=" + | "*=" | "/=" | "%=" | "+=" | "-=" | "&=" | "^=" | "|=" + + // Statements -- (extends https://es5.github.io/#A.4) + + Statement + = Block + | VariableStatement + | EmptyStatement + | ExpressionStatement + | IfStatement + | IterationStatement + | ContinueStatement + | BreakStatement + | ReturnStatement + + Block = "{" StatementList "}" + LambdaParameters = "|" ListOf "|" + + StatementList = Statement* + + ComputedStatement = computed identifier Block + + TrackStatement = track ListOf LambdaParameters? Block + + VariableStatement = VariableAssignment VariableDeclarationList #sc + + VariableAssignment = var | const | live + + VariableDeclarationList = NonemptyListOf, ","> + + VariableDeclaration = identifier Initialiser? + + Initialiser = "=" AssignmentExpression + + EmptyStatement = ";" // note: this semicolon eats newlines + + ExpressionStatement = ~("{" | function) Expression #sc + + IfStatement = if "(" Expression ")" Statement (else Statement)? + + IterationStatement = do Statement while "(" Expression ")" #sc -- doWhile + | while "(" Expression ")" Statement -- whileDo + | for "(" Expression? ";" + Expression? ";" + Expression? ")" Statement -- for3 + | for "(" var VariableDeclarationList ";" + Expression? ";" + Expression? ")" Statement -- for3var + | for "(" LeftHandSideExpression in + Expression ")" Statement -- forIn + | for "(" var VariableDeclaration in + Expression ")" Statement -- forInVar + + ContinueStatement = continue #((spacesNoNL identifier)? sc) + + BreakStatement = break #((spacesNoNL identifier)? sc) + + ReturnStatement = return (#(spacesNoNL ~space) Expression)? #sc + + Catch = catch "(" FormalParameter ")" Block + + Defer = defer (when Expression)? Block + + // Pattern Matching + + MatchExpr = match Expression (if Pattern)? "{" MatchArm+ "}" (else Block)? + MatchArm = Pattern "=>" (Expression | Block) + Pattern = identifier + | literal + | GuardPattern + GuardPattern = Expression // Just an expression! No special syntax needed + + + // §A.5 Functions and Programs -- https://es5.github.io/#A.5 + + FunctionDeclaration + = function identifier "(" FormalParameterList ")" "{" FunctionBody "}" + + FunctionExpression + = function identifier "(" FormalParameterList ")" "{" FunctionBody "}" -- named + | function "(" FormalParameterList ")" "{" FunctionBody "}" -- anonymous + + FormalParameterList = ListOf + + FormalParameter = identifier + + /* + Note: The Directive Prologue is the longest sequence of ExpressionStatement + productions occurring as the initial SourceElement (see https://es5.github.io/#x14.1) + */ + FunctionBody = &(Directive*) SourceElement* + + SourceElement = Declaration | Statement + + // Broken out so es6 can override to include ConstDecl and LetDecl + Declaration = FunctionDeclaration + + Directive = stringLiteral #sc } + +ES5Lax <: ES5 { + futureReservedWord := futureReservedWordLax +} +