local asserteq = require('pl.test').asserteq
local lexer = require 'pl.lexer'
local seq = require 'pl.seq'
local List = require('pl.List')
local open = require('pl.stringio').open
local copy2 = seq.copy2

local function test_scan(str, filter, options, expected_tokens, lang)
    local matches
    if lang then
        matches, filter = filter, options
    else
        lang = 'scan'
    end

    asserteq(copy2(lexer[lang](str, matches, filter, options)), expected_tokens)
    if lang == 'scan' then
        asserteq(copy2(lexer[lang](open(str), matches, filter, options)), expected_tokens)
    end
end

local s = '20 = hello'
test_scan(s, {space=false}, {number=false}, {
    {'number', '20'}, {'space', ' '}, {'=', '='}, {'space', ' '}, {'iden', 'hello'}
})
test_scan(s, {space=true}, {number=true}, {
    {'number', 20}, {'=', '='}, {'iden', 'hello'}
})
s = [[    'help'  "help" "dolly you're fine" "a \"quote\" here"]]
test_scan(s, nil, nil, {
    {'string', 'help'}, {'string', 'help'},
    {'string', "dolly you're fine"}, {'string', 'a \\\"quote\\\" here'}  -- Escapes are preserved literally.
})
test_scan([[\abc\]], nil, nil, {
    {'\\', '\\'}, {'iden', 'abc'}, {'\\', '\\'}
})
test_scan([["" ""]], nil, nil, {
    {'string', ''}, {'string', ''}
})
test_scan([["abc" "def\\"]], nil, nil, {
    {'string', 'abc'}, {'string', 'def\\\\'}
})
test_scan([["abc\\" "def"]], nil, nil, {
    {'string', 'abc\\\\'}, {'string', 'def'}
})
test_scan([["abc\\\" "]], nil, nil, {
    {'string', 'abc\\\\\\" '}
})

local function test_roundtrip(str)
    test_scan(str, {}, {string=false}, {{'string', str}})
end

test_roundtrip [["hello\\"]]
test_roundtrip [["hello\"dolly"]]
test_roundtrip [['hello\'dolly']]
test_roundtrip [['']]
test_roundtrip [[""]]

test_scan('test(20 and a > b)', nil, nil, {
    {'iden', 'test'}, {'(', '('}, {'number', 20}, {'keyword', 'and'},
    {'iden', 'a'}, {'>', '>'}, {'iden', 'b'}, {')', ')'}
}, 'lua')
test_scan('10+2.3', nil, nil, {
    {'number', 10}, {'+', '+'}, {'number', 2.3}
}, 'lua')

local txt = [==[
-- comment
--[[
block
comment
]][[
hello dammit
]][[hello]]
]==]

test_scan(txt, {}, nil, {
    {'comment', '-- comment\n'},
    {'comment', '--[[\nblock\ncomment\n]]'},
    {'string', 'hello dammit\n'},
    {'string', 'hello'},
    {'space', '\n'}
}, 'lua')

local lines = [[
for k,v in pairs(t) do
    if type(k) == 'number' then
        print(v) -- array-like case
    else
        print(k,v)
    end -- if
end
]]

local ls = List()
for tp,val in lexer.lua(lines,{space=true,comments=true}) do
    assert(tp ~= 'space' and tp ~= 'comment')
    if tp == 'keyword' then ls:append(val) end
end
asserteq(ls,List{'for','in','do','if','then','else','end','end'})

txt = [[
// comment
/* a long
set of words */ // more
]]

test_scan(txt, {}, nil, {
    {'comment', '// comment\n'},
    {'comment', '/* a long\nset of words */'},
    {'space', ' '},
    {'comment', '// more\n'}
}, 'cpp')

test_scan([['' "" " \\" '\'' "'"]], nil, nil, {
    {'char', ''}, -- Char literals with no or more than one characters are not a lexing error.
    {'string', ''},
    {'string', ' \\\\'},
    {'char', "\\'"},
    {'string', "'"}
}, 'cpp')
