freenode/#lisp - IRC Chatlog
Search
12:17:57
lxbarbosa
I did know that Reddit was written in Lisp before switching to Python then to React... :d
13:25:07
decent-username
Good morning. I'm pretty close to finishing the Line Of Sight algorithm for my rogue like. Right now I have a 2D array that cotains objects of typr 'cell which encapsulate things like "is this cell visible". My question is: How can I change the printed representation of an object?
13:55:15
White_Flame
it's a plain function, you could look up stuff in external tables as well; it doesn't need to be self-contained on the object itself, if it's easier for you to manage differences there
14:01:47
minion
The URL https://gitlab.common-lisp.net/users/sign_in?secret=7cebaac8 will be valid until 14:15 UTC.
18:27:17
vms14
dlowe: why would be better to have html with some way for lisp code interpolation, instead of stuff like cl-who which are directly lisp code?
18:28:03
vms14
it seems at first time html would be better, but maybe letter you'd be happier with a more lispy solution
18:30:37
dlowe
vms14: if you are working with a web design team, they will not use the lispy solution that you claim is superior
18:31:24
Xach
I use html-template and cl-who together, but I bet there are newer, nicer options for both sides.
18:38:45
Xach
vms14: the evaluation rules have to be memorized and internalized if you want to use it effectively. they are not too complicated but you *must* know them by heart or you will get confused.
18:51:24
pjb
vms14: https://github.com/informatimago/lisp/blob/master/common-lisp/html-generator/html-generators-in-lisp.txt
18:58:33
jmercouris
Ober: you can read more about them here: https://en.wikipedia.org/wiki/S-expression
19:43:01
vms14
macro-html depends on named-readtables and shadows map and time as those symbols collide with HTML element names
19:45:07
aeth
cl-who afaik walks through all of your source code within the macro and substitutes anything inside (htm ...) with the appropriate transformations to string writing functions... so it's possibly the most efficient approach, but I personally hate when there's magic like that.
19:45:38
aeth
Having to hardcode HTML tags doesn't seem like a robust approach because, as I said, it's a "living standard" so now you have to constantly update your library. You do have to hardcode what the empty elements like <br> are, though.
19:47:15
aeth
It seems really weird to define a macro for every html element instead of just using keywords and handling it with one macro like (with-html (stream) (:foo 42)) and, as you noticed, it does have potential name conflicts
19:48:28
aeth
and when you have each thing be its own macro, you have to do something like rely on rebinding *standard-output* like that library does, I guess. Because it'd be really inconvenient to have to pass in a stream to every BR
19:49:21
aeth
vms14: you don't need to know about any tags to write HTML except for the empty elements like <br>
19:51:18
aeth
vms14: if you don't know that the tag exists, and if you do it in one big keyword macro instead of namespacing each tag in macros (or function calls or w/e) that have to know each tag... then, yes, if it's unknown it's just <tag>...</tag>
19:51:22
vms14
but if you're making a library for doing this, better if we provide access or a way to add tags
19:53:27
vms14
if you write a big macro then you need to provide a way to put stuff inside <tag > like :src "url"
19:55:03
aeth
there are two syntaxes that work... (:tag :src "url" "foobar") and (:tag (:src "url") "foobar) where in the former case you iterate until you no longer get a keyword-and-value pair and that's the end of the attributes and in the latter case you always have an attributes list after every tag (more regular, but also means your code will probably be full of ()s)
19:58:57
aeth
vms14: The reason you need to know about empty elements is because (1) they close like <foo> or <foo /> depending on the style and not <foo> </foo> and (2) you want there to be an error if there's a body and not just attributes
19:59:30
aeth
For all I know, in XML <foo /> and <foo> </foo> are interchangable, but HTML is not that kind.
20:00:16
aeth
The lazy approach that you'd probably see in 95% of libraries that do this is to use a list.
20:00:53
aeth
What you should probably do, though, is you should probably use a member type, and for user configuration, also check a predicate function (perhaps an overridable method that by default turns nil? or maybe even just put it as a satisfies type)
20:01:59
aeth
a higher order function could also work, but that makes the macro much messier if you need to override it so maybe just a method that always returns nil and letting the user do :after will be the best approach
20:24:10
vms14
it is good style to make a closure to have two or more functions sharing "local" variables
20:25:30
vms14
I have a *buffer* global variable but I have other functions that would need two different buffers
20:26:52
vms14
should I change it locally, since it's special, or make a closure returning those functions, or what would be a good way?
20:36:15
vms14
Xach yes, sorry. In my context I guess what I really need is local stuff inside a function
20:55:48
aeth
vms14: You can wrap a DEFUN in a LET, but that makes it no longer top level so macros that generate DEFUNs might not work properly (since those tend to assume they're at the top level, so they could do e.g. "(progn (declaim (inline foo)) (defun foo ..." since PROGN does keep functions at top level)
20:57:58
aeth
vms14: i.e. (let ((variable 42)) (defun foo () variable) (defun (setf foo) (value) (setf variable value))) (format t "~A~%" (foo)) (setf (foo) 1) (format t "~A~%" (foo)) ; prints 42 and then prints 1
21:00:00
aeth
Are you asking if variable in my example would be accessible outside of those two functions? No, because it's lexically scoped and it's effectively creating a closure, just a global one, not one that you're used to seeing
21:00:42
jasom
vms14: that won't work unless oh is declared special (which seems unlikely given its name)
21:00:59
aeth
vms14: Lexical closures are afaik the only true way of encapsulation in CL (besides messing with symbols or the MOP or something)
21:02:37
jasom
vms14: I *think* you want something like (let ((*buffer* FOO)) (some-function)) (let ((*buffer* BAR)) (some-function)) but I may be misunderstanding your question; perhaps pastebin some code?
21:04:05
aeth
vms14: is (function) a call to FUNCTION or is it a shortcut for (defun function ...) in your notation?
21:05:48
aeth
vms14: the issue with macros is if you did `(let ((oh nil)) ...) in a macro. Then anyone who knows the variable "oh" could access it, and anyone who used the variable "oh" outside of the macro would unexpectedly get their variable overridden inside of the macro, so you gensym instead of directly binding a let, if you have a ,@body (you don't always make macros like that)
21:07:20
aeth
vms14: The thing about *oh* is called dynamic scoping, and it's a separate thing. Basically, (let ((*oh* 42)) (foo)) will replace *oh* in (foo) and all of its callers with the local 42 instead of the global (whatever it is). So it's a separate, special scoping rule that's probably implemented as a stack or at least can be thought of as a stack.
21:07:28
vms14
so with recursion a function using let with a special variable and calling other functions that use this variable the function will preserve all the *stuff* values in a recursive way
21:08:44
aeth
vms14: I see what you're probably trying to do. You're probably trying to build up a list in a bunch of recursive calls, but you can't just pass in the list because push will only modify the local binding. So (push 42 *oh*) will keep building the list while (push 42 oh) will not. Is that correct?
21:10:38
aeth
I love how that pastebin tries to get me to login to download the text. Easily the worse pastebin for that that I've seen. At least I can just copy and paste it into emacs.
21:13:09
aeth
vms14: here's how I generate HTML: https://gitlab.com/mbabich/cl-documents/blob/873268445cd2508e59ada0eb651e0c78d42b68d8/generate-html.lisp https://gitlab.com/mbabich/cl-documents/blob/873268445cd2508e59ada0eb651e0c78d42b68d8/write-html.lisp
21:14:23
aeth
vms14: you don't need to work with buffers at all, you can just work with streams and model it like a write function, e.g. (write-string "foo" some-stream)
21:15:37
aeth
vms14: What I do is I write to a string stream at compile time, unless there's some special feature, and if there's a special feature, then I write (code char 0) and push that special feature to something that ultimately gets returned so that I can handle it elsewhere.
21:16:12
aeth
It's... kind of involved to do it efficiently, but not too hard to do it naively (since all that "unless there's..." can just be ignored)
21:16:27
vms14
my idea was to maintain two buffers, one for tag attributes and another for the output buffer
21:17:32
aeth
for output, just always pass in a stream argument and write to that stream. Technically, using a UTF-8 stream via babel would probably be more efficient than writing to a string that ultimately becomes UTF-8 at some later point, but efficiency isn't the most important thing if you just need to get it to run
21:19:35
aeth
you'll want '(img :src "url") or if in a macro just (img :src "url") because the macro will implicitly quote it and you'll want the whole thing quoted
21:20:44
aeth
I mean, I normally do (setf *print-case* :downcase) in the REPL when I'm doing a lot of macroexpand so I can actually read the macros
21:24:20
aeth
vms14: You shouldn't be doing FORMAT *buffer*, though. You really want to only work with streams, and then when you call it, in the caller, do (with-output-to-string (output) (process-html-list list output))
21:25:07
aeth
Even if buffer is more efficient for format, (1) being a special variable instead of a lexical variable is probably going to kill absolutely all of the efficiency gains (and it doesn't even know if it's a buffer or a stream or the symbol nil) and (2) it makes it much harder and less abstract for this particular problem
21:26:57
aeth
You don't want a default value here because you're going to be using it for its side effect, recursively. If you want to add a default value, write a wrapper function where it's optional instead of making it be optional everywhere
21:27:24
aeth
You want to keep things as simple as possible. Nice to have features can be added later.
21:29:08
Shinmera
Phew, me and my two friends finished our Ludum Dare game jam entry just now! Three days to make a game, and of course we used lisp. https://ldjam.com/events/ludum-dare/45/outsider
21:30:51
aeth
vms14: Arrays can be faster, but if you wanted to really use arrays to their full potential you would have to do some really strange things. First, you'd have to give it a maximum size so there's no bounds checking. Second, you'd want to directly write UTF-8 bytes instead of characters to strings. Third, you'd have to use DECLARE on every function. This isn't really worth it unless you need the performance.
21:31:36
aeth
And after all that I'm not sure it would actually be that much faster, since you'd have to use something like babel instead of the implementation's potentially very optimized character writer. It would use less memory, though
21:31:57
vms14
(defun make-text-buffer () (make-array 0 :adjustable t :fill-pointer t :element-type 'character))
21:32:43
aeth
vms14: Anyway, as long as you only use FORMAT, your buffer code should be identical to stream-using code because there's no difference at all (unless you wanted to enforce your choice of using a buffer via a type check or type declaration)
21:33:28
aeth
You might want to check that it's not NIL, though, since format nil will only work as expected on one FORMAT statement.
21:33:52
aeth
(although technically speaking you could do it in one format statement and use ~/foo/ to do your recursion, unless that's disallowed by FORMAT)
21:38:03
aeth
vms14: (defun foo (list &optional (format-destination *standard-output*)) (format format-destination "~A~%" list)) (foo (list 1 2 3))
21:39:01
aeth
vms14: That basic form will give you something that will work with anything that format works with (except NIL as soon as you have more than one FORMAT, so maybe check for that and error) and if the user doesn't provide anything, then it just goes to *standard-output*, which can be rebound locally since it's a special (dynamic) variable
21:41:22
aeth
vms14: The next thing that you should do is that you should change your syntax. (foo :bar 42 "Hello!") is actually really difficult and advanced because you have to parse it to know where the plist ends and the body begins. Try using (foo (:bar 42) "Hello!") instead. This syntax is regular. Then, if you come across a list you can parse it with destructuring-bind.
21:41:48
aeth
vms14: That syntax is then just (destructuring-bind (tag attribute-plist &body body) ...)
21:42:39
aeth
vms14: Then you can handle attributes as their own special case and call them with (process-html-attributes list format-destination) without having to worry about process-html-list having to know at all about attributes
21:44:24
aeth
vms14: If you want to have tiny little functions for everything in parse-html-list, you can use FLET and put them at the top of PROCESS-HTML-LIST and then you don't have to pass format-destination and you don't have to make a global *buffer* because it's creating a closure. So (flet ((start-tag (tag) (format format-destination "<~a" tag)) ...
21:46:06
aeth
vms14: This is what is required to do (title :color red "hi") instead of (title (:color red) "hi) : https://gitlab.com/mbabich/cl-documents/blob/873268445cd2508e59ada0eb651e0c78d42b68d8/write-html.lisp#L26-69
21:47:11
aeth
vms14: That's the wrong thing to think about at this point. At this point you want a direct representation of HTML in s-expressions translated directly to an HTML string (or character buffer)
21:47:28
aeth
vms14: Once it's in s-expressions, you can preprocess it using list processing stuff later on as an earlier stage in your generation
21:48:00
aeth
vms14: the only thing special I do is insert "<!DOCTYPE html>" in front of "<html>" when that tag comes up because it's kind of important not to leave out
21:49:52
aeth
vms14: You are writing a function that writes HTML strings when given HTML in s-expression form, so that's all it should do (I kind of break this rule myself to allow support for some very simple things, but that's mainly because I couldn't find a better way to do it)
21:50:08
aeth
vms14: So you shouldn't be thinking about "so for :color red I should have a class .red and create it if not exists"
21:51:29
aeth
vms14: Yes, but you will probably never finish if you make your functions do too many things at once... if you make them too smart.
21:52:52
aeth
vms14: JS is much harder than the rest, and isn't really critical for the basic task, so you should ignore that for now. And when you do write JS, you should write JS in a direct s-expression representation of JS, rather than trying to do anything fancy, because it's much easier to do fancy things as s-expression->s-expression
21:56:56
aeth
vms14: Anyway, no matter how much you hate the source language and think that it can be improved upon, you can't do that in the function that calls FORMAT because then you'll add too much logic by the time you're done. What you'll want to eventually do is (generate-html (c (b (a your-awesome-language))) buffer)
22:02:05
vms14
the problem is the only way to have a lisp language instead of html+css is making a transpiler
22:04:50
aeth
vms14: Yes, but you were looking at the s-expression problem incorrectly. If you use the syntax (foo (:bar 42) "Hello!") then there are only two cases at the top level. Either it is a list or it is not a list. If it is not a list, then it's in the body and should be an escaped string (to escape stuff like <). If it is in a list, then it has the regular structure (destructuring-bind (tag attribute-plist &body body) item ...)
22:06:05
aeth
In all recursive calls, pass format-destination even though you want to make it a keyword argument and optional for the caller. Then, you don't need *buffer* as a global, since buffer is passed each time
22:07:56
vms14
then how would be your syntax for a lisp language like (title "hi" :color "red" :onclick #'dosomething)
22:10:06
aeth
vms14: That's a separate problem. There, you turn (title "hi" :color "red" :onclick #'dosomething) into (title (:color "red" :onclick "dosomething()") "hi") which is regular and simple and known, and can then be written by the writing function that I described
22:16:47
aeth
vms14: you don't even need to do the writing function if what you really want to do is the fancy part, since the writing function has been done a dozen times before (sometimes with the syntax (:foo :key "value" "body1" "body2") and sometimes with the syntax (:foo (:key "value") "body1" "body2") and sometimes with other syntax, but none of that matters if you're processing it anyway)
22:17:47
aeth
the writing part isn't as easy as it seems because you have to escape <, >, and &, and if in an attribute you also have to escape "
22:35:08
thijso
Shinmera: looks interesting, but it's way past my bedtime here. I'll try to remember to look at it tomorrow. Or remind me...