diff --git a/effekt/shared/src/main/scala/effekt/Lexer.scala b/effekt/shared/src/main/scala/effekt/Lexer.scala index 93d38130d..8bf0ff9f4 100644 --- a/effekt/shared/src/main/scala/effekt/Lexer.scala +++ b/effekt/shared/src/main/scala/effekt/Lexer.scala @@ -111,6 +111,23 @@ enum TokenKind { case `-` case `*` + case `>>` + case `<<` + case `~` + case `--` + case `~>` + case `<~` + case `<|` + case `|>` + case `+=` + case `-=` + case `*=` + case `/=` + case `..` + case `...` + case `^^` + case `^` + // keywords case `let` case `true` @@ -398,11 +415,17 @@ class Lexer(source: Source) extends Iterator[Token] { // Comments case ('/', '*') => advance2With(multilineComment()) case ('/', '/') => advance2With(singlelineComment()) + case ('/', '=') => advance2With(TokenKind.`/=`) case ('/', _) => advanceWith(TokenKind.`/`) // Shebang case ('#', '!') => advance2With(shebang()) + + case ('.', '.') if peekAhead(2) == '.' => advance3With(TokenKind.`...`) + case ('.', '.') => advance2With(TokenKind.`..`) + case ('.', _) => advanceWith(TokenKind.`.`) + // Two-character operators case ('=', '=') => advance2With(TokenKind.`===`) case ('=', '>') => advance2With(TokenKind.`=>`) @@ -411,11 +434,21 @@ class Lexer(source: Source) extends Iterator[Token] { case ('!', '=') => advance2With(TokenKind.`!==`) case ('!', _) => advanceWith(TokenKind.`!`) + case ('^', '^') => advance2With(TokenKind.`^^`) + case ('^', _) => advanceWith(TokenKind.`^`) + + case ('~', '>') => advance2With(TokenKind.`~>`) + case ('~', _) => advanceWith(TokenKind.`~`) + + case ('<', '<') => advance2With(TokenKind.`<<`) case ('<', '=') => advance2With(TokenKind.`<=`) case ('<', '>') => advance2With(TokenKind.`<>`) case ('<', '{') => advance2With(TokenKind.`<{`) + case ('<', '~') => advance2With(TokenKind.`<~`) + case ('<', '|') => advance2With(TokenKind.`<|`) case ('<', _) => advanceWith(TokenKind.`<`) + case ('>', '>') => advance2With(TokenKind.`>>`) case ('>', '=') => advance2With(TokenKind.`>=`) case ('>', _) => advanceWith(TokenKind.`>`) @@ -423,17 +456,23 @@ class Lexer(source: Source) extends Iterator[Token] { case (':', _) => advanceWith(TokenKind.`:`) case ('|', '|') => advance2With(TokenKind.`||`) + case ('|', '>') => advance2With(TokenKind.`|>`) case ('|', _) => advanceWith(TokenKind.`|`) case ('&', '&') => advance2With(TokenKind.`&&`) case ('&', _) => advanceWith(TokenKind.`&`) case ('+', '+') => advance2With(TokenKind.`++`) + case ('+', '=') => advance2With(TokenKind.`+=`) case ('+', _) => advanceWith(TokenKind.`+`) case ('-', c) if c.isDigit => advanceWith(number(negative = true)) + case ('-', '=') => advance2With(TokenKind.`-=`) case ('-', _) => advanceWith(TokenKind.`-`) + case ('*', '=') => advanceWith(TokenKind.`*=`) + case ('*', _) => advanceWith(TokenKind.`*`) + case ('$', '{') => interpolationDepths.push(depthTracker.braces + 1) depthTracker.braces += 1 @@ -470,8 +509,6 @@ class Lexer(source: Source) extends Iterator[Token] { depthTracker.brackets -= 1 advanceWith(TokenKind.`]`) case (',', _) => advanceWith(TokenKind.`,`) - case ('.', _) => advanceWith(TokenKind.`.`) - case ('*', _) => advanceWith(TokenKind.`*`) case ('\u0000', _) => // EOF reached - provide context about unclosed constructs diff --git a/effekt/shared/src/main/scala/effekt/Parser.scala b/effekt/shared/src/main/scala/effekt/Parser.scala index 38357f7aa..49a3642c9 100644 --- a/effekt/shared/src/main/scala/effekt/Parser.scala +++ b/effekt/shared/src/main/scala/effekt/Parser.scala @@ -975,10 +975,13 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) { } def orExpr(): Term = infix(andExpr, `||`) - def andExpr(): Term = infix(eqExpr, `&&`) - def eqExpr(): Term = infix(relExpr, `===`, `!==`) - def relExpr(): Term = infix(addExpr, `<=`, `>=`, `<`, `>`) - def addExpr(): Term = infix(mulExpr, `++`, `+`, `-`) + def andExpr(): Term = infix(pipeExpr, `&&`) + def pipeExpr(): Term = infix(bitOrExpr, `<|`, `|>`) + def bitOrExpr(): Term = infix(bitAndExpr, TokenKind.`|`) + def bitAndExpr(): Term = infix(eqExpr, `&`) + def eqExpr(): Term = infix(rangeExpr, `===`, `!==`, `<=`, `>=`, `<`, `>`) + def rangeExpr(): Term = infix(addExpr, `..`, `...`, TokenKind.`~`, TokenKind.`~>`, TokenKind.`<~`) + def addExpr(): Term = infix(mulExpr, `++`, `--`, `+`, `-`, `<<`, `>>`, `^`, `^^`) def mulExpr(): Term = infix(callExpr, `*`, `/`) inline def infix(nonTerminal: () => Term, ops: TokenKind*): Term = @@ -1022,15 +1025,36 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) { case `&&` => "infixAnd" case `===` => "infixEq" case `!==` => "infixNeq" - case `<` => "infixLt" - case `>` => "infixGt" + case `<` => "infixLt" + case `>` => "infixGt" case `<=` => "infixLte" case `>=` => "infixGte" - case `+` => "infixAdd" - case `-` => "infixSub" - case `*` => "infixMul" - case `/` => "infixDiv" + case `+` => "infixAdd" + case `+=` => "infixAdd" + case `-` => "infixSub" + case `-=` => "infixSub" + case `*` => "infixMul" + case `*=` => "infixMul" + case `/` => "infixDiv" + case `/=` => "infixDiv" case `++` => "infixConcat" + case `>>` => "infixShr" + case `<<` => "infixShl" + case `--` => "infixRemove" + + case TokenKind.`~` => "infixTilde" + case TokenKind.`~>` => "infixTildeRight" + case TokenKind.`<~` => "infixTildeLeft" + case TokenKind.`|` => "infixPipe" + case TokenKind.`&` => "infixAmp" + + case `<|` => "infixPipeLeft" + case `|>` => "infixPipeRight" + case `..` => "infixDotDot" + case `...` => "infixDotDotDot" + case `^^` => "infixHatHat" + case `^` => "infixHat" + case _ => sys.error(s"Internal compiler error: not a valid operator ${op}") } @@ -1052,18 +1076,32 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) { while (peek(`.`) || isArguments) peek.kind match { - // member selection (or method call) + // member selection, postfix acess, or method call // . // | .( ... ) case `.` => + val dot = peek consume(`.`) - val member = idRef() - // method call - if (isArguments) { - val (targs, vargs, bargs) = arguments() - e = Term.MethodCall(e, member, targs, vargs, bargs, span()) - } else { - e = Term.MethodCall(e, member, Nil, Nil, Nil, span()) + peek.kind match { + // .[, ...] + case `[` => + val bracket = peek + val arguments = some(expr, `[`, `,`, `]`) + e = MethodCall(e, + IdRef(Nil, "postfixAccess", Span(source, dot.start, bracket.end, Synthesized)), + Nil, arguments.unspan.map(a => ValueArg.Unnamed(a)), Nil, + Span(source, e.span.from, arguments.span.to, Synthesized) + ) + + case _ => + val member = idRef() + // method call + if (isArguments) { + val (targs, vargs, bargs) = arguments() + e = Term.MethodCall(e, member, targs, vargs, bargs, span()) + } else { + e = Term.MethodCall(e, member, Nil, Nil, Nil, span()) + } } // function call @@ -1168,7 +1206,20 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) { case _ if isVariable => peek(1).kind match { case _: Str => templateString() - case _ => variable() + case _ => + val lhs = variable() + peek.kind match { + case `+=` | `-=` | `*=` | `/=` => + val op = next() + val operand = expr() + val rhs = Call( + IdTarget(IdRef(Nil, opName(op.kind), op.span(source).synthesized)), + Nil, List(ValueArg.Unnamed(lhs), ValueArg.Unnamed(operand)), Nil, + Span(source, lhs.span.from, operand.span.to, Synthesized) + ) + Assign(lhs.id, rhs, span()) + case _ => lhs + } } case _ if isHole => hole() case _ if isTupleOrGroup => tupleOrGroup() @@ -1295,7 +1346,7 @@ class Parser(positions: Positions, tokens: Seq[Token], source: Source) { private def isUnitLiteral: Boolean = peek(`(`) && peek(1, `)`) def isVariable: Boolean = isIdRef - def variable(): Term = + def variable(): Term.Var = nonterminal: Var(idRef(), span()) diff --git a/examples/pos/array_operators.check b/examples/pos/array_operators.check new file mode 100644 index 000000000..1191247b6 --- /dev/null +++ b/examples/pos/array_operators.check @@ -0,0 +1,2 @@ +1 +2 diff --git a/examples/pos/array_operators.effekt b/examples/pos/array_operators.effekt new file mode 100644 index 000000000..29849be0d --- /dev/null +++ b/examples/pos/array_operators.effekt @@ -0,0 +1,29 @@ + +def postfixAccess[T](arr: Array[T], index: Int): T = unsafeGet(arr, index) + +// access as a function +def example1() = { + val arr = array[Int](10, 1) + println(arr.[2]) +} + +// access on a capability / object +interface MyRef[T] { + def postfixAccess(index: Int): T +} + +def example2() = { + def arr = new MyRef[Int] { + def postfixAccess(index) = 1 + }; + println(arr.[0] + arr.[1]) +} + +// multi dimensional access +def example3(arr: Array[Array[Int]]) = + println(arr.[0].[1] + arr.[1].[0]) + +def main() = { + example1() + example2() +} diff --git a/examples/pos/assign_operators.check b/examples/pos/assign_operators.check new file mode 100644 index 000000000..f00580c40 --- /dev/null +++ b/examples/pos/assign_operators.check @@ -0,0 +1,2 @@ +3 +1 diff --git a/examples/pos/assign_operators.effekt b/examples/pos/assign_operators.effekt new file mode 100644 index 000000000..627580015 --- /dev/null +++ b/examples/pos/assign_operators.effekt @@ -0,0 +1,9 @@ +def main() = { + var x = 1 + var y = 2 + + x += y + println(x) // 3 + y -= 1 + println(y) // 1 +} \ No newline at end of file diff --git a/examples/pos/operators.check b/examples/pos/operators.check new file mode 100644 index 000000000..f0b8c330a --- /dev/null +++ b/examples/pos/operators.check @@ -0,0 +1,255 @@ +0 +0 +1 +2 +3 +3 +4 +5 +6 +6 +7 +8 +9 +9 +10 +11 +12 +13 +14 +15 +16 +16 +17 +18 +19 +19 +20 +21 +22 +22 +23 +24 +25 +25 +26 +27 +28 +29 +30 +31 +32 +32 +33 +34 +35 +35 +36 +37 +38 +38 +39 +40 +41 +41 +42 +43 +44 +45 +46 +47 +48 +48 +49 +50 +51 +51 +52 +53 +54 +54 +55 +56 +57 +57 +58 +59 +60 +61 +62 +63 +64 +64 +65 +66 +67 +67 +68 +69 +70 +70 +71 +72 +73 +73 +74 +75 +76 +77 +78 +79 +80 +80 +81 +82 +83 +83 +84 +85 +86 +86 +87 +88 +89 +89 +90 +91 +92 +93 +94 +95 +96 +96 +97 +98 +99 +99 +100 +101 +102 +102 +103 +104 +105 +105 +106 +107 +108 +109 +110 +111 +112 +112 +113 +114 +115 +115 +116 +117 +118 +118 +119 +120 +121 +121 +122 +123 +124 +125 +126 +127 +128 +128 +129 +130 +131 +131 +132 +133 +134 +134 +135 +136 +137 +137 +138 +139 +140 +141 +142 +143 +144 +144 +145 +146 +147 +147 +148 +149 +150 +150 +151 +152 +153 +153 +154 +155 +156 +157 +158 +159 +160 +160 +161 +162 +163 +163 +164 +165 +166 +166 +167 +168 +169 +169 +170 +171 +172 +173 +174 +175 +176 +176 +177 +178 +179 +179 +180 +181 +182 +182 +183 +184 +185 +185 +186 +187 +188 +189 +190 +191 +192 +192 +193 +194 +195 +195 +196 +197 +198 +198 +199 +200 +201 +201 +202 diff --git a/examples/pos/operators.effekt b/examples/pos/operators.effekt new file mode 100644 index 000000000..0f602bb72 --- /dev/null +++ b/examples/pos/operators.effekt @@ -0,0 +1,30 @@ +import stream + +def infixShl(x: Int, y: Int): Int = bitwiseShl(x, y) +def infixShr(x: Int, y: Int): Int = bitwiseShr(x, y) + +def infixPipe(x: Int, y: Int): Int = bitwiseOr(x, y) +def infixAmp(x: Int, y: Int): Int = bitwiseAnd(x, y) +def infixHat(x: Int, y: Int): Int = bitwiseXor(x, y) + +def infixDotDot(x: Int, y: Int): Unit / emit[Int] = range(x, y) + +def main() = + for[Int] { 0 .. 255 } { redValue => + // Combine RGB channels into a single 24-bit color value + val greenValue = (redValue * 3) / 4 // Slightly less green + val blueValue = redValue / 2 // Half blue + + // Pack RGB into 24-bit color: RRRRRRRRGGGGGGGGBBBBBBBB + val rgb = (redValue << 16) | (greenValue << 8) | blueValue + + // Extract individual channels back out + val extractedRed = (rgb >> 16) & 255 + val extractedGreen = (rgb >> 8) & 255 + val extractedBlue = rgb & 255 + + // Convert to grayscale using standard weights + val grayscale = (extractedRed * 3 + extractedGreen * 6 + extractedBlue) / 10 + + println(grayscale) + } \ No newline at end of file