Website Summary Files Commits Branches @commit @diff @stats
diff options
context:
space:
mode:
Commit97a58430a248b9ea89a7da0120cfceb0e6b11af5 (patch)
Author
 Dobromir Ivanov <dobromir0ivanov@gmail.com>
2018-11-07 15:06:45 +0200
Committer
 dtrendafilov <trendafilov.dn@gmail.com>
2018-11-07 15:06:45 +0200
Files71bc769a2c4848d0bd40850b6ad94ea9e4fab3d3
IPL-97a58430a248b9ea89a7da0120cfceb0e6b11af5.tar
IPL-97a58430a248b9ea89a7da0120cfceb0e6b11af5.tar.gz
IPL-97a58430a248b9ea89a7da0120cfceb0e6b11af5.zip
Parent13f074686970cf037cbd8a4c492033bb7b14562d (diff)
A (naive) golang implementation of a JavaScript lexer. (#18)
Not that comments single line or multi line are not parsed. Numbers are parsed in the most naive way possible so don't expect miracles.
4 files changed, 744 insertions, 0 deletions
-rw-r--r--homework/45180/README.md19
-rw-r--r--homework/45180/hw1.html75
-rw-r--r--homework/45180/input.js51
-rw-r--r--homework/45180/lexer.go599
diff --git a/homework/45180/README.md b/homework/45180/README.md
new file mode 100644
index 0000000..3c56991
--- /dev/null
+++ b/homework/45180/README.md
@@ -0,0 +1,19 @@
+# Homework 1
+Create a xxx2html syntax highlighter for a language of your choice.
+
+### How to install and use the go toolchain
+
++ [How to install the Go programming language](https://golang.org/doc/install)
+
+--------------
+
+# Disclaimer
+
+ - Written on my own, does not support comments (nor single line, neither multiline)
+ - Parsing numbers is done in the most naive way possible. For example if the
+ input is 2-3 it will be considered a single number.
+
+
+# Running the whole thing
+
+``` go run lexer.go ./ingput.hs > output.html ```
diff --git a/homework/45180/hw1.html b/homework/45180/hw1.html
new file mode 100644
index 0000000..74c7f4b
--- /dev/null
+++ b/homework/45180/hw1.html
@@ -0,0 +1,75 @@
+
+<!doctype>
+<html>
+ <head>
+ <style>
+ .keyword {
+ color: red;
+ }
+ .number {
+ color: blue;
+ }
+ .string {
+ color: green;
+ }
+ .operator {
+ font-style: bold;
+ }
+ .identifier {
+ color: orange;
+ }
+ </style>
+ </head>
+ <body>
+ <pre class="code">
+<span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="identifier">Queue</span> {
+ <span class="identifier">constructor</span>() {
+ <span class="keyword">this</span>.<span class="identifier">_firstNode</span> <span class="operator">=</span> <span class="keyword">null</span>;
+ <span class="keyword">this</span>.<span class="identifier">_lastNode</span> <span class="operator">=</span> <span class="keyword">null</span>;
+ }
+
+ <span class="identifier">dequeue</span>() {
+ <span class="keyword">if</span> (<span class="operator">!</span><span class="keyword">this</span>.<span class="identifier">_firstNode</span>) {
+ <span class="keyword">return</span> <span class="keyword">this</span>.<span class="identifier">_firstNode</span>;
+ }
+
+ <span class="keyword">const</span> <span class="identifier">payload</span> <span class="operator">=</span> <span class="keyword">this</span>.<span class="identifier">_firstNode</span>.<span class="identifier">payload</span>;
+
+ <span class="keyword">this</span>.<span class="identifier">_firstNode</span> <span class="operator">=</span> <span class="keyword">this</span>.<span class="identifier">_firstNode</span>.<span class="identifier">next</span>;
+
+ <span class="keyword">return</span> <span class="identifier">payload</span>;
+ }
+
+ <span class="identifier">enqueue</span>(<span class="identifier">item</span>) {
+ <span class="keyword">const</span> <span class="identifier">newNode</span> <span class="operator">=</span> {
+ <span class="identifier">payload</span>: <span class="identifier">item</span>,
+ <span class="identifier">next</span>: <span class="keyword">null</span>
+ };
+
+ <span class="keyword">if</span> (<span class="keyword">this</span>.<span class="identifier">_firstNode</span> <span class="operator">===</span> <span class="keyword">null</span>) {
+ <span class="keyword">this</span>.<span class="identifier">_firstNode</span> <span class="operator">=</span> <span class="identifier">newNode</span>;
+ <span class="keyword">this</span>.<span class="identifier">_lastNode</span> <span class="operator">=</span> <span class="identifier">newNode</span>;
+
+ } <span class="keyword">else</span> {
+ <span class="keyword">this</span>.<span class="identifier">_lastNode</span>.<span class="identifier">next</span> <span class="operator">=</span> <span class="identifier">newNode</span>;
+ <span class="keyword">this</span>.<span class="identifier">_lastNode</span> <span class="operator">=</span> <span class="identifier">newNode</span>;
+ }
+ }
+
+ <span class="identifier">clear</span>() {
+ <span class="keyword">this</span>.<span class="identifier">_firstNode</span> <span class="operator">=</span> <span class="keyword">null</span>;
+ <span class="keyword">this</span>.<span class="identifier">_lastNode</span> <span class="operator">=</span> <span class="keyword">null</span>;
+ }
+
+ <span class="identifier">top</span>() {
+ <span class="keyword">if</span> (<span class="operator">!</span><span class="keyword">this</span>.<span class="identifier">_firstNode</span>) {
+ <span class="keyword">return</span> <span class="keyword">this</span>.<span class="identifier">_firstNode</span>;
+ }
+
+ <span class="keyword">return</span> <span class="keyword">this</span>.<span class="identifier">_firstNode</span>.<span class="identifier">payload</span>;
+ }
+
+ <span class="identifier">isEmpty</span>() {
+ <span class="keyword">return</span> <span class="operator">!</span><span class="keyword">this</span>.<span class="identifier">_firstNode</span>;
+ }
+}</pre></body></html> \ No newline at end of file
diff --git a/homework/45180/input.js b/homework/45180/input.js
new file mode 100644
index 0000000..6974019
--- /dev/null
+++ b/homework/45180/input.js
@@ -0,0 +1,51 @@
+export default class Queue {
+ constructor() {
+ this._firstNode = null;
+ this._lastNode = null;
+ }
+
+ dequeue() {
+ if (!this._firstNode) {
+ return this._firstNode;
+ }
+
+ const payload = this._firstNode.payload;
+
+ this._firstNode = this._firstNode.next;
+
+ return payload;
+ }
+
+ enqueue(item) {
+ const newNode = {
+ payload: item,
+ next: null
+ };
+
+ if (this._firstNode === null) {
+ this._firstNode = newNode;
+ this._lastNode = newNode;
+
+ } else {
+ this._lastNode.next = newNode;
+ this._lastNode = newNode;
+ }
+ }
+
+ clear() {
+ this._firstNode = null;
+ this._lastNode = null;
+ }
+
+ top() {
+ if (!this._firstNode) {
+ return this._firstNode;
+ }
+
+ return this._firstNode.payload;
+ }
+
+ isEmpty() {
+ return !this._firstNode;
+ }
+}
diff --git a/homework/45180/lexer.go b/homework/45180/lexer.go
new file mode 100644
index 0000000..9185462
--- /dev/null
+++ b/homework/45180/lexer.go
@@ -0,0 +1,599 @@
+package main
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "unicode"
+)
+
+const (
+ // Single-character tokens.
+ TokenLeftParen = iota
+ TokenRightParen
+ TokenLeftBrace
+ TokenRightBrace
+ TokenSemicolon
+ TokenDot
+ TokenComma
+ TokenColon
+ TokenQuestionmark
+ TokenLeftSquareBracket
+ TokenRightSquareBracket
+ TokenMinus
+ TokenPlus
+ TokenStar
+ TokenDivision
+ TokenModulo
+ TokenBitwiseNot
+ TokenBitwiseAnd
+ TokenBitwiseXor
+ TokenBitwiseOr
+
+ // One or two character tokens
+ TokenBang
+ TokenNotEqual
+ TokenEqual
+ TokenEqualEqual
+ TokenStrictEqual
+ TokenStrictNotEqual
+ TokenGreater
+ TokenGreaterEqual
+ TokenLess
+ TokenLessEqual
+ TokenMinusMinus
+ TokenPlusplus
+ TokenLeftShift
+ TokenRightShift
+ TokenLogicalAnd
+ TokenLogicalOr
+ TokenStarEqual
+ TokenDivideEqual
+ TokenModuloEqual
+ TokenPlusEqual
+ TokenMinusEqual
+ TokenLeftShiftEqual
+ TokenRightShiftEqual
+ TokenBitwiseAndEqual
+ TokenBitwiseXorEqual
+ TokenBitwiseOrEqual
+
+ TokenString
+ TokenNumber
+ TokenKeyword
+ TokenWhitespace
+ TokenIdentifier
+
+ TokenEOF
+ TokenError
+)
+
+var keywords = map[string]struct{}{
+ "break": {},
+ "case": {},
+ "catch": {},
+ "class": {},
+ "const": {},
+ "continue": {},
+ "debugger": {},
+ "default": {},
+ "delete": {},
+ "do": {},
+ "else": {},
+ "export": {},
+ "extends": {},
+ "finally": {},
+ "for": {},
+ "function": {},
+ "if": {},
+ "import": {},
+ "in": {},
+ "instanceof": {},
+ "new": {},
+ "return": {},
+ "super": {},
+ "switch": {},
+ "this": {},
+ "throw": {},
+ "try": {},
+ "typeof": {},
+ "var": {},
+ "void": {},
+ "while": {},
+ "with": {},
+ "yield": {},
+ "null": {},
+ "undefined": {},
+ "true": {},
+ "false": {},
+}
+
+var singleCharTokens = map[rune]struct{}{
+ '(': {},
+ ')': {},
+ '[': {},
+ ']': {},
+ '{': {},
+ '}': {},
+ ',': {},
+ '.': {},
+ ';': {},
+ '-': {},
+ '+': {},
+ '*': {},
+ '/': {},
+ '%': {},
+ '~': {},
+ '=': {},
+ '!': {},
+ '>': {},
+ '<': {},
+ '&': {},
+ '|': {},
+ '^': {},
+ '?': {},
+ ':': {},
+}
+
+type Token struct {
+ TokenType int
+ TokenValue string
+ Err error
+}
+
+type Tokenizer interface {
+ Tokenize(reader io.Reader, tokens chan Token)
+}
+
+type tokenizerImpl struct {
+ emitWhitespace bool
+}
+
+func NewTokenizer(emitWhitespace bool) Tokenizer {
+ return tokenizerImpl{emitWhitespace}
+}
+
+func (tokenizer tokenizerImpl) Tokenize(in io.Reader, tokens chan Token) {
+ reader := bufio.NewReader(in)
+ defer close(tokens)
+ for {
+ var symbol rune
+ var err error
+
+ if token := newToken(reader, readWhitespace); tokenizer.emitWhitespace {
+ tokens <- token
+ }
+ if symbol, err = peek(reader); err != nil {
+ var tokenType int
+ if err == io.EOF {
+ tokenType = TokenEOF
+ } else {
+ tokenType = TokenError
+ }
+ tokens <- Token{tokenType, "", err}
+ return
+ }
+
+ if isSingleCharToken(symbol) {
+ tokens <- newToken(reader, readSingleCharToken)
+ continue
+ } else {
+ reader.UnreadRune()
+ }
+
+ if unicode.IsDigit(symbol) {
+ tokens <- newToken(reader, readNumber)
+ continue
+ }
+
+ if symbol == '"' {
+ tokens <- newToken(reader, readString)
+ continue
+ }
+
+ tokens <- newToken(reader, readKeywordOrIdentifier)
+ }
+}
+
+func newToken(reader *bufio.Reader, tokenGenerator func(reader *bufio.Reader) (int, string, error)) Token {
+ if tokenType, tokenValue, err := tokenGenerator(reader); err != nil {
+ if err == io.EOF {
+ tokenType = TokenEOF
+ } else {
+ tokenType = TokenError
+ }
+ return Token{tokenType, "", err}
+ } else {
+ return Token{tokenType, tokenValue, nil}
+ }
+}
+
+func peek(reader *bufio.Reader) (rune, error) {
+ symbol, _, err := reader.ReadRune()
+ reader.UnreadRune()
+ return symbol, err
+}
+
+func isWhitespace(r rune) bool {
+ return r == ' ' || r == '\t' || r == '\n'
+}
+
+func readWhitespace(reader *bufio.Reader) (int, string, error) {
+ var symbol rune
+ var err error
+ var sb strings.Builder
+ for {
+ if symbol, _, err = reader.ReadRune(); err == nil && isWhitespace(symbol) {
+ sb.WriteRune(symbol)
+ } else {
+ break
+ }
+ }
+ reader.UnreadRune()
+ return TokenWhitespace, sb.String(), err
+}
+
+func isSingleCharToken(symbol rune) bool {
+ _, isPresent := singleCharTokens[symbol]
+ return isPresent
+}
+
+func readSingleCharToken(reader *bufio.Reader) (int, string, error) {
+ var token int
+ symbol, _, err := reader.ReadRune()
+ tokenValue := string(symbol)
+ switch symbol {
+ case '(':
+ token = TokenLeftParen
+ break
+ case ')':
+ token = TokenRightParen
+ break
+ case '[':
+ token = TokenLeftSquareBracket
+ break
+ case ']':
+ token = TokenRightSquareBracket
+ break
+ case '{':
+ token = TokenLeftBrace
+ break
+ case '}':
+ token = TokenRightBrace
+ break
+ case ',':
+ token = TokenComma
+ break
+ case '.':
+ token = TokenDot
+ break
+ case ';':
+ token = TokenSemicolon
+ break
+ case '-':
+ token = TokenMinus
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenMinusEqual
+ tokenValue = "-="
+ } else if s == '-' {
+ token = TokenMinusMinus
+ tokenValue = "--"
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '+':
+ token = TokenPlus
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenPlusEqual
+ tokenValue = "+="
+ } else if s == '+' {
+ token = TokenPlusplus
+ tokenValue = "++"
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '*':
+ token = TokenStar
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenStarEqual
+ tokenValue = "*="
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '/':
+ token = TokenDivision
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenDivideEqual
+ tokenValue = "/="
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '%':
+ token = TokenModulo
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenModuloEqual
+ tokenValue = "%="
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '~':
+ token = TokenBitwiseNot
+ break
+ case '=':
+ token = TokenEqual
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenStrictEqual
+ tokenValue = "==="
+ } else {
+ token = TokenEqualEqual
+ reader.UnreadRune()
+ tokenValue = "=="
+ }
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '!':
+ token = TokenBang
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenStrictNotEqual
+ tokenValue = "!==="
+ } else {
+ token = TokenNotEqual
+ tokenValue = "!=="
+ reader.UnreadRune()
+ }
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '>':
+ token = TokenGreater
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenGreaterEqual
+ tokenValue = ">="
+ } else if s == '>' {
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenRightShiftEqual
+ tokenValue = ">>="
+ } else {
+ reader.UnreadRune()
+ token = TokenRightShift
+ tokenValue = ">>"
+ }
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '<':
+ token = TokenLess
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenLessEqual
+ } else if s == '>' {
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenLeftShiftEqual
+ tokenValue = "<<="
+ } else {
+ reader.UnreadRune()
+ token = TokenLeftShift
+ tokenValue = "<<"
+ }
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '&':
+ token = TokenBitwiseAnd
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenBitwiseAndEqual
+ tokenValue = "&="
+ } else if s == '&' {
+ token = TokenLogicalAnd
+ tokenValue = "&&"
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '|':
+ token = TokenBitwiseOr
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenBitwiseOrEqual
+ tokenValue = "|="
+ } else if s == '|' {
+ token = TokenLogicalOr
+ tokenValue = "||"
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '^':
+ token = TokenBitwiseXor
+ if s, _, readErr := reader.ReadRune(); err != nil {
+ err = readErr
+ } else if s == '=' {
+ token = TokenBitwiseXorEqual
+ tokenValue = "^="
+ } else {
+ reader.UnreadRune()
+ }
+ break
+ case '?':
+ token = TokenQuestionmark
+ break
+ case ':':
+ token = TokenColon
+ break
+ default:
+ err = errors.New(fmt.Sprintf("Invalid token %s", symbol))
+ token = TokenError
+ }
+ return token, tokenValue, err
+}
+
+func isKeyword(rawToken string) bool {
+ _, isPresent := keywords[strings.ToLower(rawToken)]
+ return isPresent
+}
+
+func isIdentifierOrKeywordSymbol(symbol rune) bool {
+ return unicode.IsLetter(symbol) || unicode.IsDigit(symbol) || symbol == '_'
+}
+
+func readKeywordOrIdentifier(reader *bufio.Reader) (int, string, error) {
+ var sb strings.Builder
+ for {
+ symbol, _, err := reader.ReadRune()
+ if err != nil || isWhitespace(symbol) || !isIdentifierOrKeywordSymbol(symbol) {
+ if err == nil {
+ reader.UnreadRune()
+ break
+ }
+ if err != io.EOF {
+ return TokenError, "", err
+ }
+ if err == io.EOF {
+ return TokenEOF, "", err
+ }
+ } else {
+ sb.WriteRune(symbol)
+ }
+ }
+ rawToken := sb.String()
+ if isKeyword(rawToken) {
+ return TokenKeyword, rawToken, nil
+ } else {
+ return TokenIdentifier, rawToken, nil
+ }
+}
+
+func isNumberSymbol(symbol rune) bool {
+ return unicode.IsDigit(symbol) || symbol == '.' || symbol == 'e' || symbol == '-'
+}
+
+// Obviously extremely naive and will most certainly not work correctly in anything but the
+// straightforward case. For example this fails epicly if we have an expression like
+// 2+3 which will be parsed as a valid single number.
+// TODO Address this nonsense.
+func readNumber(reader *bufio.Reader) (int, string, error) {
+ var sb strings.Builder
+ for {
+ if symbol, _, err := reader.ReadRune(); err != nil || !isNumberSymbol(symbol) {
+ reader.UnreadRune()
+ return TokenNumber, sb.String(), err
+ } else {
+ sb.WriteRune(symbol)
+ }
+ }
+}
+
+func readString(reader *bufio.Reader) (int, string, error) {
+ var sb strings.Builder
+ openingQuote, _, err := reader.ReadRune()
+ if err != nil {
+ return TokenError, "", err
+ }
+ for {
+ if symbol, _, err := reader.ReadRune(); err != nil {
+ return TokenError, "", err
+ } else if symbol == openingQuote {
+ return TokenString, sb.String(), nil
+ } else {
+ sb.WriteRune(symbol)
+ }
+ }
+}
+
+func main() {
+ file, _ := os.Open(os.Args[1])
+ reader := bufio.NewReader(file)
+
+ tokenChan := make(chan Token)
+ go NewTokenizer(true).Tokenize(reader, tokenChan)
+ fmt.Println(`
+<!doctype>
+<html>
+ <head>
+ <style>
+ .keyword {
+ color: red;
+ }
+ .number {
+ color: blue;
+ }
+ .string {
+ color: green;
+ }
+ .operator {
+ font-style: bold;
+ }
+ .identifier {
+ color: orange;
+ }
+ </style>
+ </head>
+ <body>
+ <pre class="code">`)
+
+ for token := range tokenChan {
+ if token.TokenType >= TokenMinus && token.TokenType <= TokenBitwiseOrEqual {
+ outputToken("operator", token.TokenValue)
+ } else if token.TokenType == TokenString {
+ outputToken("string", token.TokenValue)
+ } else if token.TokenType == TokenKeyword {
+ outputToken("keyword", token.TokenValue)
+ } else if token.TokenType == TokenNumber {
+ outputToken("number", token.TokenValue)
+ } else if token.TokenType == TokenIdentifier {
+ outputToken("identifier", token.TokenValue)
+ } else {
+ fmt.Print(token.TokenValue)
+ }
+ }
+
+ fmt.Print("</pre></body></html>")
+ file.Close()
+}
+
+func outputToken(tokenType, tokenValue string) {
+ fmt.Print(fmt.Sprintf("<span class=\"%s\">%s</span>", tokenType, tokenValue))
+}