Skip to content

Commit

Permalink
Merge pull request #1 from nsetyo/add-arrow-function
Browse files Browse the repository at this point in the history
feat: add arrow function support
  • Loading branch information
nsetyo authored Aug 28, 2023
2 parents e031117 + 14fea03 commit 72a0820
Show file tree
Hide file tree
Showing 15 changed files with 242 additions and 76 deletions.
2 changes: 1 addition & 1 deletion .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no -- commitlint --edit
npx --no -- commitlint --edit "$1"
4 changes: 0 additions & 4 deletions .husky/prepare-commit-msg

This file was deleted.

6 changes: 5 additions & 1 deletion src/parser/CharStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,18 @@ export class CharStream {
rewind(marker: Position) {
this.position.line = marker.line
this.position.column = marker.column
this.index = marker.index
this.index = marker.index || -1
}

la(offset: number) {
const index = this.index + offset

return index < this.length ? this.input.charAt(index) : EOF
}

lac(offset: number) {
const index = this.index + offset

return index < this.length ? this.input.charCodeAt(index) : EOF
}

Expand All @@ -68,7 +70,9 @@ export class CharStream {
}
const ch = this.input.charAt(this.index)
this.index++

this.position.column++

if (ch === '\n') {
this.position.line += 1
this.position.column = 0
Expand Down
48 changes: 35 additions & 13 deletions src/parser/Lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ export default class Lexer {
options: Record<string, any>

constructor(input: CharStream, { preserveSourceLiterally = false } = {}) {
this.input = input
this[STATE] = [State.TEXT]
this[OPERATORS] = []
this[STRING_START] = null

this.input = input
this.options = {
preserveSourceLiterally:
preserveSourceLiterally === true ? true : false,
Expand Down Expand Up @@ -103,10 +104,10 @@ export default class Lexer {
this[STATE].length--
}

createToken(type, pos): Token {
createToken(type: string, pos: Position): Token {
const input = this.input
const endPos = input.mark()
const end = endPos.index
const end = endPos.index || -1

return {
end,
Expand All @@ -124,7 +125,9 @@ export default class Lexer {

next(): Token {
const input = this.input
let pos, c

let pos: Position
let c: string | typeof EOF

while ((c = input.la(0)) !== EOF) {
pos = input.mark()
Expand Down Expand Up @@ -164,6 +167,7 @@ export default class Lexer {
input.next()
}
}

if (this.state === State.TEXT) {
let entityToken

Expand Down Expand Up @@ -194,7 +198,9 @@ export default class Lexer {
this.error(
'Unexpected end for HTML comment',
input.mark(),
`Expected comment to end with '>' but found '${c}' instead.`
`Expected comment to end with '>' but found '${String(
c
)}' instead.`
)
}
break
Expand Down Expand Up @@ -345,8 +351,9 @@ export default class Lexer {
}
}

matchExpression(pos) {
matchExpression(pos: Position) {
const input = this.input

const c = input.la(0) as string

switch (c) {
Expand Down Expand Up @@ -384,24 +391,34 @@ export default class Lexer {
}
const { longestMatchingOperator, longestMatchEndPos } =
this.findLongestMatchingOperator()

const cc = input.lac(0)

if (cc === 95 /* _ */ || isAlpha(cc) || isDigit(cc)) {
// okay... this could be either a symbol or an operator
input.next()
const sym = this.matchSymbol(pos)
if (sym.text.length <= longestMatchingOperator.length) {
// the operator was longer so let's use that
input.rewind(longestMatchEndPos)

return this.createToken(TokenTypes.OPERATOR, pos)
}
// found a symbol
return sym
} else if (cc === 61 && input.lac(1) === 62) {
input.next()
input.next()

return this.createToken(TokenTypes.ARROW_TYPE, pos)
} else if (longestMatchingOperator) {
input.rewind(longestMatchEndPos)

return this.createToken(TokenTypes.OPERATOR, pos)
// eslint-disable-next-line no-prototype-builtins
} else if (CHAR_TO_TOKEN.hasOwnProperty(c)) {
input.next()

return this.createToken(CHAR_TO_TOKEN[c], pos)
} else if (c === '\xa0') {
return this.error(
Expand All @@ -422,8 +439,13 @@ export default class Lexer {
let longestMatchingOperator = ''
let longestMatchEndPos: Position = {} as Position

for (let i = 0, ops = this[OPERATORS], len = ops.length; i < len; i++) {
const ops = this[OPERATORS]

const len = ops.length

for (let i = 0; i < len; i++) {
const op: string = ops[i]

if (op.length > longestMatchingOperator.length && input.match(op)) {
const cc = input.lac(0)

Expand Down Expand Up @@ -672,14 +694,14 @@ export default class Lexer {
}
}

function isWhitespace(c) {
return c === '\n' || c === ' ' || c === '\t'
function isWhitespace(c: string | typeof EOF) {
return c !== EOF && (c === '\n' || c === ' ' || c === '\t')
}

function isAlpha(c) {
return (65 <= c && c <= 90) || (97 <= c && c <= 122)
function isAlpha(c: number | typeof EOF) {
return c !== EOF && ((65 <= c && c <= 90) || (97 <= c && c <= 122))
}

function isDigit(c) {
return 48 <= c && c <= 57
function isDigit(c: number | typeof EOF) {
return c !== EOF && 48 <= c && c <= 57
}
80 changes: 65 additions & 15 deletions src/parser/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import * as he from 'he'
import * as n from 'melody-types'

import { ArrowFunctionExpression } from '@/types'

import { LEFT, RIGHT } from './Associativity'
import { voidElements } from './ElementInfo'
import { createMultiTagParser } from './GenericMultiTagParser'
Expand Down Expand Up @@ -275,7 +277,7 @@ export default class Parser {
} else if (currentToken.type === Types.STRING_START) {
const stringToken = tokens.expect(Types.STRING)
declaration.parts.push(
createNode(n.StringLiteral, stringToken, stringToken.text)
createNode(n.StringLiteral, stringToken, stringToken?.text)
)
tokens.expect(Types.STRING_END)
} else if (currentToken.type === Types.EXPRESSION_START) {
Expand Down Expand Up @@ -506,11 +508,12 @@ export default class Parser {
}

matchExpression(precedence = 0) {
const tokens = this.tokens,
exprStartToken = tokens.la(0)
let token,
op,
trimLeft = false
const tokens = this.tokens
const exprStartToken = tokens.la(0)

let token
let op
let trimLeft = false

// Check for {{- (trim preceding whitespace)
if (
Expand All @@ -521,6 +524,7 @@ export default class Parser {
}

let expr = this.getPrimary()

while (
(token = tokens.la(0)) &&
token.type !== Types.EOF &&
Expand Down Expand Up @@ -565,13 +569,40 @@ export default class Parser {
}

getPrimary() {
const tokens = this.tokens,
token = tokens.la(0)
const tokens = this.tokens
const token = tokens.la(0)

const next_six_token = [
tokens.la(0),
tokens.la(1),
tokens.la(2),
tokens.la(3),
tokens.la(4),
tokens.la(5),
]

const arrow_pos = next_six_token.findIndex(
(t) => t.type === Types.ARROW_TYPE
)

if (this.isUnary(token)) {
const op = this[UNARY][token.text]
tokens.next() // consume operator
const expr = this.matchExpression(op.precedence)
return this.matchPostfixExpression(op.createNode(token, expr))
} else if (
(tokens.test(Types.LPAREN) && arrow_pos === 5) ||
(tokens.test(Types.SYMBOL) && arrow_pos === 1)
) {
const args: any[] = tokens.test(Types.LPAREN)
? this.matchArguments()
: [createNode(n.Identifier, tokens.la(0), tokens.next().text)]

tokens.next() // consume the arrow

const body = this.matchExpression()

return new ArrowFunctionExpression(args, body)
} else if (tokens.test(Types.LPAREN)) {
tokens.next() // consume '('
const expr = this.matchExpression()
Expand All @@ -586,6 +617,7 @@ export default class Parser {
const tokens = this.tokens
const token = tokens.la(0)
let node

switch (token.type) {
case Types.NULL:
node = createNode(n.NullLiteral, tokens.next())
Expand Down Expand Up @@ -883,26 +915,32 @@ export default class Parser {
tokens.test(Types.RBRACE) ? null : this.matchExpression()
)
copyStart(result, node)

setEndFromToken(result, tokens.expect(Types.RBRACE))

return result
}
}
}

matchFilterExpression(node) {
matchFilterExpression(node: Node) {
const tokens = this.tokens

let target = node

while (!tokens.test(Types.EOF)) {
const token = tokens.expect(Types.SYMBOL)

const name = createNode(n.Identifier, token, token.text)
let args
if (tokens.test(Types.LPAREN)) {
args = this.matchArguments()
} else {
args = []
}

const args: any[] = tokens.test(Types.LPAREN)
? this.matchArguments()
: []

const newTarget = new n.FilterExpression(target, name, args)

copyStart(newTarget, target)

if (newTarget.arguments.length) {
copyEnd(
newTarget,
Expand All @@ -911,6 +949,7 @@ export default class Parser {
} else {
copyEnd(newTarget, target)
}

target = newTarget

if (!tokens.test(Types.PIPE) || tokens.test(Types.EOF)) {
Expand All @@ -919,38 +958,49 @@ export default class Parser {

tokens.next() // consume '|'
}

return target
}

matchArguments() {
const tokens = this.tokens

const args: any[] = []

tokens.expect(Types.LPAREN)

while (!tokens.test(Types.RPAREN) && !tokens.test(Types.EOF)) {
if (
tokens.test(Types.SYMBOL) &&
tokens.lat(1) === Types.ASSIGNMENT
) {
const name = tokens.next()

tokens.next()

const value = this.matchExpression()

const arg = new n.NamedArgumentExpression(
createNode(n.Identifier, name, name.text),
value
)

copyEnd(arg, value)

args.push(arg)
} else {
args.push(this.matchExpression())
}

if (!tokens.test(Types.COMMA)) {
tokens.expect(Types.RPAREN)

return args
}
tokens.expect(Types.COMMA)
}
tokens.expect(Types.RPAREN)

return args
}
}
Loading

0 comments on commit 72a0820

Please sign in to comment.