<commit_before>"use strict"

module.exports = vectorizeText
module.exports.processPixels = processPixels

var surfaceNets = require('surface-nets')
var ndarray = require('ndarray')
var simplify = require('simplify-planar-graph')
var cleanPSLG = require('clean-pslg')
var cdt2d = require('cdt2d')
var toPolygonCrappy = require('planar-graph-to-polyline')

function transformPositions(positions, options, size) {
  var align = options.textAlign || "start"
  var baseline = options.textBaseline || "alphabetic"

  var lo = [1<<30, 1<<30]
  var hi = [0,0]
  var n = positions.length
  for(var i=0; i<n; ++i) {
    var p = positions[i]
    for(var j=0; j<2; ++j) {
      lo[j] = Math.min(lo[j], p[j])|0
      hi[j] = Math.max(hi[j], p[j])|0
    }
  }

  var xShift = 0
  switch(align) {
    case "center":
      xShift = -0.5 * (lo[0] + hi[0])
    break

    case "right":
    case "end":
      xShift = -hi[0]
    break

    case "left":
    case "start":
      xShift = -lo[0]
    break

    default:
      throw new Error("vectorize-text: Unrecognized textAlign: '" + align + "'")
  }

  var yShift = 0
  switch(baseline) {
    case "hanging":
    case "top":
      yShift = -lo[1]
    break

    case "middle":
      yShift = -0.5 * (lo[1] + hi[1])
    break

    case "alphabetic":
    case "ideographic":
      yShift = -3 * size
    break

    case "bottom":
      yShift = -hi[1]
    break

    default:
      throw new Error("vectorize-text: Unrecoginized textBaseline: '" + baseline + "'")
  }

  var scale = 1.0 / size
  if("lineHeight" in options) {
    scale *= +options.lineHeight
  } else if("width" in options) {
    scale = options.width / (hi[0] - lo[0])
  } else if("height" in options) {
    scale = options.height / (hi[1] - lo[1])
  }

  return positions.map(function(p) {
    return [ scale * (p[0] + xShift), scale * (p[1] + yShift) ]
  })
}

function getPixels(canvas, context, str, size) {
  var width = Math.ceil(context.measureText(str).width + 2*size)|0
  if(width > 8192) {
    throw new Error("vectorize-text: String too long (sorry, this will get fixed later)")
  }
  var height = 3 * size
  if(canvas.height < height) {
    canvas.height = height
  }

  context.fillStyle = "#000"
  context.fillRect(0, 0, canvas.width, canvas.height)

  context.fillStyle = "#fff"
  context.fillText(str, size, 2*size)

  //Cut pixels from image
  var pixelData = context.getImageData(0, 0, width, height)
  var pixels = ndarray(pixelData.data, [height, width, 4])

  return pixels.pick(-1,-1,0).transpose(1,0)
}

function getContour(pixels, doSimplify) {
  var contour = surfaceNets(pixels, 128)
  if(doSimplify) {
    return simplify(contour.cells, contour.positions, 0.25)
  }
  return {
    edges: contour.cells,
    positions: contour.positions
  }
}

function processPixelsImpl(pixels, options, size, simplify) {
  //Extract contour
  var contour = getContour(pixels, simplify)

  //Apply warp to positions
  var positions = transformPositions(contour.positions, options, size)
  var edges     = contour.edges
  var flip = "ccw" === options.orientation

  //Clean up the PSLG, resolve self intersections, etc.
  cleanPSLG(positions, edges)

  //If triangulate flag passed, triangulate the result
  if(options.polygons || options.polygon || options.polyline) {
    var result = toPolygonCrappy(edges, positions)
    var nresult = new Array(result.length)
    for(var i=0; i<result.length; ++i) {
      var loops = result[i]
      var nloops = new Array(loops.length)
      for(var j=0; j<loops.length; ++j) {
        var loop = loops[j]
        var nloop = new Array(loop.length)
        for(var k=0; k<loop.length; ++k) {
          nloop[k] = positions[loop[k]].slice()
        }
        if(flip) {
          nloop.reverse()
        }
        nloops[j] = nloop
      }
      nresult[i] = nloops
    }
    console.log(nresult)
    return nresult
  } else if(options.triangles || options.triangulate || options.triangle) {
    return {
      cells: cdt2d(positions, edges, {
        delaunay: false,
        exterior: false,
        interior: true
      }),
      positions: positions
    }
  } else {
    return {
      edges:     edges,
      positions: positions
    }
  }
}

function processPixels(pixels, options, size) {
  try {
    return processPixelsImpl(pixels, options, size, true)
  } catch(e) {}
  try {
    return processPixelsImpl(pixels, options, size, false)
  } catch(e) {}
  if(options.polygons || options.polyline || options.polygon) {
    return []
  }
  if(options.triangles || options.triangulate || options.triangle) {
    return {
      cells: [],
      positions: []
    }
  }
  return {
    edges: [],
    positions: []
  }
}

function vectorizeText(str, canvas, context, options) {
  var size = options.size || 64
  var family = options.font || "normal"

  context.font = size + "px " + family
  context.textAlign = "start"
  context.textBaseline = "alphabetic"
  context.direction = "ltr"

  var pixels = getPixels(canvas, context, str, size)

  return processPixels(pixels, options, size)
}
<commit_msg>accidental console.log<commit_after>"use strict"

module.exports = vectorizeText
module.exports.processPixels = processPixels

var surfaceNets = require('surface-nets')
var ndarray = require('ndarray')
var simplify = require('simplify-planar-graph')
var cleanPSLG = require('clean-pslg')
var cdt2d = require('cdt2d')
var toPolygonCrappy = require('planar-graph-to-polyline')

function transformPositions(positions, options, size) {
  var align = options.textAlign || "start"
  var baseline = options.textBaseline || "alphabetic"

  var lo = [1<<30, 1<<30]
  var hi = [0,0]
  var n = positions.length
  for(var i=0; i<n; ++i) {
    var p = positions[i]
    for(var j=0; j<2; ++j) {
      lo[j] = Math.min(lo[j], p[j])|0
      hi[j] = Math.max(hi[j], p[j])|0
    }
  }

  var xShift = 0
  switch(align) {
    case "center":
      xShift = -0.5 * (lo[0] + hi[0])
    break

    case "right":
    case "end":
      xShift = -hi[0]
    break

    case "left":
    case "start":
      xShift = -lo[0]
    break

    default:
      throw new Error("vectorize-text: Unrecognized textAlign: '" + align + "'")
  }

  var yShift = 0
  switch(baseline) {
    case "hanging":
    case "top":
      yShift = -lo[1]
    break

    case "middle":
      yShift = -0.5 * (lo[1] + hi[1])
    break

    case "alphabetic":
    case "ideographic":
      yShift = -3 * size
    break

    case "bottom":
      yShift = -hi[1]
    break

    default:
      throw new Error("vectorize-text: Unrecoginized textBaseline: '" + baseline + "'")
  }

  var scale = 1.0 / size
  if("lineHeight" in options) {
    scale *= +options.lineHeight
  } else if("width" in options) {
    scale = options.width / (hi[0] - lo[0])
  } else if("height" in options) {
    scale = options.height / (hi[1] - lo[1])
  }

  return positions.map(function(p) {
    return [ scale * (p[0] + xShift), scale * (p[1] + yShift) ]
  })
}

function getPixels(canvas, context, str, size) {
  var width = Math.ceil(context.measureText(str).width + 2*size)|0
  if(width > 8192) {
    throw new Error("vectorize-text: String too long (sorry, this will get fixed later)")
  }
  var height = 3 * size
  if(canvas.height < height) {
    canvas.height = height
  }

  context.fillStyle = "#000"
  context.fillRect(0, 0, canvas.width, canvas.height)

  context.fillStyle = "#fff"
  context.fillText(str, size, 2*size)

  //Cut pixels from image
  var pixelData = context.getImageData(0, 0, width, height)
  var pixels = ndarray(pixelData.data, [height, width, 4])

  return pixels.pick(-1,-1,0).transpose(1,0)
}

function getContour(pixels, doSimplify) {
  var contour = surfaceNets(pixels, 128)
  if(doSimplify) {
    return simplify(contour.cells, contour.positions, 0.25)
  }
  return {
    edges: contour.cells,
    positions: contour.positions
  }
}

function processPixelsImpl(pixels, options, size, simplify) {
  //Extract contour
  var contour = getContour(pixels, simplify)

  //Apply warp to positions
  var positions = transformPositions(contour.positions, options, size)
  var edges     = contour.edges
  var flip = "ccw" === options.orientation

  //Clean up the PSLG, resolve self intersections, etc.
  cleanPSLG(positions, edges)

  //If triangulate flag passed, triangulate the result
  if(options.polygons || options.polygon || options.polyline) {
    var result = toPolygonCrappy(edges, positions)
    var nresult = new Array(result.length)
    for(var i=0; i<result.length; ++i) {
      var loops = result[i]
      var nloops = new Array(loops.length)
      for(var j=0; j<loops.length; ++j) {
        var loop = loops[j]
        var nloop = new Array(loop.length)
        for(var k=0; k<loop.length; ++k) {
          nloop[k] = positions[loop[k]].slice()
        }
        if(flip) {
          nloop.reverse()
        }
        nloops[j] = nloop
      }
      nresult[i] = nloops
    }
    return nresult
  } else if(options.triangles || options.triangulate || options.triangle) {
    return {
      cells: cdt2d(positions, edges, {
        delaunay: false,
        exterior: false,
        interior: true
      }),
      positions: positions
    }
  } else {
    return {
      edges:     edges,
      positions: positions
    }
  }
}

function processPixels(pixels, options, size) {
  try {
    return processPixelsImpl(pixels, options, size, true)
  } catch(e) {}
  try {
    return processPixelsImpl(pixels, options, size, false)
  } catch(e) {}
  if(options.polygons || options.polyline || options.polygon) {
    return []
  }
  if(options.triangles || options.triangulate || options.triangle) {
    return {
      cells: [],
      positions: []
    }
  }
  return {
    edges: [],
    positions: []
  }
}

