Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Low-precedence and/or/not #548

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions civet.dev/cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,13 +319,25 @@ a + b = c
<Playground>
a is b
a is not b
not a
a and b
a or b
a not in b
a not instanceof b
a?
</Playground>

### Two Levels of Precedence

Like Perl, Ruby, and LiveScript, `and`/`or`/`not` have lower precedence
than `&&`/`||`/`!`/comparisons (unless you [explicitly disable via
`"civet coffeeAndOrNot"`](#coffeescript-operators)).

<Playground>
not a == b
a || b and c || d
</Playground>

### Includes Operator

<Playground>
Expand Down Expand Up @@ -1440,6 +1452,11 @@ x == y != z
x isnt y
</Playground>

<Playground>
"civet coffeeAndOr"
a || b and c || d
</Playground>

<Playground>
"civet coffeeNot"
not (x == y)
Expand Down
3 changes: 2 additions & 1 deletion notes/Comparison-to-CoffeeScript.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,15 @@ Civet provides a compatibility prologue directive that aims to be 97+% compatibl
| Configuration | What it enables |
|---------------------|---------------------------------------------------------------------|
| autoVar | declare implicit vars based on assignment to undeclared identifiers |
| coffeeAndOr | `and` → `&&`, `or` → `||` with same precedence |
| coffeeBooleans | `yes`, `no`, `on`, `off` |
| coffeeComment | `# single line comments` |
| coffeeDo | `do ->`, disables ES6 do/while |
| coffeeEq | `==` → `===`, `!=` → `!==` |
| coffeeForLoops | for in, of, from loops behave like they do in CoffeeScript |
| coffeeInterpolation | `"a string with #{myVar}"` |
| coffeeIsnt | `isnt` → `!==` |
| coffeeNot | `not` → `!` |
| coffeeNot | `not` → `!` with same precedence |
| coffeeOf | `a of b` → `a in b`, `a not of b` → `!(a in b)`, `a in b` → `b.indexOf(a) >= 0`, `a not in b` → `b.indexOf(a) < 0` |
| coffeePrototype | enables `x::` -> `x.prototype` and `x::y` -> `x.prototype.y` shorthand.

Expand Down
26 changes: 26 additions & 0 deletions source/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,18 @@ function expressionizeIteration(exp) {
)
}

/**
* binops is an array of [__, op, __, exp] tuples
* first is an expression
*/
function processLowBinaryOpExpression([first, binops]) {
const out = [makeLeftHandSideExpression(first)]
binops.forEach(([pre, op, post, exp]) => {
out.push(pre, op, post, makeLeftHandSideExpression(exp))
})
return out
}

function processBinaryOpExpression($0) {
const expandedOps = expandChainedComparisons($0)

Expand Down Expand Up @@ -1410,6 +1422,10 @@ function makeLeftHandSideExpression(expression) {
case "CallExpression":
case "MemberExpression":
case "ParenthesizedExpression":
case "DebuggerExpression": // wrapIIFE
case "SwitchExpression": // wrapIIFE
case "ThrowExpression": // wrapIIFE
case "TryExpression": // wrapIIFE
return expression
default:
return {
Expand Down Expand Up @@ -2820,6 +2836,14 @@ function processReturnValue(func) {
return true
}

function processLowUnaryExpression(pre, exp) {
if (!pre.length) return exp
return {
type: "UnaryExpression",
children: [...pre, makeLeftHandSideExpression(exp)],
}
}

function processUnaryExpression(pre, exp, post) {
if (!(pre.length || post)) return exp
// Handle "?" postfix
Expand Down Expand Up @@ -3154,6 +3178,8 @@ module.exports = {
processCoffeeInterpolation,
processConstAssignmentDeclaration,
processLetAssignmentDeclaration,
processLowBinaryOpExpression,
processLowUnaryExpression,
processParams,
processProgram,
processReturnValue,
Expand Down
5 changes: 5 additions & 0 deletions source/main.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ uncacheable = new Set [
"JSXOptionalClosingFragment"
"JSXTag"
"LeftHandSideExpression"
"LowBinaryOpExpression"
"LowBinaryOpRHS"
"LowRHS"
"LowUnaryExpression"
"MemberExpression"
"MemberExpressionRest"
"Nested"
Expand Down Expand Up @@ -126,6 +130,7 @@ uncacheable = new Set [
"SingleLineAssignmentExpression"
"SingleLineBinaryOpRHS"
"SingleLineComment"
"SingleLineLowBinaryOpRHS"
"SingleLineStatements"
"SnugNamedProperty"
"Statement"
Expand Down
78 changes: 70 additions & 8 deletions source/parser.hera
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const {
processCoffeeInterpolation,
processConstAssignmentDeclaration,
processLetAssignmentDeclaration,
processLowBinaryOpExpression,
processLowUnaryExpression,
processProgram,
processUnaryExpression,
quoteString,
Expand Down Expand Up @@ -259,10 +261,31 @@ NonPipelineArgumentPart
}
return $1

# NOTE: Match low-precedence binary ops first
LowBinaryOpExpression
LowUnaryExpression LowBinaryOpRHS* ->
if (!$2.length) return $1
return processLowBinaryOpExpression($0)

# NOTE: Excluding low-precedence binary ops
BinaryOpExpression
UnaryExpression BinaryOpRHS* ->
if ($2.length) return processBinaryOpExpression($0)
return $1
if (!$2.length) return $1
return processBinaryOpExpression($0)

LowBinaryOpRHS
# Snug binary ops a+b
LowBinaryOp:op LowRHS:rhs ->
// Insert empty whitespace placeholder to maintan structure
return [[], op, [], rhs]
# Spaced binary ops a + b
# a
# + b
# Does not match
# a
# +b
NewlineBinaryOpAllowed ( NotDedented LowBinaryOp ( _ / ( EOS __ ) ) LowRHS ):rhs -> rhs
!NewlineBinaryOpAllowed SingleLineLowBinaryOpRHS -> $2

BinaryOpRHS
# Snug binary ops a+b
Expand All @@ -278,12 +301,24 @@ BinaryOpRHS
NewlineBinaryOpAllowed ( NotDedented BinaryOp ( _ / ( EOS __ ) ) RHS ):rhs -> rhs
!NewlineBinaryOpAllowed SingleLineBinaryOpRHS -> $2

SingleLineLowBinaryOpRHS
# NOTE: It's named single line but that's only for the operator, the RHS can be after a newline
# This is to maintain compatibility with CoffeeScript conditions
_?:ws1 LowBinaryOp:op ( _ / ( EOS __ ) ):ws2 LowRHS:rhs ->
return [ws1 || [], op, ws2, rhs]

SingleLineBinaryOpRHS
# NOTE: It's named single line but that's only for the operator, the RHS can be after a newline
# This is to maintain compatibility with CoffeeScript conditions
_?:ws1 BinaryOp:op ( _ / ( EOS __ ) ):ws2 RHS:rhs ->
return [ws1 || [], op, ws2, rhs]

LowRHS
ParenthesizedAssignment
# NOTE: Here is one transition from low precedence to high precedence
BinaryOpExpression
ExpressionizedStatement

RHS
ParenthesizedAssignment
UnaryExpression
Expand All @@ -292,7 +327,13 @@ RHS
ParenthesizedAssignment
InsertOpenParen ActualAssignment InsertCloseParen

LowUnaryExpression
# NOTE: Here is one transition from low precedence to high precedence
LowUnaryOp*:pre BinaryOpExpression:exp ->
return processLowUnaryExpression(pre, exp)

# https://262.ecma-international.org/#prod-UnaryExpression
# but excluding low-precedence unary ops
UnaryExpression
# NOTE: Merged AwaitExpression with UnaryOp
# https://262.ecma-international.org/#prod-AwaitExpression
Expand Down Expand Up @@ -465,7 +506,7 @@ NestedTernaryRest
# https://262.ecma-international.org/#prod-ShortCircuitExpression
ShortCircuitExpression
# NOTE: We don't need to track the precedence of all the binary operators so they all collapse into this
BinaryOpExpression
LowBinaryOpExpression

PipelineExpression
_?:ws PipelineHeadItem:head ( NotDedented Pipe __ PipelineTailItem )+:body ->
Expand Down Expand Up @@ -2597,6 +2638,10 @@ CoffeeWordAssignmentOp
"and=" -> "&&="
"or=" -> "||="

LowBinaryOp
!CoffeeAndOrEnabled "and" NonIdContinue -> "&&"
!CoffeeAndOrEnabled "or" NonIdContinue -> "||"

BinaryOp
BinaryOpSymbol ->
if (typeof $1 === "string") return { $loc, token: $1 }
Expand Down Expand Up @@ -2670,10 +2715,10 @@ BinaryOpSymbol
"==" ->
if(module.config.coffeeEq) return "==="
return $1
"and" NonIdContinue -> "&&"
CoffeeAndOrEnabled "and" NonIdContinue -> "&&"
"&&"
CoffeeOfEnabled "of" NonIdContinue -> "in"
"or" NonIdContinue -> "||"
CoffeeAndOrEnabled "or" NonIdContinue -> "||"
"||"
# NOTE: ^^ must be above ^
"^^" / ( "xor" NonIdContinue ) ->
Expand Down Expand Up @@ -2775,14 +2820,17 @@ Xor
Xnor
/!\^\^?/ / "xnor"

LowUnaryOp
!CoffeeNotEnabled Not

UnaryOp
# Lookahead to prevent unary operators from overriding update operators
# ++/-- or block unary operator shorthand
/(?!\+\+|--)[!~+-](?!\s|[!~+-]*&)/ ->
return { $loc, token: $0 }
AwaitOp
( Delete / Void / Typeof ) !":" _?
Not # only when CoffeeNotEnabled (see definition of `Not`)
CoffeeNotEnabled Not

# https://github.com/tc39/proposal-await.ops
AwaitOp
Expand Down Expand Up @@ -4756,8 +4804,7 @@ New
return { $loc, token: $1 }

Not
# Not keyword only active in compat mode
CoffeeNotEnabled "not" NonIdContinue " "? ->
"not" NonIdContinue " "? !( _? ":" ) ->
return { $loc, token: "!" }

Of
Expand Down Expand Up @@ -6135,6 +6182,11 @@ InsertVar
"" ->
return { $loc, token: "var " }

CoffeeAndOrEnabled
"" ->
if(module.config.coffeeAndOr) return
return $skip

CoffeeBinaryExistentialEnabled
"" ->
if(module.config.coffeeBinaryExistential) return
Expand Down Expand Up @@ -6274,6 +6326,7 @@ Reset
module.config = parse.config = {
autoVar: false,
autoLet: false,
coffeeAndOr: false,
coffeeBinaryExistential: false,
coffeeBooleans: false,
coffeeClasses: false,
Expand Down Expand Up @@ -6417,11 +6470,20 @@ Reset
// default to deno compatibility if running in deno
module.config.deno = typeof Deno !== "undefined"

// coffeeAndOrNot shorthand for coffeeAndOr and coffeeNot
Object.defineProperty(module.config, "coffeeAndOrNot", {
set(b) {
module.config.coffeeAndOr = b
module.config.coffeeNot = b
}
})

// Expand setting coffeeCompat to the individual options
Object.defineProperty(module.config, "coffeeCompat", {
set(b) {
for (const option of [
"autoVar",
"coffeeAndOr",
"coffeeBinaryExistential",
"coffeeBooleans",
"coffeeClasses",
Expand Down
9 changes: 9 additions & 0 deletions test/object.civet
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,15 @@ describe "object", ->
({${line}: 1})
```

testCase ```
coffeeCompat braceless inline object with ${line} as key
---
"civet coffeeCompat"
${line}: 1
---
({${line}: 1})
```

testCase """
braceless inline object in function call
---
Expand Down