From 04ab09586bb8dd6c7f85d522931478119d4aa235 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Tue, 31 May 2022 20:36:39 +0800 Subject: [PATCH] :rocket: add stl/fg_env.nas & delete test/props props_sim maketimer_sim.nas --- makefile | 4 +- stl/fg_env.nas | 328 +++++++++++++++++++++++ test/auto_crash.nas | 5 +- test/calc.nas | 4 +- test/lexer.nas | 2 +- test/maketimer_sim.nas | 126 --------- test/md5compare.nas | 4 +- test/props.nas | 577 ----------------------------------------- test/props_sim.nas | 176 ------------- test/utf8chk.nas | 17 ++ 10 files changed, 351 insertions(+), 892 deletions(-) create mode 100644 stl/fg_env.nas delete mode 100644 test/maketimer_sim.nas delete mode 100644 test/props.nas delete mode 100644 test/props_sim.nas diff --git a/makefile b/makefile index f1fe9e4..30cdd01 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,4 @@ -.PHONY=test +.PHONY:test nasal:main.cpp nasal_ast.h nasal_err.h nasal_builtin.h nasal_opt.h nasal_codegen.h\ nasal_gc.h nasal_import.h nasal_lexer.h nasal_parse.h nasal_vm.h nasal_dbg.h nasal.h clang++ -std=c++11 -O3 main.cpp -o nasal -fno-exceptions -ldl -Wshadow -Wall @@ -28,7 +28,6 @@ test:nasal @ ./nasal -op -e -d test/lexer.nas @ ./nasal -op -e -d test/life.nas @ ./nasal -op -t test/loop.nas - @ ./nasal -op -c test/maketimer_sim.nas @ ./nasal -op -t -d test/mandel.nas @ ./nasal -op -t -d test/mandelbrot.nas @ ./nasal -op -t -d -o test/md5.nas @@ -37,7 +36,6 @@ test:nasal @ ./nasal -op -e test/nasal_test.nas @ ./nasal -op -t -d test/pi.nas @ ./nasal -op -t -d test/prime.nas - @ ./nasal -op -t -d test/props_sim.nas @ ./nasal -op -e test/qrcode.nas @ ./nasal -op -t -d test/quick_sort.nas @ ./nasal -op -e test/scalar.nas diff --git a/stl/fg_env.nas b/stl/fg_env.nas new file mode 100644 index 0000000..f838006 --- /dev/null +++ b/stl/fg_env.nas @@ -0,0 +1,328 @@ +# flightgear developer environments simulator (beta) +# ValKmjolnir 2022 + +println("------------------------------------------------------------"); +println("FlightGear simulated-env for developers project, since 2019"); +println("Developed by:"); +println("Sidi Liang (FGPRC-0762)"); +println("Haokun Lee (FGPRC-0818 aka ValKmjolnir)"); +println("------------------------------------------------------------"); +println("[\e[32m fg_env \e[0m] init begin"); +println("[\e[32m maketimer \e[0m] init tasks"); +println("[\e[32m maketimer \e[0m] init events"); +var fg_globals={ + task:{}, + event:{} +}; + +println("[\e[32m maketimer \e[0m] new func add_event(name,interval,function)"); +var add_event=func(name,interval,function){ + fg_globals.event[name]=coroutine.create(func{ + var timestamp=maketimestamp(); + timestamp.stamp(); + while(timestamp.elapsedMSec()0) + { + if(contains(tmp.val,path[i]~'['~index~']')) + return tmp.val[path[i]~'['~index~']']; + else + return tmp.val[path[i]]; + } + return tmp; + } +}; + +println("[\e[32m props \e[0m] init props.Node"); +props.Node= +{ + new:func(values=nil) + { + var res={ + parents:[props.Node], + val:{}, + type:'GHOST', + parent:nil + }; + if(typeof(values)=="hash") + res.val=values; + return res; + }, + addChild:func(name) + { + if(!contains(me.val,name)) + { + me.val[name]=props.Node.new(); + me.val[name].parent=me; + return 1; + } + return 0; + }, + addChildren:func(name,cnt=0) + { + for(var i=0;itutorial'); +props.getNode('/test/in',3).setValue('/',2147483648); + +props.getNode("/sim",1).addChild("messages"); +props.getNode("/sim/messages",1).addChild("copilot"); +props.getNode("/position",1).addChild("latitude-deg"); +props.getNode("/position",1).addChild("longitude-deg"); +props.getNode("/orientation",1).addChild("heading-deg"); +props.getNode("/controls",1).addChild("flight"); +props.getNode("/controls/flight",1).addChild("rudder"); + +props.getNode("/sim/messages/copilot",1).setValue('/',"nothing"); +props.getNode("/position/latitude-deg",1).setValue('/',90); +props.getNode("/position/longitude-deg",1).setValue('/',90); +props.getNode("/orientation/heading-deg",1).setValue('/',90); +props.getNode("/controls/flight/rudder",1).setValue('/',0.114); +println("[\e[32m props \e[0m] init done"); +println("[\e[32m fg_env \e[0m] init done"); +println("------------------------------------------------------------"); + +props.globals.debug(); \ No newline at end of file diff --git a/test/auto_crash.nas b/test/auto_crash.nas index 545c13c..2322406 100644 --- a/test/auto_crash.nas +++ b/test/auto_crash.nas @@ -1,6 +1,5 @@ # Road check and auto pilot by ValKmjolnir -import("./test/maketimer_sim.nas"); -import("./test/props_sim.nas"); +import("stl/fg_env.nas"); var dt=0.01; var intergral=0; @@ -97,5 +96,5 @@ var toggle_auto_pilot = func(){ # this is used to simulate the running process in fg # when using in fg, delete these lines below toggle_auto_pilot(); -road_check_timer.restart(1); +road_check_timer.restart(0.5); simulation(); \ No newline at end of file diff --git a/test/calc.nas b/test/calc.nas index 86a1d1a..a15d2f4 100644 --- a/test/calc.nas +++ b/test/calc.nas @@ -15,6 +15,7 @@ var source=[ ]; var lib=[ + "stl/fg_env.nas ", "stl/file.nas ", "stl/lib.nas ", "stl/list.nas ", @@ -48,7 +49,6 @@ var testfile=[ "test/lexer.nas ", "test/life.nas ", "test/loop.nas ", - "test/maketimer_sim.nas", "test/mandel.nas ", "test/mandelbrot.nas ", "test/md5.nas ", @@ -57,8 +57,6 @@ var testfile=[ "test/nasal_test.nas ", "test/pi.nas ", "test/prime.nas ", - "test/props_sim.nas ", - "test/props.nas ", "test/qrcode.nas ", "test/quick_sort.nas ", "test/scalar.nas ", diff --git a/test/lexer.nas b/test/lexer.nas index 3f2c3dd..d147866 100644 --- a/test/lexer.nas +++ b/test/lexer.nas @@ -204,7 +204,7 @@ var lexer=func(file) }; } -var lex=lexer("test/props.nas"); +var lex=lexer("stl/fg_env.nas"); lex.compile(); foreach(var tok;lex.get_token()) print('(',tok.line,' | ',tok.token,')\n'); \ No newline at end of file diff --git a/test/maketimer_sim.nas b/test/maketimer_sim.nas deleted file mode 100644 index a74ae68..0000000 --- a/test/maketimer_sim.nas +++ /dev/null @@ -1,126 +0,0 @@ - -var task={}; -var event={}; - -var add_event=func(name,interval,function){ - event[name]=coroutine.create(func{ - var timestamp=maketimestamp(); - timestamp.stamp(); - while(timestamp.elapsedMSec() 1 and !contains(val, name)) val[name] = []; - } - if(nc > 1) append(val[name], c.getValues()); - else val[name] = c.getValues(); - } - return val; -} - -## -# Initializes property if it's still undefined. First argument -# is a property name/path. It can also be nil or an empty string, -# in which case the node itself gets initialized, rather than one -# of its children. Second argument is the default value. The third, -# optional argument is a property type (one of "STRING", "DOUBLE", -# "INT", or "BOOL"). If it is omitted, then "DOUBLE" is used for -# numbers, and STRING for everything else. Returns the property -# as props.Node. The fourth optional argument enforces a type if -# non-zero. -# -Node.initNode = func(path = nil, value = 0, type = nil, force = 0) { - var prop = me.getNode(path or "", 1); - if(prop.getType() != "NONE") value = prop.getValue(); - if(force) prop.clearValue(); - if(type == nil) prop.setValue(value); - elsif(type == "DOUBLE") prop.setDoubleValue(value); - elsif(type == "INT") prop.setIntValue(value); - elsif(type == "BOOL") prop.setBoolValue(value); - elsif(type == "STRING") prop.setValue("" ~ value); - else die("initNode(): unsupported type '" ~ type ~ "'"); - return prop; -} - -## -# Useful debugging utility. Recursively dumps the full state of a -# Node object to the console. Try binding "props.dump(props.globals)" -# to a key for a fun hack. -# -var dump = func { - if(size(arg) == 1) { prefix = ""; node = arg[0]; } - else { prefix = arg[0]; node = arg[1]; } - - index = node.getIndex(); - type = node.getType(); - name = node.getName(); - val = node.getValue(); - - if(val == nil) { val = "nil"; } - name = prefix ~ name; - if(index > 0) { name = name ~ "[" ~ index ~ "]"; } - print(name, " {", type, "} = ", val); - - # Don't recurse into aliases, lest we get stuck in a loop - if(type != "ALIAS") { - children = node.getChildren(); - foreach(c; children) { dump(name ~ "/", c); } - } -} - -## -# Recursively copy property branch from source Node to -# destination Node. Doesn't copy aliases. Copies attributes -# if optional third argument is set and non-zero. -# -var copy = func(src, dest, attr = 0) { - foreach(var c; src.getChildren()) { - var name = c.getName() ~ "[" ~ c.getIndex() ~ "]"; - copy(src.getNode(name), dest.getNode(name, 1), attr); - } - var type = src.getType(); - var val = src.getValue(); - if(type == "ALIAS" or type == "NONE") return; - elsif(type == "BOOL") dest.setBoolValue(val); - elsif(type == "INT" or type == "LONG") dest.setIntValue(val); - elsif(type == "FLOAT" or type == "DOUBLE") dest.setDoubleValue(val); - else dest.setValue(val); - if(attr) dest.setAttribute(src.getAttribute()); -} - -## -# Utility. Returns a new object with its superclass/parent set to the -# Node object and its _g (ghost) field set to the specified object. -# Nasal's literal syntax can be pleasingly terse. I like that. :) -# -var wrapNode = func(node) { { parents : [Node], _g : node } } - -## -# Global property tree. Set once at initialization. Is that OK? -# Does anything ever call globals.set_props() from C++? May need to -# turn this into a function if so. -# -var globals = wrapNode(_globals()); - -## -# Shortcut for props.globals.getNode(). -# -var getNode = func return call(props.globals.getNode, arg, props.globals); - -## -# Sets all indexed property children to a single value. arg[0] -# specifies a property name (e.g. /controls/engines/engine), arg[1] a -# path under each node of that name to set (e.g. "throttle"), arg[2] -# is the value. -# -var setAll = func(base, child, value) { - var node = props.globals.getNode(base); - if(node == nil) return; - var name = node.getName(); - node = node.getParent(); - if(node == nil) return; - var children = node.getChildren(); - foreach(var c; children) - if(c.getName() == name) - c.getNode(child, 1).setValue(value); -} - -## -# Turns about anything into a list of props.Nodes, including ghosts, -# path strings, vectors or hashes containing, as well as functions -# returning any of the former and in arbitrary nesting. This is meant -# to be used in functions whose main purpose is to handle collections -# of properties. -# -var nodeList = func { - var list = []; - foreach(var a; arg) { - var t = typeof(a); - if(isa(a, Node)) - append(list, a); - elsif(t == "scalar") - append(list, props.globals.getNode(a, 1)); - elsif(t == "vector") - foreach(var i; a) - list ~= nodeList(i); - elsif(t == "hash") - foreach(var i; keys(a)) - list ~= nodeList(a[i]); - elsif(t == "func") - list ~= nodeList(a()); - elsif(t == "ghost" and ghosttype(a) == "prop") - append(list, wrapNode(a)); - else - die("nodeList: invalid nil property"); - } - return list; -} - -## -# Compiles a property branch according to the rules -# set out in $FG_ROOT/Docs/README.conditions into a Condition object. -# The 'test' method of the returend object can be used to evaluate -# the condition. -# The function returns nil on error. -# -var compileCondition = func(p) { - if(p == nil) return nil; - if(!isa(p, Node)) p = props.globals.getNode(p); - return _createCondition(p._g); -} - -## -# Evaluates a property branch according to the rules -# set out in $FG_ROOT/Docs/README.conditions. Undefined conditions -# and a nil argument are "true". The function dumps the condition -# branch and returns nil on error. -# -var condition = func(p) { - if(p == nil) return 1; - if(!isa(p, Node)) p = props.globals.getNode(p); - return _cond_and(p) -} - -var _cond_and = func(p) { - foreach(var c; p.getChildren()) - if(!_cond(c)) return 0; - return 1; -} - -var _cond_or = func(p) { - foreach(var c; p.getChildren()) - if(_cond(c)) return 1; - return 0; -} - -var _cond = func(p) { - var n = p.getName(); - if(n == "or") return _cond_or(p); - if(n == "and") return _cond_and(p); - if(n == "not") return !_cond_and(p); - if(n == "equals") return _cond_cmp(p, 0); - if(n == "not-equals") return !_cond_cmp(p, 0); - if(n == "less-than") return _cond_cmp(p, -1); - if(n == "greater-than") return _cond_cmp(p, 1); - if(n == "less-than-equals") return !_cond_cmp(p, 1); - if(n == "greater-than-equals") return !_cond_cmp(p, -1); - if(n == "property") return !!getprop(p.getValue()); - printlog("alert", "condition: invalid operator ", n); - dump(p); - return nil; -} - -var _cond_cmp = func(p, op) { - var left = p.getChild("property", 0, 0); - if(left != nil) { left = getprop(left.getValue()); } - else { - printlog("alert", "condition: no left value"); - dump(p); - return nil; - } - var right = p.getChild("property", 1, 0); - if(right != nil) { right = getprop(right.getValue()); } - else { - right = p.getChild("value", 0, 0); - if(right != nil) { right = right.getValue(); } - else { - printlog("alert", "condition: no right value"); - dump(p); - return nil; - } - } - if(left == nil or right == nil) { - printlog("alert", "condition: comparing with nil"); - dump(p); - return nil; - } - if(op < 0) return left < right; - if(op > 0) return left > right; - return left == right; -} - -## -# Runs as described in $FG_ROOT/Docs/README.commands using -# a given module by default, and returns 1 if fgcommand() succeeded, -# or 0 otherwise. The module name won't override a defined -# in the binding. -# -var runBinding = func(node, module = nil) { - if(module != nil and node.getNode("module") == nil) - node.getNode("module", 1).setValue(module); - var cmd = node.getNode("command", 1).getValue() or "null"; - condition(node.getNode("condition")) ? fgcommand(cmd, node) : 0; -} - - #--------------------------------------------------------------------------- - # Property / object update manager - # - # - Manage updates when a value has changed more than a predetermined amount. - # This class is designed to make updating displays (e.g. canvas), or - # performing actions based on a property (or value in a hash) changing - # by more than the preset amount. - # This can make a significant improvement to performance compared to simply - # redrawing a canvas in an update loop. - # - Author : Richard Harrison (rjh@zaretto.com) - #---------------------------------------------------------------------------*/ - -#example usage: -# this is using the hashlist (which works well with an Emesary notification) -# basically when the method is called it will call each section (in the lambda) -# when the value changes by more than the amount specified as the second parameter. -# It is possible to reference multiple elements from the hashlist in each FromHashList; if either -# one changes then it will result in the lambda being called. -# -# obj.update_items = [ -# UpdateManager.FromHashList(["VV_x","VV_y"], 0.01, func(val) -# { -# obj.VV.setTranslation (val.VV_x, val.VV_y + pitch_offset); -# }), -# UpdateManager.FromHashList(["pitch","roll"], 0.025, func(hdp) -# { -# obj.ladder.setTranslation (0.0, hdp.pitch * pitch_factor+pitch_offset); -# obj.ladder.setCenter (118,830 - hdp.pitch * pitch_factor-pitch_offset); -# obj.ladder.setRotation (-hdp.roll_rad); -# obj.roll_pointer.setRotation (hdp.roll_rad); -# }), -# props.UpdateManager.FromProperty("velocities/airspeed-kt", 0.01, func(val) -# { -# obj.ias_range.setTranslation(0, val * ias_range_factor); -# }), -# props.UpdateManager.FromPropertyHashList(["orientation/alpha-indicated-deg", "orientation/side-slip-deg"], 0.1, func(val) -# { -# obj.VV_x = val.property["orientation/side-slip-deg"].getValue()*10; # adjust for view -# obj.VV_y = val.property["orientation/alpha-indicated-deg"].getValue()*10; # adjust for view -# obj.VV.setTranslation (obj.VV_x, obj.VV_y); -# }), -# ] -# -#==== the update loop then becomes ====== -# -# foreach(var update_item; me.update_items) -# { -# # hdp is a data provider that can be used as the hashlist for the property -# # update from hash methods. -# update_item.update(hdp); -# } -# -var UpdateManager = -{ - _updateProperty : func(_property) - { - }, - FromProperty : func(_propname, _delta, _changed_method) - { - var obj = {parents : [UpdateManager] }; - obj.propname = _propname; - obj.property = props.globals.getNode(_propname); - obj.delta = _delta; - obj.curval = obj.property.getValue(); - obj.lastval = obj.curval; - obj.changed = _changed_method; - obj.update = func(obj) - { - me.curval = me.property.getValue(); - if (me.curval != nil) - { - me.localType = me.property.getType(); - if (me.localType == "INT" or me.localType == "LONG" or me.localType == "FLOAT" or me.localType == "DOUBLE") - { - if(me.lastval == nil or math.abs(me.lastval - me.curval) >= me.delta) - { - me.lastval = me.curval; - me.changed(me.curval); - } - } - else if(me.lastval == nil or me.lastval != me.curval) - { - me.lastval = me.curval; - me.changed(me.curval); - } - } - }; - obj.update(obj); - return obj; - }, - - IsNumeric : func(hashkey) - { - me.localType = me.property[hashkey].getType(); - if (me.localType == "UNSPECIFIED") { - print("UpdateManager: warning ",hashkey," is ",ty, " excluding from update"); - me.property[hashkey] = nil; - } - if (me.localType == "INT" or me.localType == "LONG" or me.localType == "FLOAT" or me.localType == "DOUBLE") - return 1; - else - return 0; - }, - - FromPropertyHashList : func(_keylist, _delta, _changed_method) - { - var obj = {parents : [UpdateManager] }; - obj.hashkeylist = _keylist; - obj.delta = _delta; - obj.lastval = {}; - obj.hashkey = nil; - obj.changed = _changed_method; - obj.needs_update = 0; - obj.property = {}; - obj.is_numeric = {}; - foreach (hashkey; obj.hashkeylist) { - obj.property[hashkey] = props.globals.getNode(hashkey); - obj.lastval[hashkey] = nil; -# var ty = obj.property[hashkey].getType(); -# if (ty == "INT" or ty == "LONG" or ty == "FLOAT" or ty == "DOUBLE") { -# obj.is_numeric[hashkey] = 1; -# } else -# obj.is_numeric[hashkey] = 0; -#print("create: ", hashkey," ", ty, " isnum=",obj.is_numeric[hashkey]); -# if (ty == "UNSPECIFIED") -# print("UpdateManager: warning ",hashkey," is ",ty); - } - obj.update = func(obj) - { - if (me.lastval == nil) - me.needs_update = 1; - else { - me.needs_update = 0; - - foreach (hashkey; me.hashkeylist) { - if (me.property[hashkey] != nil) { - me.valIsNumeric = me.IsNumeric(hashkey); - - if (me.lastval[hashkey] == nil - or (me.valIsNumeric and (math.abs(me.lastval[hashkey] - me.property[hashkey].getValue()) >= me.delta)) - or (!me.valIsNumeric and (me.lastval[hashkey] != me.property[hashkey].getValue()))) { - me.needs_update = 1; - break; - } - } - } - } - if (me.needs_update) { - me.changed(me); - foreach (hashkey; me.hashkeylist) { - me.lastval[hashkey] = me.property[hashkey].getValue(); - } - } - } - ; - return obj; - }, - FromHashValue : func(_key, _delta, _changed_method) - { - var obj = {parents : [UpdateManager] }; - obj.hashkey = _key; - obj.delta = _delta; - obj.isnum = _delta != nil; - obj.curval = nil; - obj.lastval = nil; - obj.changed = _changed_method; - obj.update = func(obj) - { - me.curval = obj[me.hashkey]; - if (me.curval != nil) { - if (me.isnum) { - me.curval = num(me.curval); - if (me.lastval == nil or math.abs(me.lastval - me.curval) >= me.delta) { - me.lastval = me.curval; - me.changed(me.curval); - } - } else { - if (me.lastval == nil or me.lastval != me.curval) { - me.lastval = me.curval; - me.changed(me.curval); - } - } - } - } - ; - return obj; - }, - FromHashList : func(_keylist, _delta, _changed_method) - { - var obj = {parents : [UpdateManager] }; - obj.hashkeylist = _keylist; - obj.delta = _delta; - obj.lastval = {}; - obj.hashkey = nil; - obj.changed = _changed_method; - obj.needs_update = 0; - obj.isnum = _delta != nil; - obj.update = func(obj) - { - if (me.lastval == nil) - me.needs_update = 1; - else - me.needs_update = 0; - - if (obj != nil or me.lastval == nil) { - foreach (hashkey; me.hashkeylist) { - if (me.isnum) { - if (me.lastval[hashkey] == nil or math.abs(me.lastval[hashkey] - obj[hashkey]) >= me.delta) { - me.needs_update = 1; - break; - } - } elsif (me.lastval[hashkey] == nil or me.lastval[hashkey] != obj[hashkey]) { - me.needs_update = 1; - break; - } - } - } - if (me.needs_update) { - me.changed(obj); - foreach (hashkey; me.hashkeylist) { - me.lastval[hashkey] = obj[hashkey]; - } - } - }; - return obj; - }, -}; diff --git a/test/props_sim.nas b/test/props_sim.nas deleted file mode 100644 index 29b9d41..0000000 --- a/test/props_sim.nas +++ /dev/null @@ -1,176 +0,0 @@ -var geodinfo=func(lat,lon){ - return [nil,{ - names:["Road","Freeway"] - }]; -} - -var props= -{ - globals:nil, - Node:nil, - getNode:func(path,index) - { - path=split('/',path); - var tmp=me.globals; - var path_size=size(path); - for(var i=0;i0) - { - if(contains(tmp.val,path[i]~'['~index~']')) - return tmp.val[path[i]~'['~index~']']; - else - return tmp.val[path[i]]; - } - return tmp; - } -}; - -props.Node= -{ - new:func(values=nil) - { - var result={ - parents:[props.Node], - val:{}, - type:'GHOST', - parent:nil - }; - if(typeof(values)=="hash") - result.val=values; - return result; - }, - addChild:func(name) - { - if(!contains(me.val,name)) - { - me.val[name]=props.Node.new(); - me.val[name].parent=me; - return 1; - } - return 0; - }, - addChildren:func(name,cnt=0) - { - for(var i=0;itutorial'); -props.getNode('/test/in',3).setValue('/',2147483648); - -props.getNode("/sim",1).addChild("messages"); -props.getNode("/sim/messages",1).addChild("copilot"); -props.getNode("/position",1).addChild("latitude-deg"); -props.getNode("/position",1).addChild("longitude-deg"); -props.getNode("/orientation",1).addChild("heading-deg"); -props.getNode("/controls",1).addChild("flight"); -props.getNode("/controls/flight",1).addChild("rudder"); - -props.getNode("/sim/messages/copilot",1).setValue('/',"nothing"); -props.getNode("/position/latitude-deg",1).setValue('/',90); -props.getNode("/position/longitude-deg",1).setValue('/',90); -props.getNode("/orientation/heading-deg",1).setValue('/',90); -props.getNode("/controls/flight/rudder",1).setValue('/',0.114); -props.globals.debug(); - -println("-----------------------------------"); diff --git a/test/utf8chk.nas b/test/utf8chk.nas index 4ca6e4d..497ba2f 100644 --- a/test/utf8chk.nas +++ b/test/utf8chk.nas @@ -25,8 +25,25 @@ var emojiζ΅‹θ―•=func(){ var 🍾="🍾ε₯½δΌΌοΌŒεΌ€πŸΎε’―"; var 🐘="🐘ε€ͺπŸš¬πŸ˜δΊ†ε…„εΌŸδ»¬"; var πŸ“=[🀣,πŸ˜…,🀀,πŸ₯΅,πŸ₯Ά,🀒,πŸ€“,😭,πŸ‘Ώ,🀑,πŸ’©,🍾,🐘]; + var πŸ—„οΈ={ + 🀣:🀣, + πŸ˜…:πŸ˜…, + 🀀:🀀, + πŸ₯΅:πŸ₯΅, + πŸ₯Ά:πŸ₯Ά, + 🀒:🀒, + πŸ€“:πŸ€“, + 😭:😭, + πŸ‘Ώ:πŸ‘Ώ, + 🀑:🀑, + πŸ’©:πŸ’©, + 🍾:🍾, + 🐘:🐘 + }; foreach(var πŸ“„;πŸ“) πŸ’»(πŸ“„,🎀); + foreach(var πŸ“„;keys(πŸ—„οΈ)) + πŸ’»(πŸ“„,πŸ—„οΈ[πŸ“„],🎀); } unicodeζ΅‹θ―•(); emojiζ΅‹θ―•(); \ No newline at end of file