function vectorizeText(str, canvas, context, options) {
  var size = options.size || 64
  var family = options.font || "normal"

  context.font = size + "px " + family
  context.textAlign = "start"
  context.textBaseline = "alphabetic"
  context.direction = "ltr"

  var pixels = getPixels(canvas, context, str, size)

  return processPixels(pixels, options, size)
}
<|endoftext|>


<commit_before>const ServiceConstruction = require('service_construction');

var mod = {};
module.exports = mod;

mod.loop = function() {
    const flags = _.filter(Game.flags, o => FlagUtil.remoteMining.examine(o));
    for(const flag of flags) {
        //Extract flag data
        const destinedTarget = flag.name;
        let assignedRoom = flag.memory.assignedRoom;
        if(assignedRoom === undefined) {
            //Find suitable room to do this task
            //NOTE: Currently we only calculate range
            let range = Infinity;
            let selectedRoomName;
            for(const roomName in Game.rooms) {
                const room = Game.rooms[roomName];
                const roomType = room.memory.roomType;
                if(roomType && roomType === C.OWNED_ROOM && room.controller.level>=2) {
                    const r = Game.map.getRoomLinearDistance(flag.pos.roomName, roomName);
                    if(r < range) {
                        range = r;
                        selectedRoomName = roomName;
                    }
                }
            }
            assignedRoom = selectedRoomName;
            Logger.info(`Detect new remote mining flag => ${flag.name} and assigned to [${assignedRoom}]`);
        }
        //Do we have a assignedRoom now?
        if(assignedRoom) {
            //Write back to memory
            flag.memory.assignedRoom = assignedRoom;

            //====== Builders on demand ======
            //Loop construction
            ServiceConstruction.loopRemoteMining(flag);
            //Is there sites need to be build or road need to be repair?
            if(flag.room) {
                const sites = flag.room.cachedFind(FIND_CONSTRUCTION_SITES);
                const lastRepairTick = flag.room.memory.lastRepairTick || 0;
                if(sites.length > 0 || (Game.time-lastRepairTick)>1500) {
                    //Find builder and assign destinedTarget to sent builder outside when it is available
                    const builders = _.filter(Game.rooms[assignedRoom].cachedCreeps(), c => c.memory.role===C.BUILDER && c.memory.destinedTarget===undefined);
                    if(builders.length>0) {
                        const builder = builders[0];
                        builder.memory.destinedTarget=destinedTarget;
                        Logger.info(`Sending ${builder.name} to ${destinedTarget}`);
                    }
                } else {
                    //Find builder which is assigned to current flag, unassigned it
                    const builders = _.filter(Game.rooms[assignedRoom].cachedCreeps(), c => c.memory.role===C.BUILDER && c.memory.destinedTarget===destinedTarget);
                    if(builders.length>0) {
                        const builder = builders[0];
                        delete builder.memory.destinedTarget;
                        Logger.info(`Sending ${builder.name} back from ${destinedTarget}`);
                    }
                }
            }

            //====== Guardians on demand ======
            if(flag.room) {
                if(flag.room.memory.underAttack) {
                    //Find guardians and assign destinedTarget to sent guardians outside when it is available
                    const guardians = _.filter(Game.rooms[assignedRoom].cachedCreeps(), c => c.memory.role===C.GUARDIAN && c.memory.destinedTarget===undefined);
                    _.forEach(guardians, guardian => {
                        guardian.memory.destinedTarget = destinedTarget;
                        Logger.info(`Sending ${guardian.name} to ${destinedTarget}`);
                    });
                } else {
                    //Find guardians which is assigned to current flag, unassigned it
                    const guardians = _.filter(Game.rooms[assignedRoom].cachedCreeps(), c => c.memory.role===C.GUARDIAN && c.memory.destinedTarget===destinedTarget);
                    _.forEach(guardians, guardian => {
                        delete guardian.memory.destinedTarget;
                        Logger.info(`Sending ${guardian.name} back from ${destinedTarget}`);
                    });
                }
            }

            //Enqueue remote-mining task related creeps
            this.queueCreeps(assignedRoom, destinedTarget);
        }
    }
};

mod.queueCreeps = function(roomName, destinedTarget) {
    //=== Role count ===
    const roomCreeps = _.filter(Game.creeps, function(creep) { return creep.memory.destinedTarget === destinedTarget; });
    let cnt = {
        total: 0,
    };
    _.forEach(Role, o => cnt[o.roleName] = 0); //Init cnt
    //All role is required
    for(let creep of roomCreeps) {
        const role = creep.memory.role;
        if(creep.spawning || Setup[role] && creep.ticksToLive >= Setup[role].prespawn) {
            cnt[role]++;
            cnt.total++;
        }
    }
    //Count role in queue as well
    const queueRoom = Game.rooms[roomName];
    const queue = queueRoom.queue.extern;
    for(const setupArr of queue) {
        const setupName = setupArr[0];
        const extraMemory = setupArr[1];
        if(extraMemory.destinedTarget === destinedTarget) {
            const role = setupName;
            cnt[role]++;
            cnt.total++;
        }
    }

    //Prepare objects needed
    const extraMemory = {
        destinedTarget: destinedTarget
    };
    const targetRoomName = Game.flags[destinedTarget].pos.roomName;
    const room = Game.rooms[targetRoomName];

    //Do we have vision of that room?
    if(room === undefined) {
        //No vision
        //Spawn a scout first
        if(cnt[C.SCOUT]===0) {
            queueRoom.queue.extern.push([C.SCOUT, extraMemory]);
            cnt[C.SCOUT]++;
        }
        return;
    }

    if(!queueRoom.storage) {
        //Can't afford spawn cost of claimer
        // sent remoteWorkers instead of a remote group
        if(room) {
            //=== Harvest all sources and spawn dedicated hauler ===
            let needWorker = room.sources.length*2 - cnt[C.REMOTE_WORKER];
            //Actually enqueue harvesters and haulers
            while(needWorker-- > 0) {
                queueRoom.queue.extern.push([C.REMOTE_WORKER, extraMemory]);
                cnt[C.REMOTE_WORKER]++;
            }
            //==== Recycle remoteHauler and remoteHarvester ====
            if(cnt[C.REMOTE_HARVESTER]>0 || cnt[C.REMOTE_HAULER]>0) {
                Logger.info(`Recycling remoteHarvester and remoteHauler of ${destinedTarget} due to lack of energy in storage`);
                _.forEach(roomCreeps, c => {
                    if(c.memory.role===C.REMOTE_HARVESTER || c.memory.role===C.REMOTE_HAULER) {
                        c.memory.role = C.RECYCLER;
                    }
                });
            }
        }

        return;
    }

    //=== Keep room reserved ===
    const controller = room.controller;
    if(controller && controller.canTouch) {
        const reservation = controller.reservation;
        if(reservation && reservation.username===C.USERNAME && reservation.ticksToEnd>1000) {
            //No need to spawn reserver
        } else if(cnt[C.RESERVER]===0) {
            queueRoom.queue.extern.push([C.RESERVER, extraMemory]);
            cnt[C.RESERVER]++;
        }
    }

    //=== Harvest all sources and spawn dedicated hauler ===
    //That is one pair of harvester-hauler for the room
    const range = Util.Helper.calcRoomsRealDistance(queueRoom.name, room.name);
    const haulerLimit = range+1;
    let needHarvester = 1 - cnt[C.REMOTE_HARVESTER];
    let needHauler = haulerLimit - cnt[C.REMOTE_HAULER];
    if(needHarvester>0) {
        queueRoom.queue.extern.push([C.REMOTE_HARVESTER, extraMemory]);
        cnt[C.REMOTE_HARVESTER]++;
    }
    if(needHauler>0) {
        queueRoom.queue.extern.push([C.REMOTE_HAULER, extraMemory]);
        cnt[C.REMOTE_HAULER]++;
    }
};
<commit_msg>compute amount of remoteHauler by path length between sources and storage<commit_after>const ServiceConstruction = require('service_construction');

var mod = {};
module.exports = mod;

mod.loop = function() {
    const flags = _.filter(Game.flags, o => FlagUtil.remoteMining.examine(o));
    for(const flag of flags) {
        //Extract flag data
        const destinedTarget = flag.name;
        let assignedRoom = flag.memory.assignedRoom;
        if(assignedRoom === undefined) {
            //Find suitable room to do this task
            //NOTE: Currently we only calculate range
            let range = Infinity;
            let selectedRoomName;
            for(const roomName in Game.rooms) {
                const room = Game.rooms[roomName];
                const roomType = room.memory.roomType;
                if(roomType && roomType === C.OWNED_ROOM && room.controller.level>=2) {
                    const r = Game.map.getRoomLinearDistance(flag.pos.roomName, roomName);
                    if(r < range) {
                        range = r;
                        selectedRoomName = roomName;
                    }
                }
            }
            assignedRoom = selectedRoomName;
            //Write back to memory
            flag.memory.assignedRoom = assignedRoom;
            Logger.info(`Detect new remote mining flag => ${flag.name} and assigned to [${assignedRoom}]`);
        }
        //Do we have a assignedRoom now?
        if(assignedRoom) {
            //====== Builders on demand ======
            //Loop construction
            ServiceConstruction.loopRemoteMining(flag);
            //Is there sites need to be build or road need to be repair?
            if(flag.room) {
                const sites = flag.room.cachedFind(FIND_CONSTRUCTION_SITES);
                const lastRepairTick = flag.room.memory.lastRepairTick || 0;
                if(sites.length > 0 || (Game.time-lastRepairTick)>1500) {
                    //Find builder and assign destinedTarget to sent builder outside when it is available
                    const builders = _.filter(Game.rooms[assignedRoom].cachedCreeps(), c => c.memory.role===C.BUILDER && c.memory.destinedTarget===undefined);
                    if(builders.length>0) {
                        const builder = builders[0];
                        builder.memory.destinedTarget=destinedTarget;
                        Logger.info(`Sending ${builder.name} to ${destinedTarget}`);
                    }
                } else {
                    //Find builder which is assigned to current flag, unassigned it
                    const builders = _.filter(Game.rooms[assignedRoom].cachedCreeps(), c => c.memory.role===C.BUILDER && c.memory.destinedTarget===destinedTarget);
                    if(builders.length>0) {
                        const builder = builders[0];
                        delete builder.memory.destinedTarget;
                        Logger.info(`Sending ${builder.name} back from ${destinedTarget}`);
                    }
                }
            }

            //====== Guardians on demand ======
            if(flag.room) {
                if(flag.room.memory.underAttack) {
                    //Find guardians and assign destinedTarget to sent guardians outside when it is available
                    const guardians = _.filter(Game.rooms[assignedRoom].cachedCreeps(), c => c.memory.role===C.GUARDIAN && c.memory.destinedTarget===undefined);
                    _.forEach(guardians, guardian => {
                        guardian.memory.destinedTarget = destinedTarget;
                        Logger.info(`Sending ${guardian.name} to ${destinedTarget}`);
                    });
                } else {
                    //Find guardians which is assigned to current flag, unassigned it
                    const guardians = _.filter(Game.rooms[assignedRoom].cachedCreeps(), c => c.memory.role===C.GUARDIAN && c.memory.destinedTarget===destinedTarget);
                    _.forEach(guardians, guardian => {
                        delete guardian.memory.destinedTarget;
                        Logger.info(`Sending ${guardian.name} back from ${destinedTarget}`);
                    });
                }
            }

            //Enqueue remote-mining task related creeps
            this.queueCreeps(assignedRoom, destinedTarget);
        }
    }
};

