Skip to content

Commit

Permalink
Implement for in statement
Browse files Browse the repository at this point in the history
  • Loading branch information
edubart committed Sep 9, 2020
1 parent 7ad345e commit 54ad63f
Show file tree
Hide file tree
Showing 26 changed files with 231 additions and 118 deletions.
11 changes: 11 additions & 0 deletions docs/pages/libraries.md
Expand Up @@ -39,6 +39,17 @@ Basic library contains common functions.
| `_VERSION`{:.language-nelua} | A string of Nelua version. |
{: .table.table-bordered.table-striped.table-sm}

## iterators

Iterators library contains iterator related functions.

| Variable Name | Description |
|---------------|------|
| `ipairs`{:.language-nelua} | Work with vector, sequence, span and array. |
| `pairs`{:.language-nelua} | Alias to ipairs |
| `next`{:.language-nelua} | Work with vector, sequence, span and array. |
{: .table.table-bordered.table-striped.table-sm}

## filestream

Filestream library contains filestream object, mainly used for `io` library.
Expand Down
13 changes: 1 addition & 12 deletions lib/basic.nelua
Expand Up @@ -58,18 +58,6 @@ global function assert(cond: auto, msg: auto) <inline>
end

--[[
global function ipairs()
error('not implemented yet')
end
global function next()
error('not implemented yet')
end
global function pairs()
error('not implemented yet')
end
global function load()
error('not implemented yet')
end
Expand Down Expand Up @@ -119,6 +107,7 @@ global function select()
end
]]

-- pairs/ipairs/next is implemented in iterators.nelua
-- tostring/tonumber is implemented in stringview.nelua/string.nelua
-- type is implemented in traits.nelua
-- require is implemented by the nelua compiler
Expand Down
48 changes: 48 additions & 0 deletions lib/iterators.nelua
@@ -0,0 +1,48 @@
-- Include this file to get the globals "pairs", "ipairs" and "next",
-- to be used with when iterating with "for in".

-- Concept used to pass containers by reference.
local list_reference_concept = #[concept(function(x)
local reftype
local containertype
if x.type.is_pointer then
reftype = x.type
containertype = reftype.subtype
elseif x.type.is_span or x.type.is_sequence then
reftype = x.type
containertype = reftype
else
containertype = x.type
reftype = types.PointerType(containertype)
end
if containertype.is_contiguous then
return reftype
end
end)]#

