Suppose I have a function in Idris that does some computation. For simplicity, let it be stringly typed for now.
f: String -> String
How can I compile this function to JavaScript so that it can then be called from any ordinary JavaScript code?
If that is too easy, suppose f, instead of String, deals with Double or even a custom Idris data type.
I know I can compile a whole module with a Main.main function and a more or less inscrutable blob of JavaScript will be output. Can I maybe extract my function from there by hand? How should I go about it?
P.S. Despite my answering myself, I am still looking for a better solution, so welcome.
Using this example, it seems at least with the Node backend this is doable. I've marked interact as export and added a library descriptor:
module Main
import Data.String
f: Double -> Double
f x = x + 1
export interact: String -> String
interact s = let x = parseDouble s in
case x of
Nothing => "NaN"
Just x => show (f x)
main: IO ()
main = do
s <- getLine
putStrLn (interact s)
lib : FFI_Export FFI_JS "" []
lib = Data String "String" $
Fun interact "interact" $
Fun main "main" $
End
I have then compiled with the --interface flag (this fails with --codegen javascript...):
idris --codegen node --interface --output ExportToJS.js ExportToJS.idr
and the resulting .js file has this at the end:
module.exports = {
interact: Main__interact,
main: Main__interact
};
}.call(this))
This should allow you to do require("./ExportToJavaScript.js").interact("42") from Node, and there is probably an equivalent to use from a browser.
Yes, you can extract any function by hand.
Build a module as follows:
module Main
import Data.String
f: Double -> Double
f x = x + 1
interact: String -> String
interact s = let x = parseDouble s in
case x of
Nothing => "NaN"
Just x => show (f x)
main: IO ()
main = do
s <- getLine
putStrLn (interact s)
Compile it as follows:
% idris --codegen javascript --output Main.js Main.idr
A file called Main.js will be created. There will be several megabytes of more or less inscrutable JavaScript code, just as you say.
Edit this file by hand and edit it similarly to this:
--- Resistors.js
+++ Resistors-default.js
## -1,7 +1,5 ##
"use strict";
-(function(){
-
const $JSRTS = {
throw: function (x) {
throw x;
## -36130,7 +36128,3 ##
}
}
}
-
-
-$_0_runMain();
-}.call(this))
Now notice this JS file has comments in it marking the JS functions with their Idris names. For instance, corresponding to our interact function there will be located this JS function:
// Main.interact
function Main__interact($_0_arg){
const $_1_in = Data__String__parseDouble($_0_arg);
if(($_1_in.type === 1)) {
const $cg$3 = Main__bestMatch_39_($_1_in.$1, Main__manyResistors_39_());
let $cg$2 = null;
$cg$2 = $cg$3.$1;
return Prelude__Show__Main___64_Prelude__Show__Show_36_Schema_58__33_show_58_0($cg$2);
} else {
return "NaN";
}
}
If you attach this JS file to a web page as a script, you may then open JS console in a browser and interact with your Idris functions, like this:
Main__interact("10")
"11"
Hope this helps!
Related
Suppose I have an Angular application that performs calculations between different parameters. The number of parameters can change, as well as the relations between.
To simplify, say users operates with 3 parameters fill-in A, B to obtain X and Y, having the formulas X = A + B and Y = A * X.
The application has one js file per calculated parameter. In that case A and B are filled in by user, and X and Y - calculated.
x.js
export function X(A: number, B: number): number {
return A + B;
}
y.js
export function Y(A: number, X: number): number {
return A * X;
}
The formulas are supposed to change over time. So I would like to let some special users to be able to modify the formula, and then preview/test the result.
A user could then load a js formula, modify it, and try it. In that mode, the function X() should not be loaded from the x.js file, but from the user modified text in editor.
My question is if there is a way to temporarily replace a JS function used by angular from a js file with some js code modified by user in the browser. That behavior should not affect other web application users.
It greatly depends on how you declare your functions.
But yes, it's possible.
All you need to have is a way to access your function. It must NOT be global (i.e. you have to call it with, for instance, window.myFunction).
Here is an example :
window.myFn = (a, b) => a + b;
console.log('Original function : ' + window.myFn(2, 3));
const symbol = window.myFn;
window.myFn = (a, b) => a * b;
console.log('Changed function : ' + window.myFn(2, 3));
window.myFn = (a, b, c) => symbol(a, b) * c;
console.log('Extended function : ' + window.myFn(2, 3, 4));
Note that you will have many context issues depending on how you wrote your functions, for instance in Angular, something like this
export class Component {
myFn() {}
}
Should be overwritten like this
export class Component {
constructor() { window.myFn = this.myFn.bind(this); }
myFn() {}
}
I am wrting a plain .env file as following:
VAR1=VAL1
VAR2=VAL2
I wonder if there's some module I can use in NodeJS to have some effect like :
somefunction(envfile.VAR1) = VAL3
and the resulted .env file would be
VAR1=VAL3
VAR2=VAL2
i.e., with other variables unchanged, just update the selected variable.
You can use the fs, os module and some basic array/string operations.
const fs = require("fs");
const os = require("os");
function setEnvValue(key, value) {
// read file from hdd & split if from a linebreak to a array
const ENV_VARS = fs.readFileSync("./.env", "utf8").split(os.EOL);
// find the env we want based on the key
const target = ENV_VARS.indexOf(ENV_VARS.find((line) => {
return line.match(new RegExp(key));
}));
// replace the key/value with the new value
ENV_VARS.splice(target, 1, `${key}=${value}`);
// write everything back to the file system
fs.writeFileSync("./.env", ENV_VARS.join(os.EOL));
}
setEnvValue("VAR1", "ENV_1_VAL");
.env
VAR1=VAL1
VAR2=VAL2
VAR3=VAL3
Afer the executen, VAR1 will be ENV_1_VAL
No external modules no magic ;)
I think the accepted solution will suffice for most use cases, but I encountered a few problems while using it personally:
It will match keys that is prefixed with your target key if it is found first (e.g. if ENV_VAR is the key, ENV_VAR_FOO is also a valid match).
If the key does not exist in your .env file, it will replace the last line of your .env file. In my case, I wanted to do an upsert instead of just updating existing env var.
It will match commented lines and update them.
I modified a few things from Marc's answer to solve the above problems:
function setEnvValue(key, value) {
// read file from hdd & split if from a linebreak to a array
const ENV_VARS = fs.readFileSync(".env", "utf8").split(os.EOL);
// find the env we want based on the key
const target = ENV_VARS.indexOf(ENV_VARS.find((line) => {
// (?<!#\s*) Negative lookbehind to avoid matching comments (lines that starts with #).
// There is a double slash in the RegExp constructor to escape it.
// (?==) Positive lookahead to check if there is an equal sign right after the key.
// This is to prevent matching keys prefixed with the key of the env var to update.
const keyValRegex = new RegExp(`(?<!#\\s*)${key}(?==)`);
return line.match(keyValRegex);
}));
// if key-value pair exists in the .env file,
if (target !== -1) {
// replace the key/value with the new value
ENV_VARS.splice(target, 1, `${key}=${value}`);
} else {
// if it doesn't exist, add it instead
ENV_VARS.push(`${key}=${value}`);
}
// write everything back to the file system
fs.writeFileSync(".env", ENV_VARS.join(os.EOL));
}
It looks like - you want to read your current .env file, after you want to change some values and save it.
You should use the fs module from standard Node.js module library: https://nodejs.org/api/fs.html
var updateAttributeEnv = function(envPath, attrName, newVal){
var dataArray = fs.readFileSync(envPath,'utf8').split('\n');
var replacedArray = dataArray.map((line) => {
if (line.split('=')[0] == attrName){
return attrName + "=" + String(newVal);
} else {
return line;
}
})
fs.writeFileSync(envPath, "");
for (let i = 0; i < replacedArray.length; i++) {
fs.appendFileSync(envPath, replacedArray[i] + "\n");
}
}
I wrote this function to solve my issue.
Simple and it works:
for typescript
import fs from 'fs'
import os from 'os'
import path from 'path'
function setEnvValue(key: string, value: string): void {
const environment_path = path.resolve('config/environments/.env.test')
const ENV_VARS = fs.readFileSync(environment_path, 'utf8').split(os.EOL)
const line = ENV_VARS.find((line: string) => {
return line.match(`(?<!#\\s*)${key}(?==)`)
})
if (line) {
const target = ENV_VARS.indexOf(line as string)
if (target !== -1) {
ENV_VARS.splice(target, 1, `${key}=${value}`)
} else {
ENV_VARS.push(`${key}=${value}`)
}
}
fs.writeFileSync(environment_path, ENV_VARS.join(os.EOL))
}
I have a component that needs strings from the backend. I currently request the .po file from the server, convert it to .json and return it to my React component. I then want to be able to display those strings whilst replacing the correct values in the string, i.e.
<FormattedMessage id={dynamicId} values={dynamicVals} />
dynamicId is pulled from a separate api call, as well as dynamicVals.
My problem is that these strings are not bundled like all of my other app strings, so react-intl is unaware of them. How can I add these strings to the library client-side/async? I've attempted using defineMessages and addLocaleData, but I either am doing something incorrectly, or am not using the right api methods. Does addLocaleData provide the means to adding strings to the library? Is this possible to do?
In summary:
How can I receive
{
notifications.friendships.nowfriends: "{name} is now your friend"
}
from the api and display it using:
<FormattedMessage id='notifications.friendships.nowfriends' values={{ name: 'StackOver Bro' }} />
Thanks for the help in advance.
In case anyone wants to know what I ended up doing...
Since I already had the strings and the variables to interpolate with, I bypassed the localization library and just used the following function (thanks to the answers on this question, especially #user2501097 and #Bryan Rayner)
/**
* see: https://stackoverflow.com/questions/29182244/convert-a-string-to-a-template-string
*
* Produces a function which uses template strings to do simple interpolation from objects.
*
* Usage:
* var makeMeKing = generateTemplateString('${name} is now the king of {country}!');
*
* console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
* // Logs 'Bryan is now the king of Scotland!'
*/
const generateTemplateString = (function () {
const cache = {}
function generateTemplate(template) {
let fn = cache[template]
if (!fn) {
// Replace ${expressions} (etc) with ${map.expressions}.
const sanitized = template
.replace(/\$?\{([\s]*[^;\s\{]+[\s]*)\}/g, (_, match) => {
return `\$\{map.${match.trim()}\}`
})
// Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
.replace(/(\$\{(?!map\.)[^}]+\})/g, '')
fn = Function('map', `return \`${sanitized}\``)
cache[template] = fn
}
return fn
}
return generateTemplate
}())
export default generateTemplateString
I'm trying to parse some Javascript libraries with Clojure to generate externs files for using them in ClojureScript.
Clojure read-file function
I stumbled across and old Clojure source file that intended to do this, but has some deprecated code and doesn't work anymore, so I wanted to take some concepts from it and start over.
From the script of the Gists above I started with the read-file function.
(ns n01se.externs-for-cljs
(:require [clojure.java.io :as io]
[cljs.compiler :as comp]
[cljs.analyzer :as ana]))
(defn read-file [file]
(let [eof (Object.)]
(with-open [stream (clojure.lang.LineNumberingPushbackReader. (io/reader file))]
(vec (take-while #(not= % eof)
(repeatedly #(read stream false eof)))))))
First Question: why eof is represented by a plain Object ?
Parsing Javascript
Then I fed this Javascript source code to the read-test-file function:
yayQuery = function() { // <= EXCEPTION: read exception at {
var yay = {};
yay.sayHello = function(message) {
console.log(message);
}
yay.getMessage = function() {
return 'Hello, world!';
}
return yay;
};
I startup the cider debugger and start checking the loop and as expected I get yayQuery.
So I got:
yayQuery
=
function
()
Until the reader gets to {, and this exception pops up.
Map literal must contain an equal number of forms.
Prevent read from "evaluating" ?
Hypothesis: Looks like the read function tries to interpret { so starts searching for a key ?
Second Question: If the hypothesis above is true. Can I avoid read from evaluating the code ?
I tried binding *read-eval* to false but I still got the same exception:
(ns externer.core
(:require [clojure.java.io :as io])
(:gen-class))
(defn read-test-file [file]
(let [eof (Object.)]
(with-open [stream (clojure.lang.LineNumberingPushbackReader. (io/reader file))]
(binding [*read-eval* false]
(take-while #(not= % eof)
(repeatedly #(read stream false eof)))))))
(defn -main
[& args]
(read-test-file (first args)))
I also tried edn also because of the unsafety of using read but I still got the same exception as above.
(ns externer.core
(:require [clojure.java.io :as io])
(:gen-class))
(defn read-test-file [file]
(let [eof (Object.)]
(with-open [stream (clojure.lang.LineNumberingPushbackReader. (io/reader file))]
(take-while #(not= % eof)
(repeatedly #(edn/read {:eof eof} stream))))))
(defn -main
[& args]
(read-test-file (first args)))
Final Question: There's another more safe/correct way to generate externs automatically ?
I want to parse an input mathematical string. How do I do it in Dart (with three.dart)?
Here is the js (three.js) version:
zFunc = Parser.parse(zFuncText).toJSFunction( ['x','y'] );
meshFunction = function(x, y)
{
x = xRange * x + xMin;
y = yRange * y + yMin;
var z = zFunc(x,y); //= Math.cos(x) * Math.sqrt(y);
if ( isNaN(z) )
return new THREE.Vector3(0,0,0); // TODO: better fix
else
return new THREE.Vector3(x, y, z);
};
in short : "is not as simple as in JS"
Explanation :
if i understand well your need, you want to be able to parse string expression like :
String math_square_expr = "(x) => pow(x, 2)"
some_magic_func(math_squre_expr)(5) // return 25
but it's possible in javascript, because you have some function like 'eval()' and others thing that make easy to transform string to code.
In Dart, you don't have this kind of function. the situation is more like with Java for this case.
You have 2 choice:
Build your own mathematical parser -> it's not cool, but if you need only little stuff it's possible
Use Js to do the job -> another solution is to use Javascript to create the function, but executed with dart. here a example :
import 'dart:js';
void main() {
String function = "var f = function(x, y) { return x*x + y; }";
JsObject functionJs = context.callMethod("eval", [function]);
print(functionJs);
print(context.callMethod("f", [5, 2]));
}
explanation:
String function = "var f = function(x, y) { return x*x + y; }";
First i create a variable named function to simulate user input, it need to be replace by a call or other thing to get user input.
Important thing: i create a javascript function that i store in a javascript variable. This will create a global javascript symbol named f.
JsObject functionJs = context.callMethod("eval", [function]);
Here i call a javascript function "eval" to dynamic evaluate the string previously create. after this operation my global JS context will contain a variable named f, with a function store in it.
print(context.callMethod("f", [5, 2]));
Know than my function is created and i can access on it, i call my function store in f, with my 2 parameters (x=5, y=2) and i display the result.
Note: becareful the to named of the JS variable that you will store your function. take a unused named.