mod.queueCreeps = function(roomName, destinedTarget) {
    //=== Role count ===
    const roomCreeps = _.filter(Game.creeps, function(creep) { return creep.memory.destinedTarget === destinedTarget; });
    let cnt = {
        total: 0,
    };
    _.forEach(Role, o => cnt[o.roleName] = 0); //Init cnt
    //All role is required
    for(let creep of roomCreeps) {
        const role = creep.memory.role;
        if(creep.spawning || Setup[role] && creep.ticksToLive >= Setup[role].prespawn) {
            cnt[role]++;
            cnt.total++;
        }
    }
    //Count role in queue as well
    const queueRoom = Game.rooms[roomName];
    const queue = queueRoom.queue.extern;
    for(const setupArr of queue) {
        const setupName = setupArr[0];
        const extraMemory = setupArr[1];
        if(extraMemory.destinedTarget === destinedTarget) {
            const role = setupName;
            cnt[role]++;
            cnt.total++;
        }
    }

    //Prepare objects needed
    const extraMemory = {
        destinedTarget: destinedTarget
    };
    const targetRoomName = Game.flags[destinedTarget].pos.roomName;
    const room = Game.rooms[targetRoomName];

    //Do we have vision of that room?
    if(room === undefined) {
        //No vision
        //Spawn a scout first
        if(cnt[C.SCOUT]===0) {
            queueRoom.queue.extern.push([C.SCOUT, extraMemory]);
            cnt[C.SCOUT]++;
        }
        return;
    }

    if(!queueRoom.storage) {
        //Can't afford spawn cost of claimer
        // sent remoteWorkers instead of a remote group
        if(room) {
            //=== Harvest all sources and spawn dedicated hauler ===
            let needWorker = room.sources.length*2 - cnt[C.REMOTE_WORKER];
            //Actually enqueue harvesters and haulers
            while(needWorker-- > 0) {
                queueRoom.queue.extern.push([C.REMOTE_WORKER, extraMemory]);
                cnt[C.REMOTE_WORKER]++;
            }
            //==== Recycle remoteHauler and remoteHarvester ====
            if(cnt[C.REMOTE_HARVESTER]>0 || cnt[C.REMOTE_HAULER]>0) {
                Logger.info(`Recycling remoteHarvester and remoteHauler of ${destinedTarget} due to lack of energy in storage`);
                _.forEach(roomCreeps, c => {
                    if(c.memory.role===C.REMOTE_HARVESTER || c.memory.role===C.REMOTE_HAULER) {
                        c.memory.role = C.RECYCLER;
                    }
                });
            }
        }

        return;
    }

    //=== Keep room reserved ===
    const controller = room.controller;
    if(controller && controller.canTouch) {
        const reservation = controller.reservation;
        if(reservation && reservation.username===C.USERNAME && reservation.ticksToEnd>1000) {
            //No need to spawn reserver
        } else if(cnt[C.RESERVER]===0) {
            queueRoom.queue.extern.push([C.RESERVER, extraMemory]);
            cnt[C.RESERVER]++;
        }
    }

    //====== Calculate path length between remote sources and home controller ======
    const flag = Game.flags[destinedTarget];
    let avgPathLength = flag.memory.avgPathLength;
    if(avgPathLength === undefined) {
        let length = 0;
        let goals = _.map(room.sources, function(source) {
            // We can't actually walk on sources-- set `range` to 1
            // so we path next to it.
            return { pos: source.pos, range: 1 };
        });
        const pathObj = PathFinder.search(queueRoom.storage.pos, goals);
        avgPathLength = pathObj.path.length;
        //Write back
        flag.memory.avgPathLength = avgPathLength;
    }

    //=== Harvest all sources and spawn dedicated hauler ===
    //That is one pair of harvester-hauler for the room
    const energyGenPerTick = 30; //hardcode for 15work*2harvest_power
    const energyHaulPerRound = 1600; //hardcode for 32carry
    const ticksPerRound = avgPathLength*2; //best situation: there are well maintained roads on the path, so move speed is 1 tile/tick
    const haulerLimit = parseInt((energyGenPerTick*ticksPerRound/energyHaulPerRound).toFixed());
    //Logger.debug(`[${queueRoom.name} <-> ${flag.room.name}] ${haulerLimit}`);
    let needHarvester = 1 - cnt[C.REMOTE_HARVESTER];
    let needHauler = haulerLimit - cnt[C.REMOTE_HAULER];
    if(needHarvester>0) {
        queueRoom.queue.extern.push([C.REMOTE_HARVESTER, extraMemory]);
        cnt[C.REMOTE_HARVESTER]++;
    }
    if(needHauler>0) {
        queueRoom.queue.extern.push([C.REMOTE_HAULER, extraMemory]);
        cnt[C.REMOTE_HAULER]++;
    }
};
<|endoftext|>


<commit_before>/* eslint-disable no-param-reassign*/
import TokenTranslator from "./token-translator.js";
import { normalizeOptions } from "./options.js";

const STATE = Symbol("espree's internal state");
const ESPRIMA_FINISH_NODE = Symbol("espree's esprimaFinishNode");

/**
 * Converts an Acorn comment to a Esprima comment.
 * @param {boolean} block True if it's a block comment, false if not.
 * @param {string} text The text of the comment.
 * @param {int} start The index at which the comment starts.
 * @param {int} end The index at which the comment ends.
 * @param {Location} startLoc The location at which the comment starts.
 * @param {Location} endLoc The location at which the comment ends.
 * @returns {Object} The comment object.
 * @private
 */
function convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, endLoc) {
    const comment = {
        type: block ? "Block" : "Line",
        value: text
    };

    if (typeof start === "number") {
        comment.start = start;
        comment.end = end;
        comment.range = [start, end];
    }

    if (typeof startLoc === "object") {
        comment.loc = {
            start: startLoc,
            end: endLoc
        };
    }

    return comment;
}