-- Macro that implements the next iterator for lists.
## local function impl_ipairs_next(listtype)
index = index + 1
if index >= (#list + #[listtype.is_oneindexing and 1 or 0]#) then
return false, 0, #[listtype.subtype]#()
end
return true, index, list[index]
## end

-- Use with "for in" to iterate lists.
global function ipairs(list: list_reference_concept) <inline>
## local listvaltype = list.type:implict_deref_type()
local function ipairs_next(list: #[list.type]#, index: integer) <inline>
## impl_ipairs_next(listvaltype)
end
return ipairs_next, list, #[listvaltype.is_oneindexing and 0 or -1]#
end

-- Get the next element from a container.
global function next(list: list_reference_concept,
index: #[optional_concept(integer)]#)
## impl_ipairs_next(list.type:implict_deref_type())
end

-- at the moment pairs only works like ipairs
global pairs: auto = ipairs
2 changes: 2 additions & 0 deletions lib/sequence.nelua
Expand Up @@ -9,6 +9,7 @@
-- thus by default there is no need to manually reset the sequence.

require 'memory'
require 'iterators'

## local make_generic_sequence = generalize(function(T, Allocator)
## static_assert(traits.is_type(T), "invalid type '%s'", T)
Expand All @@ -33,6 +34,7 @@ require 'memory'
local sequenceT = sequenceT.value
sequenceT.is_contiguous = true
sequenceT.is_sequence = true
sequenceT.is_oneindexing = true -- used in 'ipairs'
sequenceT.subtype = T
sequenceT.choose_braces_type = function(nodes) return types.ArrayType(T, #nodes) end
]]
Expand Down
1 change: 1 addition & 0 deletions lib/vector.nelua
Expand Up @@ -8,6 +8,7 @@
-- thus by default there is no need to manually reset the vector.

require 'memory'
require 'iterators'

## local make_generic_vector = generalize(function(T, Allocator)
## static_assert(traits.is_type(T), "invalid type '%s'", T)
Expand Down
114 changes: 66 additions & 48 deletions nelua/analyzer.lua
Expand Up @@ -177,10 +177,7 @@ local function visitor_convert(context, parent, parentindex, vartype, valnode, v
objsym = objtype.symbol
assert(objsym)
local n = context.parser.astbuilder.aster
local idnode = n.Id{objsym.name}
local pattr = Attr{foreignsymbol=objsym}
idnode.attr:merge(pattr)
idnode.pattr = pattr
local idnode = n.Id{objsym.name, pattr={forcesymbol=objsym}}
local newvalnode = n.Call{{valnode}, n.DotIndex{mtname, idnode}}
newvalnode.src = valnode.src
newvalnode.pos = valnode.pos
Expand Down Expand Up @@ -442,13 +439,13 @@ end
function visitors.Id(context, node)
local name = node[1]
local symbol
if not node.attr.foreignsymbol then
if not node.attr.forcesymbol then
symbol = context.scope.symbols[name]
if not symbol then
node:raisef("undeclared symbol '%s'", name)
end
else
symbol = node.attr.foreignsymbol
symbol = node.attr.forcesymbol
end
symbol:link_node(node)
node.done = symbol
Expand Down Expand Up @@ -1413,40 +1410,6 @@ function visitors.Repeat(context, node)
context:pop_scope()
end

function visitors.ForIn(context, node)
local _, inexpnodes, blocknode = node[1], node[2], node[3]
assert(#inexpnodes > 0)
if #inexpnodes > 3 then
node:raisef("`in` statement can have at most 3 arguments")
end
local infuncnode = inexpnodes[1]
local infunctype = infuncnode.attr.type
if infunctype and not (infunctype.is_any or infunctype.is_procedure) then
node:raisef("first argument of `in` statement must be a function, but got type '%s'",
infunctype)
end
context:traverse_nodes(inexpnodes)

repeat
local scope = context:push_forked_cleaned_scope('loop', node)
--[[
if itvarnodes then
for i,itvarnode in ipairs(itvarnodes) do
local itsymbol = context:traverse_node(itvarnode)
if infunctype and infunctype.is_procedure then
local fittype = infunctype:get_return_type(i)
itsymbol:add_possible_type(fittype)
end
end
end
]]
context:traverse_node(blocknode)

local resolutions_count = scope:resolve()
context:pop_scope()
until resolutions_count == 0
end

function visitors.ForNum(context, node)
local itvarnode, begvalnode, compop, endvalnode, stepvalnode, blocknode =
node[1], node[2], node[3], node[4], node[5], node[6]
Expand Down Expand Up @@ -1550,6 +1513,67 @@ function visitors.ForNum(context, node)
end
end

function visitors.ForIn(context, node)
local itvarnodes, inexpnodes, blocknode = node[1], node[2], node[3]
assert(#itvarnodes > 0)
assert(#inexpnodes > 0)
if #inexpnodes > 3 then
node:raisef("`in` statement can have at most 3 arguments")
end

if context.generator == 'lua' then -- lua backend
context:traverse_nodes(inexpnodes)
repeat
local scope = context:push_forked_cleaned_scope('loop', node)
context:traverse_node(blocknode)
local resolutions_count = scope:resolve()
context:pop_scope()
until resolutions_count == 0
else -- on other backends must implement using while loops
local n = context.parser.astbuilder.aster

-- build extra nodes for the extra iterating values
local itvardeclnodes = {}
local itvaridnodes = {}
for i=1,#itvarnodes-1 do
local itvarnode = itvarnodes[i+1]
itvardeclnodes[i] = itvarnode
itvaridnodes[i] = n.Id{itvarnode[1], pattr={noinit=true}}
end

-- replace the for in node with a while loop
local newnode = n.Do{n.Block{{
n.VarDecl{'local', {
n.IdDecl{'__fornext'},
n.IdDecl{'__forstate'},
n.IdDecl{'__forit'}
},
inexpnodes
},
n.While{n.Boolean{true}, n.Block{{
n.VarDecl{'local', tabler.insertvalues({
n.IdDecl{'__forcont', pattr={noinit=true}}
}, itvardeclnodes)},
n.Assign{
tabler.insertvalues({
n.Id{'__forcont'},
n.Id{'__forit'}
}, itvaridnodes), {
n.Call{{n.Id{'__forstate'}, n.Id{'__forit'}}, n.Id{'__fornext'}}
}
},
n.If{{{n.UnaryOp{'not', n.Id{'__forcont'}}, n.Block{{
n.Break{}
}}}}},
n.VarDecl{'local', {itvarnodes[1]}, {n.Id{'__forit'}}},
n.Do{blocknode}
}}}
}}}
node:transform(newnode)
context:traverse_node(newnode)
end
end

function visitors.Break(context, node)
if not context.scope:get_parent_of_kind('loop') then
node:raisef("`break` statement is not inside a loop")
Expand Down Expand Up @@ -2127,10 +2151,7 @@ local function override_unary_op(context, node, opname, objnode, objtype)
local n = context.parser.astbuilder.aster
local objsym = objtype.symbol
assert(objsym)
local idnode = n.Id{objsym.name}
local pattr = Attr{foreignsymbol=objsym}
idnode.attr:merge(pattr)
idnode.pattr = pattr
local idnode = n.Id{objsym.name, pattr={forcesymbol=objsym}}
local newnode = n.Call{{objnode}, n.DotIndex{mtname, idnode}}
node:transform(newnode)
context:traverse_node(node)
Expand Down Expand Up @@ -2227,10 +2248,7 @@ local function override_binary_op(context, node, opname, lnode, rnode, ltype, rt
local n = context.parser.astbuilder.aster
local objsym = objtype.symbol
assert(objsym)
local idnode = n.Id{objsym.name}
local pattr = Attr{foreignsymbol=objsym}
idnode.attr:merge(pattr)
idnode.pattr = pattr
local idnode = n.Id{objsym.name, pattr={forcesymbol=objsym}}
local newnode = n.Call{{lnode, rnode}, n.DotIndex{mtname, idnode}}
if neg then
newnode = n.UnaryOp{'not', newnode}
Expand Down
4 changes: 3 additions & 1 deletion nelua/analyzercontext.lua
Expand Up @@ -8,7 +8,7 @@ local VisitorContext = require 'nelua.visitorcontext'

local AnalyzerContext = class(VisitorContext)

function AnalyzerContext:_init(visitors, parser, ast)
function AnalyzerContext:_init(visitors, parser, ast, generator)
VisitorContext._init(self, visitors)
self.parser = parser
self.rootscope = Scope(self, 'root', ast)
Expand All @@ -25,6 +25,8 @@ function AnalyzerContext:_init(visitors, parser, ast)
self.after_analyze = {}
self.after_inferences = {}
self.unresolvedcount = 0
assert(generator)
self.generator = generator
end

function AnalyzerContext:push_pragmas()
Expand Down
20 changes: 8 additions & 12 deletions nelua/astbuilder.lua
Expand Up @@ -36,23 +36,16 @@ function ASTBuilder:create_value(val, srcnode)
node = val
elseif traits.is_type(val) then
local typedefs = require 'nelua.typedefs'
node = aster.Type{'auto'}
-- inject persistent parsed type
local pattr = Attr({
node = aster.Type{'auto', pattr={
type = typedefs.primtypes.type,
value = val
})
node.attr:merge(pattr)
node.pattr = pattr
}}
elseif traits.is_string(val) then
node = aster.String{val}
elseif traits.is_symbol(val) then
node = aster.Id{val.name}
local pattr = Attr({
foreignsymbol = val
})
node.attr:merge(pattr)
node.pattr = pattr
node = aster.Id{val.name, pattr={
forcesymbol = val
}}
elseif bn.isnumeric(val) then
local num = bn.parse(val)
local neg = false
Expand Down Expand Up @@ -97,6 +90,9 @@ function ASTBuilder:register(tag, shape)
for k,v in iters.spairs(params) do
node[k] = v
end
if params.pattr then
node.attr:merge(params.pattr)
end
return node
end
return klass
Expand Down
2 changes: 1 addition & 1 deletion nelua/astdefs.lua
Expand Up @@ -184,7 +184,7 @@ astbuilder:register('ForNum', {
ntypes.Block, -- block
})
astbuilder:register('ForIn', {
stypes.array_of(ntypes.IdDecl):is_optional(), -- iteration vars
stypes.array_of(ntypes.IdDecl), -- iteration vars
stypes.array_of(ntypes.Node), -- in exprlist
ntypes.Block -- block
})
Expand Down
4 changes: 3 additions & 1 deletion nelua/astnode.lua
Expand Up @@ -99,7 +99,9 @@ function ASTNode:format_message(category, message, ...)
message = stringer.pformat(message, ...)
if self.src and self.pos then
message = errorer.get_pretty_source_pos_errmsg(self.src, self.pos, message, category)
end
else --luacov:disable
message = category .. ': ' .. message .. '\n'
end --luacov:enable
return message
end

Expand Down
2 changes: 1 addition & 1 deletion nelua/builtins.lua
Expand Up @@ -13,7 +13,7 @@ function builtins.require(context, node)

local justloaded = false
if not attr.loadedast then
local canloadatruntime = config.generator == 'lua'
local canloadatruntime = context.generator == 'lua'
local argnode = node[1][1]
if not (argnode and
argnode.attr.type and argnode.attr.type.is_stringview and
Expand Down
17 changes: 4 additions & 13 deletions nelua/cgenerator.lua
Expand Up @@ -1095,19 +1095,10 @@ function visitors.ForNum(context, node, emitter)
context:pop_scope()
end

--[[
function visitors.ForIn(_, node, emitter)
local itvarnodes, inexpnodes, blocknode = node:args()
emitter:add_indent_ln("{")
emitter:inc_indent()
--visit_assignments(context, emitter, itvarnodes, inexpnodes, true)
emitter:add_indent("while(true) {")
emitter:add(blocknode)
emitter:add_indent_ln("}")
emitter:dec_indent()
emitter:add_indent_ln("}")
end
]]
function visitors.ForIn() --luacov:disable
-- this should never happen
error('impossible')
end --luacov:enable

function visitors.Break(context, _, emitter)
destroy_upscopes_variables(context, emitter, 'loop')
Expand Down
2 changes: 1 addition & 1 deletion nelua/configer.lua
Expand Up @@ -106,7 +106,7 @@ local function create_parser(args)
:count("*"):convert(convert_param, tabler.copy(defconfig.define or {}))
argparser:option('-P --pragma', 'Set initial compiler pragma')
:count("*"):convert(convert_param, tabler.copy(defconfig.pragma or {}))
argparser:option('-g --generator', "Code generator to use (lua/c)", defconfig.generator)
argparser:option('-g --generator', "Code generator backend to use (lua/c)", defconfig.generator)
argparser:option('-p --path', "Set module search path", defconfig.path)
argparser:option('-L --add-path', "Add module search path", tabler.copy(defconfig.add_path or {}))
:count("*"):convert(convert_add_path)
Expand Down

0 comments on commit 54ad63f

Please sign in to comment.