An interpreted languages written in Go


Go Report Card license Release


This repository contains an interpreter for the "Monkey" programming language, as described in Write an Interpreter in Go.

My changes

The interpreter in this repository has been significantly extended from the starting point:

  • Added single-line & multi-line comments.
  • Added postfix operators (i++, i--).
  • Allow accessing individual characters of a string via the index-operator.
  • Added a driver to read from STDIN, or a named file, rather than a REPL.
    • This allows executing the examples easily (for example "./monkey examples/hello.mon".)
  • Added a collection of standard-library functions.
    • Including file input/output, type-discovery, string, and math functions.
  • Added a new way to define functions, via function.
  • Added the general-purpose comparision functions <= & >=.
  • Allow string comparisons via ==, !=, <=, & >=.
  • Allow comparisions to be complex:
    • if ( a >= 'a' && a <= 'z' ) ..
    • if ( a || b ) ..
  • Allow assignments without let.
    • This also allows operators such as "+=", "-=", "*=", & "/=" to work.
  • Added command-line handling, so that scripts can read their own arguments.
  • Added global-constants available by default
    • For example PI, E, STDIN, STDOUT, & STDERR.
  • Most scripts will continue running in the face of errors.
    • To correct/detect "obvious" errors add pragma("strict"); to your script, which will cause the interpreter to show a suitable error-message and terminate.
  • Function arguments may have defaults. For example:
    • function greet( name = "World" ) { puts("Hello, " + name + "\n"); }
  • Moved parts of the standard-library to 100% pure monkey, rather than implementing it in go.
  • Added the eval function.
    • Which allows executing monkey-code from a string.
  • Improved error-reporting from the parser.
    • It will now show the line-number of failures (where possible).
  • Added support for regular expressions, both literally and via match
    • if ( name ~= /steve/i ) { puts( "Hello Steve\n"); }
  • Added support for ternary expressions.
  • Added support for creating arrays of consecutive integers via the range operator (1..10).
  • Added the ability to iterate over the contents of arrays, hashes, and strings via the foreach statement.
  • Added printf and sprintf primitives, which work as you would expect.
    • printf( "%d %s", 3, "Steve" );
  • Added support for switch statements, with block-based case expressions.
    • No bugs due to C-style "fall-through".

1. Installation

There are two ways to install monkey from source, depending upon the version of go you're using:

Source Installation go <= 1.11

If you're using go before 1.11 then the following command should fetch/update monkey, and install it upon your system:

 $ go get -u

Source installation go >= 1.12

If you're using a more recent version of go (which is highly recommended), you need to clone to a directory which is not present upon your GOPATH:

git clone
cd monkey
go install

Binary Releases

Alternatively you could install a binary-release, from the release page.

If you're an emacs user might also wish to install the monkey.el file, which provides syntax highlighting for monkey-scripts.

1.1 Usage

To execute a monkey-script simply pass the name to the interpreter:

 $ monkey ./example/hello.mon

Scripts can be made executable by adding a suitable shebang line:

 $ cat hello.mon
 #!/usr/bin/env monkey
 puts( "Hello, world!\n" );

Execution then works as you would expect:

 $ chmod 755 hello.mon
 $ ./hello.mon
 Hello, world!

If no script-name is passed to the interpreter it will read from STDIN and execute that instead, allowing simple tests to be made.

2 Syntax

NOTE: Example-programs can be found beneath examples/ which demonstrate these things, as well as parts of the standard-library.

2.1 Definitions

Variables are defined using the let keyword, with each line ending with ;.

  let a = 3;
  let b = 1.2;

Variables may be integers, floats, strings, or arrays/hashes (which are discussed later).

Some variables are defined by default, for example:

puts( PI ); // Outputs: 3.14159..
puts( E );  // Outputs: 2.71828..

Variables may be updated without the need for let, for example this works as you would expect:

let world = "Earth";
world = "world";
puts( "Hello, " + world + "!\n");

If you're not running with pragma("strict"); you can also declare and use variables without the need for let, but that should be avoided as typos will cause much confusion!

 name = "Steve";
 puts( "Hello, " + name + "\n");

2.2 Arithmetic operations

monkey supports all the basic arithmetic operation of int and float types.