export default () => Parser => {
    const tokTypes = Object.assign({}, Parser.acorn.tokTypes);

    if (Parser.acornJsx) {
        Object.assign(tokTypes, Parser.acornJsx.tokTypes);
    }

    return class Espree extends Parser {
        constructor(opts, code) {
            if (typeof opts !== "object" || opts === null) {
                opts = {};
            }
            if (typeof code !== "string" && !(code instanceof String)) {
                code = String(code);
            }

            const options = normalizeOptions(opts);
            const ecmaFeatures = options.ecmaFeatures || {};
            const tokenTranslator =
                options.tokens === true
                    ? new TokenTranslator(tokTypes, code)
                    : null;

            // Initialize acorn parser.
            super({

                // do not use spread, because we don't want to pass any unknown options to acorn
                ecmaVersion: options.ecmaVersion,
                sourceType: options.sourceType,
                ranges: options.ranges,
                locations: options.locations,

                // Truthy value is true for backward compatibility.
                allowReturnOutsideFunction: Boolean(ecmaFeatures.globalReturn),

                // Collect tokens
                onToken: token => {
                    if (tokenTranslator) {

                        // Use `tokens`, `ecmaVersion`, and `jsxAttrValueToken` in the state.
                        tokenTranslator.onToken(token, this[STATE]);
                    }
                    if (token.type !== tokTypes.eof) {
                        this[STATE].lastToken = token;
                    }
                },

                // Collect comments
                onComment: (block, text, start, end, startLoc, endLoc) => {
                    if (this[STATE].comments) {
                        const comment = convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, endLoc);

                        this[STATE].comments.push(comment);
                    }
                }
            }, code);

            // Initialize internal state.
            this[STATE] = {
                tokens: tokenTranslator ? [] : null,
                comments: options.comment === true ? [] : null,
                impliedStrict: ecmaFeatures.impliedStrict === true && this.options.ecmaVersion >= 5,
                ecmaVersion: this.options.ecmaVersion,
                jsxAttrValueToken: false,
                lastToken: null,
                templateElements: []
            };
        }

        tokenize() {
            do {
                this.next();
            } while (this.type !== tokTypes.eof);

            // Consume the final eof token
            this.next();

            const extra = this[STATE];
            const tokens = extra.tokens;

            if (extra.comments) {
                tokens.comments = extra.comments;
            }

            return tokens;
        }

        finishNode(...args) {
            const result = super.finishNode(...args);

            return this[ESPRIMA_FINISH_NODE](result);
        }

        finishNodeAt(...args) {
            const result = super.finishNodeAt(...args);

            return this[ESPRIMA_FINISH_NODE](result);
        }

        parse() {
            const extra = this[STATE];
            const program = super.parse();

            program.sourceType = this.options.sourceType;

            if (extra.comments) {
                program.comments = extra.comments;
            }
            if (extra.tokens) {
                program.tokens = extra.tokens;
            }

            /*
             * Adjust opening and closing position of program to match Esprima.
             * Acorn always starts programs at range 0 whereas Esprima starts at the
             * first AST node's start (the only real difference is when there's leading
             * whitespace or leading comments). Acorn also counts trailing whitespace
             * as part of the program whereas Esprima only counts up to the last token.
             */
            if (program.body.length) {
                const [firstNode] = program.body;

                if (program.range) {
                    program.range[0] = firstNode.range[0];
                }
                if (program.loc) {
                    program.loc.start = firstNode.loc.start;
                }
                program.start = firstNode.start;
            }
            if (extra.lastToken) {
                if (program.range) {
                    program.range[1] = extra.lastToken.range[1];
                }
                if (program.loc) {
                    program.loc.end = extra.lastToken.loc.end;
                }
                program.end = extra.lastToken.end;
            }

            /*
             * https://github.com/eslint/espree/issues/349
             * Ensure that template elements have correct range information.
             * This is one location where Acorn produces a different value
             * for its start and end properties vs. the values present in the
             * range property. In order to avoid confusion, we set the start
             * and end properties to the values that are present in range.
             * This is done here, instead of in finishNode(), because Acorn
             * uses the values of start and end internally while parsing, making
             * it dangerous to change those values while parsing is ongoing.
             * By waiting until the end of parsing, we can safely change these
             * values without affect any other part of the process.
             */
            this[STATE].templateElements.forEach(templateElement => {
                const terminalDollarBraceL = this.input.slice(templateElement.end, templateElement.end + 2) === "${";

                templateElement.start--;
                templateElement.end += (terminalDollarBraceL ? 2 : 1);
            });

            return program;
        }

        parseTopLevel(node) {
            if (this[STATE].impliedStrict) {
                this.strict = true;
            }
            return super.parseTopLevel(node);
        }

        /**
         * Overwrites the default raise method to throw Esprima-style errors.
         * @param {int} pos The position of the error.
         * @param {string} message The error message.
         * @throws {SyntaxError} A syntax error.
         * @returns {void}
         */
        raise(pos, message) {
            const loc = Parser.acorn.getLineInfo(this.input, pos);
            const err = new SyntaxError(message);

            err.index = pos;
            err.lineNumber = loc.line;
            err.column = loc.column + 1; // acorn uses 0-based columns
            throw err;
        }

        /**
         * Overwrites the default raise method to throw Esprima-style errors.
         * @param {int} pos The position of the error.
         * @param {string} message The error message.
         * @throws {SyntaxError} A syntax error.
         * @returns {void}
         */
        raiseRecoverable(pos, message) {
            this.raise(pos, message);
        }

        /**
         * Overwrites the default unexpected method to throw Esprima-style errors.
         * @param {int} pos The position of the error.
         * @throws {SyntaxError} A syntax error.
         * @returns {void}
         */
        unexpected(pos) {
            let message = "Unexpected token";

            if (pos !== null && pos !== void 0) {
                this.pos = pos;

                if (this.options.locations) {
                    while (this.pos < this.lineStart) {
                        this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1;
                        --this.curLine;
                    }
                }

                this.nextToken();
            }

            if (this.end > this.start) {
                message += ` ${this.input.slice(this.start, this.end)}`;
            }

            this.raise(this.start, message);
        }

        /*
        * Esprima-FB represents JSX strings as tokens called "JSXText", but Acorn-JSX
        * uses regular tt.string without any distinction between this and regular JS
        * strings. As such, we intercept an attempt to read a JSX string and set a flag
        * on extra so that when tokens are converted, the next token will be switched
        * to JSXText via onToken.
        */
        jsx_readString(quote) { // eslint-disable-line camelcase
            const result = super.jsx_readString(quote);

            if (this.type === tokTypes.string) {
                this[STATE].jsxAttrValueToken = true;
            }
            return result;
        }

        /**
         * Performs last-minute Esprima-specific compatibility checks and fixes.
         * @param {ASTNode} result The node to check.
         * @returns {ASTNode} The finished node.
         */
        [ESPRIMA_FINISH_NODE](result) {

            // Acorn doesn't count the opening and closing backticks as part of templates
            // so we have to adjust ranges/locations appropriately.
            if (result.type === "TemplateElement") {

                // additional adjustment needed if ${ is the last token
                const terminalDollarBraceL = this.input.slice(result.end, result.end + 2) === "${";

                if (result.range) {
                    result.range[0]--;
                    result.range[1] += (terminalDollarBraceL ? 2 : 1);
                }

                if (result.loc) {
                    result.loc.start.column--;
                    result.loc.end.column += (terminalDollarBraceL ? 2 : 1);
                }

                // save template element references to fix start/end later
                this[STATE].templateElements.push(result);
            }

            if (result.type.includes("Function") && !result.generator) {
                result.generator = false;
            }

            return result;
        }
    };
};
<commit_msg>Chore: Refactor `TemplateElement` range fix (#489)<commit_after>/* eslint-disable no-param-reassign*/
import TokenTranslator from "./token-translator.js";
import { normalizeOptions } from "./options.js";

const STATE = Symbol("espree's internal state");
const ESPRIMA_FINISH_NODE = Symbol("espree's esprimaFinishNode");

/**
 * Converts an Acorn comment to a Esprima comment.
 * @param {boolean} block True if it's a block comment, false if not.
 * @param {string} text The text of the comment.
 * @param {int} start The index at which the comment starts.
 * @param {int} end The index at which the comment ends.
 * @param {Location} startLoc The location at which the comment starts.
 * @param {Location} endLoc The location at which the comment ends.
 * @returns {Object} The comment object.
 * @private
 */
function convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, endLoc) {
    const comment = {
        type: block ? "Block" : "Line",
        value: text
    };

    if (typeof start === "number") {
        comment.start = start;
        comment.end = end;
        comment.range = [start, end];
    }

    if (typeof startLoc === "object") {
        comment.loc = {
            start: startLoc,
            end: endLoc
        };
    }

    return comment;
}

export default () => Parser => {
    const tokTypes = Object.assign({}, Parser.acorn.tokTypes);

    if (Parser.acornJsx) {
        Object.assign(tokTypes, Parser.acornJsx.tokTypes);
    }

    return class Espree extends Parser {
        constructor(opts, code) {
            if (typeof opts !== "object" || opts === null) {
                opts = {};
            }
            if (typeof code !== "string" && !(code instanceof String)) {
                code = String(code);
            }

            const options = normalizeOptions(opts);
            const ecmaFeatures = options.ecmaFeatures || {};
            const tokenTranslator =
                options.tokens === true
                    ? new TokenTranslator(tokTypes, code)
                    : null;

            // Initialize acorn parser.
            super({

                // do not use spread, because we don't want to pass any unknown options to acorn
                ecmaVersion: options.ecmaVersion,
                sourceType: options.sourceType,
                ranges: options.ranges,
                locations: options.locations,

                // Truthy value is true for backward compatibility.
                allowReturnOutsideFunction: Boolean(ecmaFeatures.globalReturn),

                // Collect tokens
                onToken: token => {
                    if (tokenTranslator) {

                        // Use `tokens`, `ecmaVersion`, and `jsxAttrValueToken` in the state.
                        tokenTranslator.onToken(token, this[STATE]);
                    }
                    if (token.type !== tokTypes.eof) {
                        this[STATE].lastToken = token;
                    }
                },

                // Collect comments
                onComment: (block, text, start, end, startLoc, endLoc) => {
                    if (this[STATE].comments) {
                        const comment = convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, endLoc);

                        this[STATE].comments.push(comment);
                    }
                }
            }, code);

            // Initialize internal state.
            this[STATE] = {
                tokens: tokenTranslator ? [] : null,
                comments: options.comment === true ? [] : null,
                impliedStrict: ecmaFeatures.impliedStrict === true && this.options.ecmaVersion >= 5,
                ecmaVersion: this.options.ecmaVersion,
                jsxAttrValueToken: false,
                lastToken: null,
                templateElements: []
            };
        }

        tokenize() {
            do {
                this.next();
            } while (this.type !== tokTypes.eof);

            // Consume the final eof token
            this.next();

            const extra = this[STATE];
            const tokens = extra.tokens;

            if (extra.comments) {
                tokens.comments = extra.comments;
            }

            return tokens;
        }

        finishNode(...args) {
            const result = super.finishNode(...args);

            return this[ESPRIMA_FINISH_NODE](result);
        }

        finishNodeAt(...args) {
            const result = super.finishNodeAt(...args);

            return this[ESPRIMA_FINISH_NODE](result);
        }

        parse() {
            const extra = this[STATE];
            const program = super.parse();

            program.sourceType = this.options.sourceType;

            if (extra.comments) {
                program.comments = extra.comments;
            }
            if (extra.tokens) {
                program.tokens = extra.tokens;
            }

            /*
             * Adjust opening and closing position of program to match Esprima.
             * Acorn always starts programs at range 0 whereas Esprima starts at the
             * first AST node's start (the only real difference is when there's leading
             * whitespace or leading comments). Acorn also counts trailing whitespace
             * as part of the program whereas Esprima only counts up to the last token.
             */
            if (program.body.length) {
                const [firstNode] = program.body;

                if (program.range) {
                    program.range[0] = firstNode.range[0];
                }
                if (program.loc) {
                    program.loc.start = firstNode.loc.start;
                }
                program.start = firstNode.start;
            }
            if (extra.lastToken) {
                if (program.range) {
                    program.range[1] = extra.lastToken.range[1];
                }
                if (program.loc) {
                    program.loc.end = extra.lastToken.loc.end;
                }
                program.end = extra.lastToken.end;
            }

            /*
             * https://github.com/eslint/espree/issues/349
             * Ensure that template elements have correct range information.
             * This is one location where Acorn produces a different value
             * for its start and end properties vs. the values present in the
             * range property. In order to avoid confusion, we set the start
             * and end properties to the values that are present in range.
             * This is done here, instead of in finishNode(), because Acorn
             * uses the values of start and end internally while parsing, making
             * it dangerous to change those values while parsing is ongoing.
             * By waiting until the end of parsing, we can safely change these
             * values without affect any other part of the process.
             */
            this[STATE].templateElements.forEach(templateElement => {
                const startOffset = -1;
                const endOffset = templateElement.tail ? 1 : 2;

                templateElement.start += startOffset;
                templateElement.end += endOffset;

                if (templateElement.range) {
                    templateElement.range[0] += startOffset;
                    templateElement.range[1] += endOffset;
                }

                if (templateElement.loc) {
                    templateElement.loc.start.column += startOffset;
                    templateElement.loc.end.column += endOffset;
                }
            });

            return program;
        }

        parseTopLevel(node) {
            if (this[STATE].impliedStrict) {
                this.strict = true;
            }
            return super.parseTopLevel(node);
        }

        /**
         * Overwrites the default raise method to throw Esprima-style errors.
         * @param {int} pos The position of the error.
         * @param {string} message The error message.
         * @throws {SyntaxError} A syntax error.
         * @returns {void}
         */
        raise(pos, message) {
            const loc = Parser.acorn.getLineInfo(this.input, pos);
            const err = new SyntaxError(message);

            err.index = pos;
            err.lineNumber = loc.line;
            err.column = loc.column + 1; // acorn uses 0-based columns
            throw err;
        }

        /**
         * Overwrites the default raise method to throw Esprima-style errors.
         * @param {int} pos The position of the error.
         * @param {string} message The error message.
         * @throws {SyntaxError} A syntax error.
         * @returns {void}
         */
        raiseRecoverable(pos, message) {
            this.raise(pos, message);
        }

        /**
         * Overwrites the default unexpected method to throw Esprima-style errors.
         * @param {int} pos The position of the error.
         * @throws {SyntaxError} A syntax error.
         * @returns {void}
         */
        unexpected(pos) {
            let message = "Unexpected token";

            if (pos !== null && pos !== void 0) {
                this.pos = pos;

                if (this.options.locations) {
                    while (this.pos < this.lineStart) {
                        this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1;
                        --this.curLine;
                    }
                }

                this.nextToken();
            }

            if (this.end > this.start) {
                message += ` ${this.input.slice(this.start, this.end)}`;
            }

            this.raise(this.start, message);
        }

        /*
        * Esprima-FB represents JSX strings as tokens called "JSXText", but Acorn-JSX
        * uses regular tt.string without any distinction between this and regular JS
        * strings. As such, we intercept an attempt to read a JSX string and set a flag
        * on extra so that when tokens are converted, the next token will be switched
        * to JSXText via onToken.
        */
        jsx_readString(quote) { // eslint-disable-line camelcase
            const result = super.jsx_readString(quote);

            if (this.type === tokTypes.string) {
                this[STATE].jsxAttrValueToken = true;
            }
            return result;
        }

        /**
         * Performs last-minute Esprima-specific compatibility checks and fixes.
         * @param {ASTNode} result The node to check.
         * @returns {ASTNode} The finished node.
         */
        [ESPRIMA_FINISH_NODE](result) {

            // Acorn doesn't count the opening and closing backticks as part of templates
            // so we have to adjust ranges/locations appropriately.
            if (result.type === "TemplateElement") {

                // save template element references to fix start/end later
                this[STATE].templateElements.push(result);
            }

            if (result.type.includes("Function") && !result.generator) {
                result.generator = false;
            }

            return result;
        }
    };
};
<|endoftext|>


