//This lexer has been writen with help of "Real world OCaml" book By Yaron Minsky, Anil Madhavapeddy, Jason Hickey (chapter 16)
//see: https://github.com/fsprojects/FsLexYacc/blob/94f96a3ded28019e9376c985e3465d25d9a8a80e/tests/fsyacc/unicode/test1-unicode-lex.fsl
{

module Aestas.Commands.Compiler.Lexer

open FSharp.Text.Lexing
open System
open System.Text
open Parser

exception SyntaxError of string

let lexeme = LexBuffer<_>.LexemeString

let newline (lexbuf: LexBuffer<_>) = 
  lexbuf.StartPos <- lexbuf.StartPos.NextLine

let ( @+ ) (sb: StringBuilder) (s: string) = sb.Append(s)
let to_string (sb: StringBuilder) = sb.ToString()

let escape_to_string = function
| "\\\\" -> "\\"
| "\\\"" -> "\""
| "\\n" -> "\n"
| x -> x
}

let digit = '\Nd'
let letter = '\Lu' | '\Ll' | '\Lm' | '\Lo' | '\Nl'

let frac = '.' digit*
let exp = ['e' 'E'] ['-' '+']? digit+
let float = digit+ frac? exp?

let ident_start = letter | '_'  | '@'

let connecting_char = '\Pc'
let combining_char = '\Mn' | '\Mc'
let formatting_char = '\Cf' 
let ident_char = letter | digit | connecting_char | combining_char | formatting_char | '\''

let id = ('-' | "--")? ident_start ident_char* 

let op_comp = '<' | '>' | "<>" | ">=" | "<="
let op_0 = '+' | '-'
let op_1 = '*' | '/'

let white = [' ' '\t']+
let newline = ['\r' '\n' ' ']+

let string_escape = '\\' ['"' '\\' 'n']

rule read =
  parse
  | white    { SPACE }
  | newline  { newline lexbuf; NEWLINE }
  | "let"    { LET }
  | 'Î»'      { LAMBDA }
  | "lambda" { LAMBDA }
  | "true"   { TRUE }
  | "false"  { FALSE }
  | "if"     { IF }
  | "then"   { THEN }
  | "else"   { ELSE }
  | "->"     { RIGHT_ARROW }
  | op_0     { BINOP_0 (string (lexeme lexbuf)) }
  | op_1     { BINOP_1 (string (lexeme lexbuf)) }
  | id       { ID (string (lexeme lexbuf))}
  | float    { NUMBER (float (lexeme lexbuf)) }
  | '='      { EQUAL }
  | op_comp  { BINOP_COMP (string (lexeme lexbuf)) }
  | '"'      { read_string (StringBuilder()) lexbuf } 
  | '{'      { LEFT_BRACE }
  | '}'      { RIGHT_BRACE }
  | '['      { LEFT_BRACK }
  | ']'      { RIGHT_BRACK }
  | '('      { LEFT_PAREN }
  | ')'      { RIGHT_PAREN }
  | ':'      { COLON }
  | ','      { COMMA }
  | '^'      { POWER }
  | '|'      { PIPE }
  | "..."    { TAILPATTERN }
  | '.'      { DOT }
  | eof      { EOF }
  | _ { raise (Exception (sprintf "SyntaxError: Unexpected char: '%s' Line: %d Column: %d" (lexeme lexbuf) (lexbuf.StartPos.Line + 1) lexbuf.StartPos.Column)) }


and read_string (str: StringBuilder) =
  parse
  | '"'                     { STRING (to_string str) }
  | [^ '\n' '\r' '"' '\\']+ { read_string (str @+ (lexeme lexbuf)) lexbuf }
  | string_escape           { read_string (str @+ (lexeme lexbuf |> escape_to_string)) lexbuf }
  | eof                     { raise (Exception ("String is not terminated")) }