The int type is represented by int64 and float type is represented by float64.

   let a = 3;
   let b = 1.2;

   puts( a + b  );  // Outputs: 4.2
   puts( a - b  );  // Outputs: 1.8
   puts( a * b  );  // Outputs: 3.6
   puts( a / b  );  // Outputs: 2.5
   puts( 2 ** 3 ) ; // Outputs: 8

Here ** is used to raise the first number to the power of the second. When operating with integers the modulus operator is available too, via %.

2.3 Builtin containers

monkey contains two builtin containers: array and hash.

2.3.1 Arrays

An array is a list which organizes items by linear sequence. Arrays can hold multiple types.

 let a = [1, 2.3, "array"];
 let b = [false, true, "Hello World", 3, 3.13];

Adding to an array is done via the push function:

 let a = push(a, "another");

You can iterate over the contents of an array like so:

 let i = 0;
 for( i < len(a) ) {
    puts( "Array index ", i, " contains ", a[i], "\n");

With the definition we included that produces this output:

 Array index 0 contains 1
 Array index 1 contains 2.3
 Array index 2 contains array
 Array index 3 contains another

As a helper you may define an array of consecutive integers via the range operator (..):

 let a = 1..10;

2.3.2 Hashes

A hash is a key/value container, but note that keys may only be of type boolean, int and string.

let a = {"name":"monkey",

puts(a); // Outputs: {name: monkey, true: 1, 7: seven}

puts(a["name"]); // Outputs: monkey

Updating a hash is done via the set function, but note that this returns an updated hash - rather than changing in-place:

let b = set(a, 8, "eight");
puts(b);  // Outputs: {name: monkey, true: 1, 7: seven, 8: eight}

You can iterate over the keys in a hash via the keys function, or delete keys via delete (again these functions returns an updated value rather than changing it in-place).

Hash functions are demonstrated in the examples/hash.mon sample.

2.4 Builtin functions

The core primitives are:

  • delete
    • Deletes a hash-key.
  • int
    • convert the given float/string to an integer.
  • keys
    • Return the keys of the specified array.
  • len
    • Yield the length of builtin containers.
  • match
    • Regular-expression matching.
  • pragma
    • Allow the run-time environment to be controlled.
    • We currently support only pragma("strict");.
  • push
    • push an elements into the array.
  • puts
    • Write literal value of objects to STDOUT.
  • printf
    • Write values to STDOUT, via a format-string.
  • set
    • insert key value pair into the map.
  • sprintf
    • Create strings, via a format-string.
  • string
    • convert the given item to a string.
  • type
    • returns the type of a variable.

The following functions are also part of our standard library, but are implemented in 100% pure monkey:

  • first
    • yield the first element of array.
  • last
    • yield the last element of array.
  • rest
    • yield an array which excludes the first element.

2.4.1 The Standard Library

In addition to the core built-in functions we also have a minimal-standard library. The library includes some string/file primitives, a regular-expression matcher, and some maths-helpers.

You can see the implementation of the go-based standard-library beneath evaluator/stdlib*, and several of these functions are documented in the various examples/.

NOTE: Parts of our standard-library are implemented in 100% pure monkey, and these are embedded in our compiled interpreter. The source of the functions can be viewed in data/stdlib.mon, but to ease compilation these are included in the compiled interpreter via static.go.

If you wish to make changes to the monkey-based standard-library you'll need to rebuild static.go after editing stdlib.mon. To do this use the implant tool.

If you don't already have implant installed fetch it like so:

 go get -u

Now regenerate the embedded version of the standard-library and rebuild the binary to make your changes:

implant -input data/ -output static.go
go build .

2.5 Functions

monkey uses fn to define a function which will be assigned to a variable for naming/invocation purposes:

let add = fn(a, b) { return a + b;};
puts(add(1,2));  // Outputs: 3

// functions can be used via their variables
let addTwo = fn(a,b, f) { return 2 + f(a, b);};
puts( addTwo(1,2, add) ); // outputs: 5.

It is also possible to define a function without the use of let, via the function keyword. This was added to make the language feel more natural to C-developers:

function hello() { puts "Hello, world\n" ; };
hello();   // Outputs: Hello, world" to the console.

You may specify a default value for arguments which are not provided, for example:

let foo = fn( name = "World!") {
  puts( "Hello, " + name + "\n" );

foo( "Steve" );

This will output what you expect:

Hello, World!
Hello, Steve

The same thing works for literal functions:

// Function with a default (string) argument
function meh( arg = "Steve" ) {
  puts( "Argument:", arg, " has type:", type(arg), "\n");

// Call it with no argument and the default will be used.

// But of course all the rest work just fine.
meh( 1 );
meh( 1/3.0 );
meh( "Steve" );
meh( [1,2,3,4] );
meh( {"Steve":"Kemp", true:1, false:0, 7:"seven"} );

2.6 If-else statements

monkey supports if-else statements.

let max = fn(a, b) {
  if (a > b) {
    return a;
  } else {
    return b;

puts( max(1, 2) );  // Outputs: 2

2.6.1 Ternary Expressions

monkey supports the use of ternary expressions, which work as you would expect with a C-background:

function max(a,b) {
  return( a > b ? a : b );

puts( "max(1,2) -> ", max(1, 2), "\n" );
puts( "max(-1,-2) -> ", max(-1, -2), "\n" );

Note that in the interests of clarity nested ternary-expressions are illegal!

2.7 Switch Statements

Monkey supports the switch and case expressions, as the following example demonstrates:

  name = "Steve";

  switch( name ) {
    case /^steve$/i {
       printf("Hello Steve - we matched you via a regexp\n");
    case "St" + "even" {
       printf("Hello SteveN, you were matched via an expression\n" );
    case 3 {
       printf("Hello number three, we matched you literally.\n");
    default {
       printf("Default case: %s\n", string(name) );

See also examples/switch.mon.

2.8 For-loop statements

monkey supports a golang-style for-loop statement.

 let sum = fn(x) {
    let i = 1;
    let sum = 0;

    for (i < x) {
       sum += i;
    return sum;

 puts(sum(100));  // Outputs: 4950

2.8.1 Foreach statements

In addition to iterating over items with the for statement, as shown above, it is also possible to iterate over various items via the foreach statement.

For example to iterate over an array:

 a = [ "My", "name", "is", "Steve" ]
 foreach item in a {
      puts( "\t",  item , "\n");

Here you see that we've iterated over the items of the array, we can also see their offsets like so:

 foreach offset, item in a {
      puts( offset, "\t",  item , "\n");

The same style of iteration works for Arrays, Hashes, and the characters which make up a string. You can see examples of this support in examples/iteration.mon.

When iterating over hashes you can receive either the keys, or the keys and value at each step in the iteration, otherwise you receive the value and an optional index.


monkey support two kinds of comments:

  • Single-line comments begin with // and last until the end of the line.
  • Multiline comments between /* and */.

2.10 Postfix Operators

The ++ and -- modifiers are permitted for integer-variables, for example the following works as you would expect showing the numbers from 0 to 5:

let i = 0;
for ( i <= 5 ) {
   puts( i, "\n" );

Another feature borrowed from C allows variables to be updated in-place via the operators +=, -=, *=, & /=.

Using += our previous example could be rewritten as:

let i = 0;
for ( i <= 5 ) {
   puts( i, "\n" );
   i += 1;

The update-operators work with integers and doubles by default, when it comes to strings the only operator supported is +=, allowing for a string-append:

let str = "Forename";
str += " Surname";
str += "\n";
puts( str );           // -> "Forename Surname\n"

2.11 Command Execution

As with many scripting languages commands may be executed via the backtick operator (```).

  let uptime = `/usr/bin/uptime`;

  if ( uptime ) {
      puts( "STDOUT: ", uptime["stdout"].trim() , "\n");
      puts( "STDERR: ", uptime["stderr"].trim() , "\n");
  } else {
      puts( "Failed to run command\n");

The output will be a hash with two keys stdout and stderr. NULL is returned if the execution fails. This can be seen in examples/exec.mon.

2.12 Regular Expressions

The match function allows matching a string against a regular-expression.

If a match fails NULL will be returned, otherwise a hash containing any capture groups in the match.

This is demonstrated in the examples/regexp.mon example.

You can also perform matching (complete with captures), with a literal regular expression object:

if ( Name ~= /steve/i ) { puts( "Hello Steve\n" ); }
if ( Name !~ /[aeiou]/i ) { puts( "You have no vowels.\n" ); }

// captures become $1, $2, $N, etc.
ip = "";
if ( ip ~= /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/  ) {
    printf("Matched! %s.%s.%s.%s\n", $1, $2, $3, $4 );

2.13 File I/O

The open primitive is used to open files, and can be used to open files for either reading, or writing:

// Open a file for reading
fh = open( "/etc/passwd" );
fh = open( "/etc/passwd", "r" );

// Open a file for writing
fh = open( "/tmp/blah", "w" );

// Open a file for appending
fh = open( "/tmp/blah", "wa" );

Once you have a file-object you can invoke methods upon it:

  • read()
    • Read a line of input, returning that input as a string.
  • readlines()
    • Read the lines of the given file, and return them as an array.
  • write(data)
    • Write the data to the given file.

These are demonstrated in the following examples:

By default three filehandles will be made available, as constants:

    • Use for reading STDIN.
    • Used for writing messages.

2.14 File Operations

The primitive stat will return a hash of details about the given file, or directory entry.

You can change the permissions of a file via the chmod function, but note that the second argument is an octal string:

chmod( "/tmp/", "755")
chmod( "/tmp/normal", "644")

To remove a file, use unlink:

unlink( "/tmp/trash.txt" )

And finally to make a directory:

mkdir( "/tmp/blah" );

3. Object Methods

There is now support for "object-methods". Object methods are methods which are defined against a type. For example all of our primitive types allow a methods() method, which returns the methods which are available against them.

Similarly each of them implement a type() function which returns the type involved:

let i = 1;
puts( i.type() );

let s = "Steve";
puts( s.type() );

Or even:

puts( "Steve".type() );

Seeing methods available works as you would expect:

a = [ "Array", "Is", "Here" ];

let i = 0;
for ( i < len(a.methods() ) ) {
   puts( "Method " + a.methods()[i] + "\n" );

This shows:

Method find
Method len
Method methods
Method string

The string object has the most methods at the time of writing, but no doubt things will change over time.

3.1 Defininig New Object Methods

The object-methods mentioned above are implemented in Go, however it is also possible to define such methods in 100% monkey!

You can define a method via something like:

function string.steve() {
   puts( "Hello, I received '", self, "' as an argument\n" );

Note that the function has access to the object it was invoked upon via the implicit self name. Invocation would look as you expect:

let s = "Hello, world";
s.steve();   -> Hello, I received 'Hello, world' as an argument

You can see data/stdlib.mon implements some primitives in this fashion, for example the functional-programming methods, array.filter, string.toupper, etc, etc.

Github Setup

This repository is configured to run tests upon every commit, and when pull-requests are created/updated. The testing is carried out via .github/ which is used by the github-action-tester action.

Releases are automated in a similar fashion via .github/build, and the github-action-publish-binaries action.


  • Allow object-based method-invocation.

    Allow object-based method-invocation.

    This pull-request will be complete when we allow methods to be invoked upon objects/types.

    It is currently a work-in-progress, but once complete and merged it will close #23.

    opened by skx 3
  • 53 regexp

    53 regexp

    This pull request will close #53, by implementing support for regular expressions as objects.

    Currently the following program works:

    frodo ~/Repos/ $ cat reg.mon
    x = /steve/i;
    puts( x, " is type:" , type(x), "\n" );

    It produces:

    frodo ~/Repos/ $ go build . ; ./monkey reg.mon 
    (?i)steve is type:regexp

    Note here the flag /i has been converted to the version go prefers, the (?i) prefix.

    Outstanding task is mostly to implement !~ and =~ operators.

    opened by skx 2
  • Add more file/directory primitives.

    Add more file/directory primitives.

    #47 covers a reworking of file/directory handling.

    In addition to the work described there we should add:

    • file.stat()
      • As mentioned.
    • unlink
      • Delete a file/directory.
    • mkdir
      • Make a directory.
      • mkdir( "/tmp/foo/bar", { "mode": "0755" } )
    opened by skx 2
  • We could rethink our environmental variables.

    We could rethink our environmental variables.

    At the moment all environmental variables are imported as variables with a $-prefix.

    For example this shows your current username, assuming a Unix system:

      puts( "Hello ", $USER , "\n");


      puts( "Hello " + $USER + "\n");

    However this importing is read-only. So when you execute processes you cannot change the values.

    It might make more sense to clone environmental variables to a hash:

      puts( "The PATH is:" ,ENV["PATH"], " You are :" , ENV["USER"], "\n" );

    Using a hash would still allow iteration, via keys, but we could add special handling such that writing ot the ENV-hash would allow updating the contents of an environmental variable.

    I'm not 100% sure if this would be a useful change, but it feels like it might be useful?

    opened by skx 2
  • Added

    Added "EvalContext"

    Having a global Context can be useful, but causes some issues.

    • Can only be used with a single Monkey script instance (Canceling one would cancel the others or if the base CTX wasn't replaced properly)
    • Could allow for a panic if a caller sets CTX to nil before execution (example could be bad cleanup/replacement of previous runs)

    Purposed a new function "EvalContext", inline with Go compatibility guidelines such as "exec.Command" here that would allow for non-breaking updates. This new function would perform the work "Eval" previously did and the "Eval" function would call this function with the background context to keep with previous execution behavior.

    opened by iDigitalFlame 1
  • Ability to cancel a running Monkey Script

    Ability to cancel a running Monkey Script

    Was trying to find a way to cancel a running Monkey script (in code directly, not command line based) and I don't see any method to do this properly.

    Could there be a possibility of using a Context or something to stop execution in progress? A change to the Eval function like below would solve some of the issue (The function would bail after the current operation completes)

    func Eval(x context.Context, node ast.Node, env *object.Environment) object.Object {
            switch node := node.(type) {
                    select {
                            case <-x.Done():
                                    return &object.Error{Message: x.Err().Error()}


    enhancement good first issue 
    opened by iDigitalFlame 1
  • call function from go

    call function from go

    Hello! How do call function from go?

    env := object.NewEnvironment()
    l := lexer.New(`let add = fn(a, b) { return a + b;}; gofn(add, 2, 3)`)
    p := parser.New(l)
    program := p.ParseProgram()
    if len(p.Errors()) != 0 {
    	for _, msg := range p.Errors() {
    		fmt.Printf("\t%s\n", msg)
    	func(env *object.Environment, args ...object.Object) object.Object {
    		var add = args[0].(*object.Function)
    		return add.Invoke(env, args[1:]...) // ?
    opened by moisespsena 1
  • 70 switch

    70 switch

    Once complete this pull-request will implement support for switch statements, which will close #70.

    Our switch statement will allow an arbitrary number of case expressions, allowing different blocks of code to be executed depending upon the match.

    We allow one default branch in case nothing matches, but this is optional (it is an error to have multiple default cases).

    Note that we avoid any confusion with C due to using blocks explicitly, there can be no "fall-thru".

    opened by skx 1
  • Add regular expression support

    Add regular expression support

    This should work:

     if ( "Steve" ~= /steve/i ) {

    We should also see:

      print( type( /foo/ ) ) ;   -> "regexp"

    (So it is a distinct type, not a string.)

    opened by skx 1
  • Sorting an array of booleans fails ..

    Sorting an array of booleans fails ..

    Calling this code led me on a chase:

     [ true, false, true ].uniq();

    array.uniq does everything else, up to the point where it sorts the keys of the (temporary) hash it used.

    The comparison makes no sense:

    if ( true < false ) { ... }

    So either :

    • Disallow this.
    • Fake an order (false comes first?)
    • Don't sort the array when the types are mixed/bogus.

    Suspect there's no real right/wrong answer here. Just a choice to be made.

    opened by skx 1
  • Nested for-loops contain an error

    Nested for-loops contain an error

    This code works:

    function array.sort() {
       let pass = 0;
       for( ! self.sorted?() ) {
          let i = 1;
          let l = len(self);
          for( i < l ) {
             if ( self[i] < self[i-1] ) {
               self = self.swap( i-1, i);
          pass +=1;
       return self;

    However remove the use of pass and it panics. Seems to be the return-value of the for looks is causing isseus - suspect it doesn't return a value and it should.

    opened by skx 1
Steve Kemp
Steve Kemp
gpython is a python interpreter written in go "batteries not included"

gpython gpython is a part re-implementation / part port of the Python 3.4 interpreter to the Go language, "batteries not included". It includes: runti

go-python 659 Dec 28, 2022
A BASIC interpreter written in golang.

05 PRINT "Index" 10 PRINT "GOBASIC!" 20 PRINT "Limitations" Arrays Line Numbers IF Statement DATA / READ Statements Builtin Functions Types 30 PRINT "

Steve Kemp 289 Dec 24, 2022
PHP parser written in Go

PHP Parser written in Go This project uses goyacc and ragel tools to create PHP parser. It parses source code into AST. It can be used to write static

Vadym Slizov 899 Dec 25, 2022
High-performance PHP application server, load-balancer and process manager written in Golang

RoadRunner is an open-source (MIT licensed) high-performance PHP application server, load balancer, and process manager. It supports running as a serv

Spiral Scout 6.9k Dec 30, 2022
Scriptable interpreter written in golang

Anko Anko is a scriptable interpreter written in Go. (Picture licensed under CC BY-SA 3.0, photo by Ocdp) Usage Example - Embedded package main impor

mattn 1.3k Dec 23, 2022
A compiler for the ReCT programming language written in Golang

ReCT-Go-Compiler A compiler for the ReCT programming language written in Golang

null 6 Nov 30, 2022
Show Languages In Code. A fast and lightweight CLI to generate stats on the languages inside your project

slic Show Languages In Code. Usage Run it with an -h flag to list all commands. -d flag can be used to specify the directory of search -i flag can be

Saurav Pal 3 Dec 25, 2021
Golem is a general purpose, interpreted scripting language.

The Golem Programming Language Golem is a general purpose, interpreted scripting language, that brings together ideas from many other languages, inclu

Mike Jarmy 1 Sep 28, 2022
Dabulang is an interpreted object-oriented programming language aimed towards game development.

Dabulang (ダブ言語) Dabulang is an interpreted object-oriented programming language aimed towards game development. The language's standard library has a

Job Vonk 0 Sep 14, 2022
Sand is the next, versatile, high-level compiled or interpreted language that's easy to learn and performant to run.

Sand is the newest, dynamically typed, interpreted programming language. Table of Contents History Project Stats History Sand was created as part of @

Neuron AI 4 Mar 13, 2022
Interpreted Programming Language built in Go. Lexer, Parser, AST, VM.

Gago | Programming Language Built in Go if you are looking for the docs, go here Gago is a interpreted programming language. It is fully written in Go

Glaukio 4 May 6, 2022
slang 🐕‍🦺 | a Programing language written to understand how programing languages are written

slang ??‍?? goal was to learn how a interpreter works, in other works who does these programing languages i use on daily basis works behind the seen h

Ankit Yadav 1 Nov 18, 2021
Translate your Go program into multiple languages.

go-i18n go-i18n is a Go package and a command that helps you translate Go programs into multiple languages. Supports pluralized strings for all 200+ l

Nick Snyder 2.3k Jan 1, 2023
URL-friendly slugify with multiple languages support.

slug Package slug generate slug from unicode string, URL-friendly slugify with multiple languages support. Documentation online Example package main

GoSimple 903 Jan 4, 2023
Produces a set of tags from given source. Source can be either an HTML page, Markdown document or a plain text. Supports English, Russian, Chinese, Hindi, Spanish, Arabic, Japanese, German, Hebrew, French and Korean languages.

Tagify Gets STDIN, file or HTTP address as an input and returns a list of most popular words ordered by popularity as an output. More info about what

ZoomIO 26 Dec 19, 2022
DockerSlim (docker-slim): Don't change anything in your Docker container image and minify it by up to 30x (and for compiled languages even more) making it secure too! (free and open source)

Minify and Secure Docker containers (free and open source!) Don't change anything in your Docker container image and minify it by up to 30x making it

docker-slim 15.7k Dec 27, 2022
Tool that can parse Go files into an abstract syntax tree and translate it to several programming languages.

GoDMT GoDMT, the one and only Go Data Model Translator. The goal of this project is to provide a tool that can parse Go files that include var, const,

Josep Jesus Bigorra Algaba 44 Nov 28, 2022
libraries for various programming languages that make it easy to generate per-process trace files that can be loaded into chrome://tracing

chrometracing: chrome://tracing trace_event files The chrometracing directory contains libraries for various programming languages that make it easy t

Google 23 Oct 6, 2022
Lightweight static analysis for many languages. Find bug variants with patterns that look like source code.

Lightweight static analysis for many languages. Find bugs and enforce code standards. Semgrep is a fast, open-source, static analysis tool that finds

r2c 7.6k Jan 9, 2023
Distributed web crawler admin platform for spiders management regardless of languages and frameworks. 分布式爬虫管理平台,支持任何语言和框架

Crawlab 中文 | English Installation | Run | Screenshot | Architecture | Integration | Compare | Community & Sponsorship | CHANGELOG | Disclaimer Golang-

Crawlab Team 9.5k Jan 7, 2023