<commit_before>export const defaultChatSubject = 'Introduce yourself!';
export const rewardValue = {
  star: 200
};
import { Color } from './css';

export const clientVersion = '0.1.90';

export const charLimit = {
  chat: {
    subject: 200,
    message: 5000
  },
  comment: 10000,
  playlist: {
    title: 200,
    description: 5000
  },
  videoQuestion: {
    title: 2000,
    choice: 2000
  },
  subject: {
    title: 300,
    description: 5000
  },
  rewardComment: 5000,
  statusMsg: 500,
  url: {
    title: 300,
    description: 20000,
    url: 1000
  },
  video: {
    title: 200,
    description: 10000,
    url: 300
  }
};
export const profileThemes = {
  black: {
    color: Color.black(),
    cover: Color.black()
  },
  logoBlue: {
    color: Color.logoBlue(),
    cover: Color.logoBlue()
  },
  green: {
    color: Color.green(),
    cover: Color.green()
  },
  orange: {
    color: Color.orange(),
    cover: Color.orange()
  },
  pink: {
    color: Color.pink(),
    cover: Color.pink()
  },
  rose: {
    color: Color.rose(),
    cover: Color.rose()
  }
};
export const returnMaxStars = ({ difficulty }) => {
  let maxStars = 5;
  if (difficulty > 0) {
    maxStars = 10 * difficulty;
  }
  return maxStars;
};
<commit_msg>version up<commit_after>export const defaultChatSubject = 'Introduce yourself!';
export const rewardValue = {
  star: 200
};
import { Color } from './css';

export const clientVersion = '0.1.91';

export const charLimit = {
  chat: {
    subject: 200,
    message: 5000
  },
  comment: 10000,
  playlist: {
    title: 200,
    description: 5000
  },
  videoQuestion: {
    title: 2000,
    choice: 2000
  },
  subject: {
    title: 300,
    description: 5000
  },
  rewardComment: 5000,
  statusMsg: 500,
  url: {
    title: 300,
    description: 20000,
    url: 1000
  },
  video: {
    title: 200,
    description: 10000,
    url: 300
  }
};
export const profileThemes = {
  black: {
    color: Color.black(),
    cover: Color.black()
  },
  logoBlue: {
    color: Color.logoBlue(),
    cover: Color.logoBlue()
  },
  green: {
    color: Color.green(),
    cover: Color.green()
  },
  orange: {
    color: Color.orange(),
    cover: Color.orange()
  },
  pink: {
    color: Color.pink(),
    cover: Color.pink()
  },
  rose: {
    color: Color.rose(),
    cover: Color.rose()
  }
};
export const returnMaxStars = ({ difficulty }) => {
  let maxStars = 5;
  if (difficulty > 0) {
    maxStars = 10 * difficulty;
  }
  return maxStars;
};
<|endoftext|>


<commit_before>export * from './Header';
export * from './Footer';
export * from './ItemsList';
export * from './item';
export * from './Costper';
export * from './Button';
export * from './Card';
export * from './CardSection';
export * from './InputField';
export * from './Spinner';
<commit_msg>Remove footer reference from index.js<commit_after>export * from './Header';
export * from './ItemsList';
export * from './item';
export * from './Costper';
export * from './Button';
export * from './Card';
export * from './CardSection';
export * from './InputField';
export * from './Spinner';
<|endoftext|>


<commit_before>import _clone from 'lodash-es/clone';
import _map from 'lodash-es/map';
import _flattenDeep from 'lodash-es/flatten';
import { geoExtent, geoLineIntersection } from '../geo';
import { utilDisplayLabel } from '../util';
import { t } from '../util/locale';
import {
    validationIssue,
    validationIssueFix
} from '../core/validator';
import { osmNode } from '../osm';
import { actionAddMidpoint } from '../actions';
import { geoChooseEdge } from '../geo';

export function validationCrossingWays() {
    // Check if the edge going from n1 to n2 crosses (without a connection node)
    // any edge on way. Return the cross point if so.
    function findEdgeToWayCrossCoords(n1, n2, way, graph) {
        var crossCoords = [];
        var nodes = graph.childNodes(way);
        for (var j = 0; j < nodes.length - 1; j++) {
            var nA = nodes[j],
                nB = nodes[j + 1];
            if (nA.id === n1.id || nA.id === n2.id ||
                nB.id === n1.id || nB.id === n2.id) {
                // n1 or n2 is a connection node; skip
                continue;
            }

            var point = geoLineIntersection([n1.loc, n2.loc], [nA.loc, nB.loc]);
            if (point) crossCoords.push(point);
        }
        return crossCoords;
    }

    // returns the way or its parent relation, whichever has a useful feature type
    function getFeatureWithFeatureTypeTagsForWay(way, graph) {
        if (getFeatureTypeForTags(way.tags) === null) {
            // if the way doesn't match a feature type, check is parent relations
            var parentRels = graph.parentRelations(way);
            for (var i = 0; i < parentRels.length; i++) {
                var rel = parentRels[i];
                if (getFeatureTypeForTags(rel.tags) !== null) {
                    return rel;
                }
            }
        }
        return way;
    }

    function hasTag(tags, key) {
        return tags[key] !== undefined && tags[key] !== 'no';
    }

    function getFeatureTypeForCrossingCheck(way, graph) {
        var tags = getFeatureWithFeatureTypeTagsForWay(way, graph).tags;
        return getFeatureTypeForTags(tags);
    }

    // only validate certain waterway features
    var waterways = new Set(['canal', 'ditch', 'drain', 'river', 'stream']);
    // ignore certain highway and railway features
    var ignoredHighways = new Set(['rest_area', 'services']);
    var ignoredRailways = new Set(['train_wash']);

    function getFeatureTypeForTags(tags) {
        if (hasTag(tags, 'building')) return 'building';

        // don't check non-building areas
        if (hasTag(tags, 'area')) return null;

        if (hasTag(tags, 'highway') && !ignoredHighways.has(tags.highway)) return 'highway';
        if (hasTag(tags, 'railway') && !ignoredRailways.has(tags.railway)) return 'railway';
        if (hasTag(tags, 'waterway') && waterways.has(tags.waterway)) return 'waterway';

        return null;
    }

    function extendTagsByInferredLayer(tags, way) {
        if (!hasTag(tags, 'layer')) {
            tags.layer = way.layer().toString();
        }
        return tags;
    }

    function isLegitCrossing(way1, featureType1, way2, featureType2, graph) {
        var tags1 = _clone(getFeatureWithFeatureTypeTagsForWay(way1, graph).tags),
            tags2 = _clone(getFeatureWithFeatureTypeTagsForWay(way2, graph).tags);
        tags1 = extendTagsByInferredLayer(tags1, way1);
        tags2 = extendTagsByInferredLayer(tags2, way2);

        // For better readability, not chaining all the true conditions into one if statement.
        if ((featureType1 === 'highway' && featureType2 === 'highway') ||
            (featureType1 === 'highway' && featureType2 === 'railway') ||
            (featureType1 === 'railway' && featureType2 === 'railway')) {
            // Legit cases:
            // (1) they're on different layers
            // (2) only one of the two ways is on a bridge
            // (3) only one of the two ways is in a tunnel
            if (tags1.layer !== tags2.layer) return true;
            if (hasTag(tags1, 'bridge') && !hasTag(tags2, 'bridge')) return true;
            if (!hasTag(tags1, 'bridge') && hasTag(tags2, 'bridge')) return true;
            if (hasTag(tags1, 'tunnel') && !hasTag(tags2, 'tunnel')) return true;
            if (!hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel')) return true;
        }
        if ((featureType1 === 'highway' && featureType2 === 'waterway') ||
            (featureType1 === 'railway' && featureType2 === 'waterway')) {
            // Legit cases:
            // (1) highway/railway is on a bridge
            // (2) only one of the two ways is in a tunnel
            // (3) both are in tunnels but on different layers
            if (hasTag(tags1, 'bridge')) return true;
            if (hasTag(tags1, 'tunnel') && !hasTag(tags2, 'tunnel')) return true;
            if (!hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel')) return true;
            if (hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel') && tags1.layer !== tags2.layer) return true;
        }
        if ((featureType1 === 'highway' && featureType2 === 'building') ||
            (featureType1 === 'railway' && featureType2 === 'building')) {
            // Legit cases:
            // (1) highway/railway has a bridge or tunnel tag
            // (2) highway/railway has a covered tag
            if (hasTag(tags1, 'bridge') || hasTag(tags1, 'tunnel') || hasTag(tags1, 'covered')) return true;
        }
        if (featureType1 === 'waterway' && featureType2 === 'waterway') {
            // Legit cases:
            // (1) only one of the water is in a tunnel
            // (2) both are in tunnels but on differnt layers
            if (hasTag(tags1, 'tunnel') && !hasTag(tags2, 'tunnel')) return true;
            if (!hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel')) return true;
            if (hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel') && tags1.layer !== tags2.layer) return true;
        }
        if (featureType1 === 'waterway' && featureType2 === 'building') {
            // Legit cases:
            // (1) water is in a tunnel
            // (2) water has a covered tag
            if (hasTag(tags1, 'tunnel') || hasTag(tags1, 'covered')) return true;
        }
        if (featureType1 === 'building' && featureType2 === 'building') {
            // Legit case: they're on different layers
            if (tags1.layer !== tags2.layer) return true;
        }
        return false;
    }

    // highway values for which we shouldn't recommend connecting to waterways
    var highwaysDisallowingFords = new Set([
        'motorway', 'motorway_link', 'trunk', 'trunk_link',
        'primary', 'primary_link', 'secondary', 'secondary_link'
    ]);
    var railwayEqualsCrossingHighways = new Set([
        'path', 'footway', 'cycleway', 'bridleway', 'pedestrian', 'steps'
    ]);

    function tagsForConnectionNodeIfAllowed(entity1, entity2) {
        var featureType1 = getFeatureTypeForTags(entity1.tags);
        var featureType2 = getFeatureTypeForTags(entity2.tags);
        if (featureType1 === featureType2) {
            if (featureType1 === 'highway') return {};
            if (featureType1 === 'waterway') return {};
            if (featureType1 === 'railway') return {};
        } else {
            var featureTypes = new Set([featureType1, featureType2]);
            if (featureTypes.has('highway')) {
                if (featureTypes.has('building')) return {};
                if (featureTypes.has('railway')) {
                    if (railwayEqualsCrossingHighways.has(entity1.tags.highway) ||
                        railwayEqualsCrossingHighways.has(entity2.tags.highway)) {
                        // path-rail connections use this tag
                        return { railway: 'crossing' };
                    } else {
                        // road-rail connections use this tag
                        return { railway: 'level_crossing' };
                    }
                }
                if (featureTypes.has('waterway')) {
                    // do not allow fords on structures
                    if (hasTag(entity1.tags, 'tunnel') && hasTag(entity2.tags, 'tunnel')) return null;
                    if (hasTag(entity1.tags, 'bridge') && hasTag(entity2.tags, 'bridge')) return null;

                    if (highwaysDisallowingFords.has(entity1.tags.highway) ||
                        highwaysDisallowingFords.has(entity2.tags.highway)) {
                        // do not allow fords on major highways
                        return null;
                    }
                    return { ford: 'yes' };
                }
            }
        }
        return null;
    }

    function findCrossingsByWay(entity, graph, tree) {
        var edgeCrossInfos = [];
        if (entity.type !== 'way') return edgeCrossInfos;

        var entFeatureType = getFeatureTypeForCrossingCheck(entity, graph);
        if (entFeatureType === null) return edgeCrossInfos;

        for (var i = 0; i < entity.nodes.length - 1; i++) {
            var nid1 = entity.nodes[i],
                nid2 = entity.nodes[i + 1],
                n1 = graph.entity(nid1),
                n2 = graph.entity(nid2),
                extent = geoExtent([
                    [
                        Math.min(n1.loc[0], n2.loc[0]),
                        Math.min(n1.loc[1], n2.loc[1])
                    ],
                    [
                        Math.max(n1.loc[0], n2.loc[0]),
                        Math.max(n1.loc[1], n2.loc[1])
                    ]
                ]),
                intersected = tree.intersects(extent, graph);
            for (var j = 0; j < intersected.length; j++) {
                if (intersected[j].type !== 'way') continue;

                // only check crossing highway, waterway, building, and railway
                var way = intersected[j],
                    wayFeatureType = getFeatureTypeForCrossingCheck(way, graph);
                if (wayFeatureType === null ||
                    isLegitCrossing(entity, entFeatureType, way, wayFeatureType, graph) ||
                    isLegitCrossing(way, wayFeatureType, entity, entFeatureType, graph)) {
                    continue;
                }

                var crossCoords = findEdgeToWayCrossCoords(n1, n2, way, graph);
                for (var k = 0; k < crossCoords.length; k++) {
                    edgeCrossInfos.push({
                        ways: [entity, way],
                        featureTypes: [entFeatureType, wayFeatureType],
                        cross_point: crossCoords[k],
                    });
                }
            }
        }
        return edgeCrossInfos;
    }

    var validation = function(entity, context) {

        var graph = context.graph();
        var tree = context.history().tree();
        // create one issue per crossing point
        var issues = [];
        var waysToCheck = _flattenDeep(_map([entity], function(entity) {
            if (!getFeatureTypeForTags(entity.tags)) {
                return [];
            }
            if (entity.type === 'way') {
                return entity;
            } else if (entity.type === 'relation' &&
                entity.tags.type === 'multipolygon' &&
                // only check multipolygons if they are buildings
                hasTag(entity.tags, 'building')) {
                return _map(entity.members, function(member) {
                    if (context.hasEntity(member.id)) {
                        var entity = context.entity(member.id);
                        if (entity.type === 'way') {
                            return entity;
                        }
                    }
                    return [];
                });
            }
            return [];
        }));
        var crossings = waysToCheck.reduce(function(array, way) {
            return array.concat(findCrossingsByWay(way, graph, tree));
        }, []);
        for (var j in crossings) {
            var crossing = crossings[j];

            // use the entities with the tags that define the feature type
            var entities = crossing.ways.sort(function(entity1, entity2) {
                var type1 = getFeatureTypeForCrossingCheck(entity1, graph);
                var type2 = getFeatureTypeForCrossingCheck(entity2, graph);
                if (type1 === type2) {
                    return utilDisplayLabel(entity1, context) > utilDisplayLabel(entity2, context);
                } else if (type1 === 'waterway') {
                    return true;
                } else if (type2 === 'waterway') {
                    return false;
                }
                return type1 < type2;
            });
            entities = _map(entities, function(way) {
                return getFeatureWithFeatureTypeTagsForWay(way, graph);
            });

            var connectionTags = tagsForConnectionNodeIfAllowed(entities[0], entities[1]);

            var crossingTypeID;
            if (hasTag(entities[0].tags, 'tunnel') && hasTag(entities[1].tags, 'tunnel')) {
                crossingTypeID = 'tunnel-tunnel';
                if (connectionTags) {
                    crossingTypeID += '_connectable';
                }
            }
            else if (hasTag(entities[0].tags, 'bridge') && hasTag(entities[1].tags, 'bridge')) {
                crossingTypeID = 'bridge-bridge';
                if (connectionTags) {
                    crossingTypeID += '_connectable';
                }
            }
            else {
                crossingTypeID = crossing.featureTypes.sort().join('-');
            }

            var messageDict = {
                feature: utilDisplayLabel(entities[0], context),
                feature2: utilDisplayLabel(entities[1], context)
            };

            var fixes = [];
            if (connectionTags) {
                fixes.push(new validationIssueFix({
                    title: t('issues.fix.add_connection_vertex.title'),
                    onClick: function() {
                        var loc = this.issue.coordinates;
                        var ways = this.issue.info.ways;
                        var connectionTags = this.issue.info.connectionTags;

                        context.perform(
                            function actionConnectCrossingWays(graph) {
                                var projection = context.projection;

                                var node = osmNode({ loc: loc, tags: connectionTags });
                                graph = graph.replace(node);

                                var way0 = graph.entity(ways[0].id);
                                var choice0 = geoChooseEdge(graph.childNodes(way0), projection(loc), projection);
                                var edge0 = [way0.nodes[choice0.index - 1], way0.nodes[choice0.index]];
                                graph = actionAddMidpoint({loc: loc, edge: edge0}, node)(graph);

                                var way1 = graph.entity(ways[1].id);
                                var choice1 = geoChooseEdge(graph.childNodes(way1), projection(loc), projection);
                                var edge1 = [way1.nodes[choice1.index - 1], way1.nodes[choice1.index]];
                                graph = actionAddMidpoint({loc: loc, edge: edge1}, node)(graph);

                                return graph;
                            },
                            t('issues.fix.add_connection_vertex.undo_redo')
                        );
                    }
                }));
            }

            issues.push(new validationIssue({
                type: 'crossing_ways',
                severity: 'warning',
                message: t('issues.crossing_ways.message', messageDict),
                tooltip: t('issues.crossing_ways.'+crossingTypeID+'.tip'),
                entities: entities,
                info: { ways: crossing.ways, connectionTags: connectionTags },
                coordinates: crossing.cross_point,
                fixes: fixes
            }));
        }

        return issues;
    };

    validation.type = 'crossing_ways';

    return validation;
}
<commit_msg>Improve crossing ways validation performance by reducing garbage collection<commit_after>import _clone from 'lodash-es/clone';
import _map from 'lodash-es/map';
import _flattenDeep from 'lodash-es/flatten';
import { geoExtent, geoLineIntersection } from '../geo';
import { utilDisplayLabel } from '../util';
import { t } from '../util/locale';
import {
    validationIssue,
    validationIssueFix
} from '../core/validator';
import { osmNode } from '../osm';
import { actionAddMidpoint } from '../actions';
import { geoChooseEdge } from '../geo';

export function validationCrossingWays() {
    // Check if the edge going from n1 to n2 crosses (without a connection node)
    // any edge on way. Return the cross point if so.
    function findEdgeToWayCrossCoords(n1, n2, way, graph) {
        var crossCoords = [];
        var nA, nB, segment1, segment2;
        var nodes = graph.childNodes(way);
        for (var j = 0; j < nodes.length - 1; j++) {
            nA = nodes[j];
            nB = nodes[j + 1];
            if (nA.id === n1.id || nA.id === n2.id ||
                nB.id === n1.id || nB.id === n2.id) {
                // n1 or n2 is a connection node; skip
                continue;
            }
            segment1 = [n1.loc, n2.loc];
            segment2 = [nA.loc, nB.loc];
            var point = geoLineIntersection(segment1, segment2);
            if (point) crossCoords.push(point);
        }
        return crossCoords;
    }

    // returns the way or its parent relation, whichever has a useful feature type
    function getFeatureWithFeatureTypeTagsForWay(way, graph) {
        if (getFeatureTypeForTags(way.tags) === null) {
            // if the way doesn't match a feature type, check is parent relations
            var parentRels = graph.parentRelations(way);
            for (var i = 0; i < parentRels.length; i++) {
                var rel = parentRels[i];
                if (getFeatureTypeForTags(rel.tags) !== null) {
                    return rel;
                }
            }
        }
        return way;
    }

    function hasTag(tags, key) {
        return tags[key] !== undefined && tags[key] !== 'no';
    }

    function getFeatureTypeForCrossingCheck(way, graph) {
        var tags = getFeatureWithFeatureTypeTagsForWay(way, graph).tags;
        return getFeatureTypeForTags(tags);
    }

    // only validate certain waterway features
    var waterways = new Set(['canal', 'ditch', 'drain', 'river', 'stream']);
    // ignore certain highway and railway features
    var ignoredHighways = new Set(['rest_area', 'services']);
    var ignoredRailways = new Set(['train_wash']);

    function getFeatureTypeForTags(tags) {
        if (hasTag(tags, 'building')) return 'building';

        // don't check non-building areas
        if (hasTag(tags, 'area')) return null;

        if (hasTag(tags, 'highway') && !ignoredHighways.has(tags.highway)) return 'highway';
        if (hasTag(tags, 'railway') && !ignoredRailways.has(tags.railway)) return 'railway';
        if (hasTag(tags, 'waterway') && waterways.has(tags.waterway)) return 'waterway';

        return null;
    }

    function extendTagsByInferredLayer(tags, way) {
        if (!hasTag(tags, 'layer')) {
            tags.layer = way.layer().toString();
        }
        return tags;
    }

    function isLegitCrossing(way1, featureType1, way2, featureType2, graph) {
        var tags1 = _clone(getFeatureWithFeatureTypeTagsForWay(way1, graph).tags),
            tags2 = _clone(getFeatureWithFeatureTypeTagsForWay(way2, graph).tags);
        tags1 = extendTagsByInferredLayer(tags1, way1);
        tags2 = extendTagsByInferredLayer(tags2, way2);

        // For better readability, not chaining all the true conditions into one if statement.
        if ((featureType1 === 'highway' && featureType2 === 'highway') ||
            (featureType1 === 'highway' && featureType2 === 'railway') ||
            (featureType1 === 'railway' && featureType2 === 'railway')) {
            // Legit cases:
            // (1) they're on different layers
            // (2) only one of the two ways is on a bridge
            // (3) only one of the two ways is in a tunnel
            if (tags1.layer !== tags2.layer) return true;
            if (hasTag(tags1, 'bridge') && !hasTag(tags2, 'bridge')) return true;
            if (!hasTag(tags1, 'bridge') && hasTag(tags2, 'bridge')) return true;
            if (hasTag(tags1, 'tunnel') && !hasTag(tags2, 'tunnel')) return true;
            if (!hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel')) return true;
        }
        if ((featureType1 === 'highway' && featureType2 === 'waterway') ||
            (featureType1 === 'railway' && featureType2 === 'waterway')) {
            // Legit cases:
            // (1) highway/railway is on a bridge
            // (2) only one of the two ways is in a tunnel
            // (3) both are in tunnels but on different layers
            if (hasTag(tags1, 'bridge')) return true;
            if (hasTag(tags1, 'tunnel') && !hasTag(tags2, 'tunnel')) return true;
            if (!hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel')) return true;
            if (hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel') && tags1.layer !== tags2.layer) return true;
        }
        if ((featureType1 === 'highway' && featureType2 === 'building') ||
            (featureType1 === 'railway' && featureType2 === 'building')) {
            // Legit cases:
            // (1) highway/railway has a bridge or tunnel tag
            // (2) highway/railway has a covered tag
            if (hasTag(tags1, 'bridge') || hasTag(tags1, 'tunnel') || hasTag(tags1, 'covered')) return true;
        }
        if (featureType1 === 'waterway' && featureType2 === 'waterway') {
            // Legit cases:
            // (1) only one of the water is in a tunnel
            // (2) both are in tunnels but on differnt layers
            if (hasTag(tags1, 'tunnel') && !hasTag(tags2, 'tunnel')) return true;
            if (!hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel')) return true;
            if (hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel') && tags1.layer !== tags2.layer) return true;
        }
        if (featureType1 === 'waterway' && featureType2 === 'building') {
            // Legit cases:
            // (1) water is in a tunnel
            // (2) water has a covered tag
            if (hasTag(tags1, 'tunnel') || hasTag(tags1, 'covered')) return true;
        }
        if (featureType1 === 'building' && featureType2 === 'building') {
            // Legit case: they're on different layers
            if (tags1.layer !== tags2.layer) return true;
        }
        return false;
    }

    // highway values for which we shouldn't recommend connecting to waterways
    var highwaysDisallowingFords = new Set([
        'motorway', 'motorway_link', 'trunk', 'trunk_link',
        'primary', 'primary_link', 'secondary', 'secondary_link'
    ]);
    var railwayEqualsCrossingHighways = new Set([
        'path', 'footway', 'cycleway', 'bridleway', 'pedestrian', 'steps'
    ]);

    function tagsForConnectionNodeIfAllowed(entity1, entity2) {
        var featureType1 = getFeatureTypeForTags(entity1.tags);
        var featureType2 = getFeatureTypeForTags(entity2.tags);
        if (featureType1 === featureType2) {
            if (featureType1 === 'highway') return {};
            if (featureType1 === 'waterway') return {};
            if (featureType1 === 'railway') return {};
        } else {
            var featureTypes = new Set([featureType1, featureType2]);
            if (featureTypes.has('highway')) {
                if (featureTypes.has('building')) return {};
                if (featureTypes.has('railway')) {
                    if (railwayEqualsCrossingHighways.has(entity1.tags.highway) ||
                        railwayEqualsCrossingHighways.has(entity2.tags.highway)) {
                        // path-rail connections use this tag
                        return { railway: 'crossing' };
                    } else {
                        // road-rail connections use this tag
                        return { railway: 'level_crossing' };
                    }
                }
                if (featureTypes.has('waterway')) {
                    // do not allow fords on structures
                    if (hasTag(entity1.tags, 'tunnel') && hasTag(entity2.tags, 'tunnel')) return null;
                    if (hasTag(entity1.tags, 'bridge') && hasTag(entity2.tags, 'bridge')) return null;

                    if (highwaysDisallowingFords.has(entity1.tags.highway) ||
                        highwaysDisallowingFords.has(entity2.tags.highway)) {
                        // do not allow fords on major highways
                        return null;
                    }
                    return { ford: 'yes' };
                }
            }
        }
        return null;
    }

    function findCrossingsByWay(entity, graph, tree) {
        var edgeCrossInfos = [];
        if (entity.type !== 'way') return edgeCrossInfos;

        var entFeatureType = getFeatureTypeForCrossingCheck(entity, graph);
        if (entFeatureType === null) return edgeCrossInfos;

        for (var i = 0; i < entity.nodes.length - 1; i++) {
            var nid1 = entity.nodes[i],
                nid2 = entity.nodes[i + 1],
                n1 = graph.entity(nid1),
                n2 = graph.entity(nid2),
                extent = geoExtent([
                    [
                        Math.min(n1.loc[0], n2.loc[0]),
                        Math.min(n1.loc[1], n2.loc[1])
                    ],
                    [
                        Math.max(n1.loc[0], n2.loc[0]),
                        Math.max(n1.loc[1], n2.loc[1])
                    ]
                ]),
                intersected = tree.intersects(extent, graph);
            for (var j = 0; j < intersected.length; j++) {
                if (intersected[j].type !== 'way') continue;

                // only check crossing highway, waterway, building, and railway
                var way = intersected[j],
                    wayFeatureType = getFeatureTypeForCrossingCheck(way, graph);
                if (wayFeatureType === null ||
                    isLegitCrossing(entity, entFeatureType, way, wayFeatureType, graph) ||
                    isLegitCrossing(way, wayFeatureType, entity, entFeatureType, graph)) {
                    continue;
                }

                var crossCoords = findEdgeToWayCrossCoords(n1, n2, way, graph);
                for (var k = 0; k < crossCoords.length; k++) {
                    edgeCrossInfos.push({
                        ways: [entity, way],
                        featureTypes: [entFeatureType, wayFeatureType],
                        cross_point: crossCoords[k],
                    });
                }
            }
        }
        return edgeCrossInfos;
    }

    var validation = function(entity, context) {

        var graph = context.graph();
        var tree = context.history().tree();
        // create one issue per crossing point
        var issues = [];
        var waysToCheck = _flattenDeep(_map([entity], function(entity) {
            if (!getFeatureTypeForTags(entity.tags)) {
                return [];
            }
            if (entity.type === 'way') {
                return entity;
            } else if (entity.type === 'relation' &&
                entity.tags.type === 'multipolygon' &&
                // only check multipolygons if they are buildings
                hasTag(entity.tags, 'building')) {
                return _map(entity.members, function(member) {
                    if (context.hasEntity(member.id)) {
                        var entity = context.entity(member.id);
                        if (entity.type === 'way') {
                            return entity;
                        }
                    }
                    return [];
                });
            }
            return [];
        }));
        var crossings = waysToCheck.reduce(function(array, way) {
            return array.concat(findCrossingsByWay(way, graph, tree));
        }, []);
        for (var j in crossings) {
            var crossing = crossings[j];

            // use the entities with the tags that define the feature type
            var entities = crossing.ways.sort(function(entity1, entity2) {
                var type1 = getFeatureTypeForCrossingCheck(entity1, graph);
                var type2 = getFeatureTypeForCrossingCheck(entity2, graph);
                if (type1 === type2) {
                    return utilDisplayLabel(entity1, context) > utilDisplayLabel(entity2, context);
                } else if (type1 === 'waterway') {
                    return true;
                } else if (type2 === 'waterway') {
                    return false;
                }
                return type1 < type2;
            });
            entities = _map(entities, function(way) {
                return getFeatureWithFeatureTypeTagsForWay(way, graph);
            });

            var connectionTags = tagsForConnectionNodeIfAllowed(entities[0], entities[1]);

            var crossingTypeID;
            if (hasTag(entities[0].tags, 'tunnel') && hasTag(entities[1].tags, 'tunnel')) {
                crossingTypeID = 'tunnel-tunnel';
                if (connectionTags) {
                    crossingTypeID += '_connectable';
                }
            }
            else if (hasTag(entities[0].tags, 'bridge') && hasTag(entities[1].tags, 'bridge')) {
                crossingTypeID = 'bridge-bridge';
                if (connectionTags) {
                    crossingTypeID += '_connectable';
                }
            }
            else {
                crossingTypeID = crossing.featureTypes.sort().join('-');
            }

            var messageDict = {
                feature: utilDisplayLabel(entities[0], context),
                feature2: utilDisplayLabel(entities[1], context)
            };

            var fixes = [];
            if (connectionTags) {
                fixes.push(new validationIssueFix({
                    title: t('issues.fix.add_connection_vertex.title'),
                    onClick: function() {
                        var loc = this.issue.coordinates;
                        var ways = this.issue.info.ways;
                        var connectionTags = this.issue.info.connectionTags;

                        context.perform(
                            function actionConnectCrossingWays(graph) {
                                var projection = context.projection;

                                var node = osmNode({ loc: loc, tags: connectionTags });
                                graph = graph.replace(node);

                                var way0 = graph.entity(ways[0].id);
                                var choice0 = geoChooseEdge(graph.childNodes(way0), projection(loc), projection);
                                var edge0 = [way0.nodes[choice0.index - 1], way0.nodes[choice0.index]];
                                graph = actionAddMidpoint({loc: loc, edge: edge0}, node)(graph);

                                var way1 = graph.entity(ways[1].id);
                                var choice1 = geoChooseEdge(graph.childNodes(way1), projection(loc), projection);
                                var edge1 = [way1.nodes[choice1.index - 1], way1.nodes[choice1.index]];
                                graph = actionAddMidpoint({loc: loc, edge: edge1}, node)(graph);

                                return graph;
                            },
                            t('issues.fix.add_connection_vertex.undo_redo')
                        );
                    }
                }));
            }

            issues.push(new validationIssue({
                type: 'crossing_ways',
                severity: 'warning',
                message: t('issues.crossing_ways.message', messageDict),
                tooltip: t('issues.crossing_ways.'+crossingTypeID+'.tip'),
                entities: entities,
                info: { ways: crossing.ways, connectionTags: connectionTags },
                coordinates: crossing.cross_point,
                fixes: fixes
            }));
        }

        return issues;
    };

    validation.type = 'crossing_ways';

    return validation;
}
<|endoftext|>


<commit_before>
JSHC.Test.Tests.codegen = function(tester){
    var cases = {};

    cases.arith = {
        fileSystem: {"Arith": "module Arith where\n" +
                     "a x y = x + y\n"+
                     "b x y = x - y\n"+
                     "c x y = x / y\n"+
                     "d x y = x * y\n"+
                     "e x y = x + 2 - y\n"+
                     "f x y = x + 2 + y\n"+
                     "g x y = x + 4 / y\n"+
                     "h x y = x + 2 * y\n"+
                     "i x y = x + 2 - y + 3\n"+
                     "j x y = x + 2 * y + 3\n"+
                     "k x y = x + 2 / y + 3\n"
                      },
        commands: {
                   "a 1 2": "3",
                   "b 5 2": "3",
                   "c 4 2": "2",
                   "d 2 2": "4",
                   "e 1 2": "1",
                   "f 1 2": "5",
                   "g 1 2": "3",
                   "h 1 10": "21",
                   "i 1 2": "4",
                   "j 1 2": "8",
                   "k 1 2": "5"
                   }
    }

    cases.wheres = {
        fileSystem: {"Wheres": "module Wheres where\n"+
           "a = x where x = 1\n"+
           "b = x 1 where x z = 1 + z\n"+
           "c = x + y where\n x = 1\n y = 2\n"+
           "d = x + y where x = 1\n                y = 2\n"+
           "e = x + y\n where\n  x = 1\n  y = 2\n"
           },
        commands: {
            "a": "1",
            "b": "2",
            "c": "3",
            "d": "3",
            "e": "3",
        }
    }

    cases.lets = {
        fileSystem: { "Lets": "module Lets where\n"+
           "a = let x = 1 in x\n"+
           "b = let x z = 1 + z in x 1\n"+
           "c = let\n x = 1\n y = 2\n in x + y\n"+
           "d = let x = 1\n        y = 2\n        in x + y\n"+
           "e =\n let x = 1\n     y = 2\n  in x + y\n"
        },
        commands: {
            "a": "1",
            "b": "2",
            "c": "3",
            "d": "3",
            "e": "3",
        }

    }

    for(var cname in cases){
        JSHC.Test.runData(tester,
            {name: "codegen-"+cname,
             errors: 0,
             fileSystem: cases[cname].fileSystem,
             commands: cases[cname].commands});
    }
};

<commit_msg>added test cases for lambda and case expressions<commit_after>
JSHC.Test.Tests.codegen = function(tester){
    var cases = {};

    cases.arith = {
        fileSystem: {"Arith": "module Arith where\n" +
                     "a x y = x + y\n"+
                     "b x y = x - y\n"+
                     "c x y = x / y\n"+
                     "d x y = x * y\n"+
                     "e x y = x + 2 - y\n"+
                     "f x y = x + 2 + y\n"+
                     "g x y = x + 4 / y\n"+
                     "h x y = x + 2 * y\n"+
                     "i x y = x + 2 - y + 3\n"+
                     "j x y = x + 2 * y + 3\n"+
                     "k x y = x + 2 / y + 3\n"
                      },
        commands: {
                   "a 1 2": "3",
                   "b 5 2": "3",
                   "c 4 2": "2",
                   "d 2 2": "4",
                   "e 1 2": "1",
                   "f 1 2": "5",
                   "g 1 2": "3",
                   "h 1 10": "21",
                   "i 1 2": "4",
                   "j 1 2": "8",
                   "k 1 2": "5"
                   }
    }

    cases.wheres = {
        fileSystem: {"Wheres": "module Wheres where\n"+
           "a = x where x = 1\n"+
           "b = x 1 where x z = 1 + z\n"+
           "c = x + y where\n x = 1\n y = 2\n"+
           "d = x + y where x = 1\n                y = 2\n"+
           "e = x + y\n where\n  x = 1\n  y = 2\n"
           },
        commands: {
            "a": "1",
            "b": "2",
            "c": "3",
            "d": "3",
            "e": "3",
        }
    }

    cases.lets = {
        fileSystem: { "Lets": "module Lets where\n"+
           "a = let x = 1 in x\n"+
           "b = let x z = 1 + z in x 1\n"+
           "c = let\n x = 1\n y = 2\n in x + y\n"+
           "d = let x = 1\n        y = 2\n        in x + y\n"+
           "e =\n let x = 1\n     y = 2\n  in x + y\n"
        },
        commands: {
            "a": "1",
            "b": "2",
            "c": "3",
            "d": "3",
            "e": "3",
        }

    }
    cases.lambda = {
        fileSystem: {"Lambda": "module Lambda where\n"+
        "a = \\ a -> g a\ng = \\ a -> a + 1\n"+
        "b = \\ a -> \n  case a of\n    1 -> 2\n    2 -> 1\n"+
        "c = \\ a b -> \n  case a of\n    b -> 2\n"+
        "d = \\ a b -> \n  case a of\n    b -> 2\n    2 -> 1\n"+
        "e = \\ a b c -> \n  case a of\n    b -> c\n"+
        "f = \\ a b c -> \n  case a of\n    b -> 2\n    c -> 1\n"
        },
        commands: {
        "a 1": "2",
        "b 1": "2",
        "c 1 1": "2",
        "d 1 2": "1",
        "e 1 1 3": "3",
        "f 1 4 1": "1"
        }
    }

    for(var cname in cases){
        JSHC.Test.runData(tester,
            {name: "codegen-"+cname,
             errors: 0,
             fileSystem: cases[cname].fileSystem,
             commands: cases[cname].commands});
    }
};

<|endoftext|>